Compare commits

...

No commits in common. "gh-pages" and "main" have entirely different histories.

824 changed files with 327786 additions and 3122 deletions

5
.cargo/config.toml Normal file
View file

@ -0,0 +1,5 @@
[build]
rustc-wrapper = "sccache"
[target.x86_64-unknown-linux-gnu]
linker = "clang"

52
.env.embedded Normal file
View file

@ -0,0 +1,52 @@
# BotServer Embedded Configuration
# For Orange Pi, Raspberry Pi, and other ARM SBCs
# Server
HOST=0.0.0.0
PORT=9000
RUST_LOG=info
# Database (SQLite for embedded, no PostgreSQL needed)
DATABASE_URL=sqlite:///opt/botserver/data/botserver.db
# LLM Configuration - Local llama.cpp
LLM_PROVIDER=llamacpp
LLM_API_URL=http://127.0.0.1:8080
LLM_MODEL=tinyllama
# Alternative: Use remote API
# LLM_PROVIDER=openai
# LLM_API_URL=https://api.openai.com/v1
# LLM_API_KEY=sk-...
# Alternative: Ollama (if installed)
# LLM_PROVIDER=ollama
# LLM_API_URL=http://127.0.0.1:11434
# LLM_MODEL=tinyllama
# Memory limits for embedded
MAX_CONTEXT_TOKENS=2048
MAX_RESPONSE_TOKENS=512
STREAMING_ENABLED=true
# Embedded UI
STATIC_FILES_PATH=/opt/botserver/ui
DEFAULT_UI=embedded
# WebSocket
WS_PING_INTERVAL=30
WS_TIMEOUT=300
# Security (change in production!)
JWT_SECRET=embedded-change-me-in-production
CORS_ORIGINS=*
# Logging
LOG_FILE=/opt/botserver/data/botserver.log
LOG_MAX_SIZE=10M
LOG_RETENTION=7
# Performance tuning for low-memory devices
# Uncomment for <2GB RAM devices
# RUST_BACKTRACE=0
# MALLOC_ARENA_MAX=2

36
.env.example Normal file
View file

@ -0,0 +1,36 @@
# BotServer Environment Configuration
# =====================================
#
# ONLY VAULT VARIABLES ARE ALLOWED IN THIS FILE!
# All secrets (DATABASE_URL, API keys, etc.) MUST be stored in Vault.
# NO LEGACY FALLBACK - Vault is mandatory.
#
# Vault paths for secrets:
# - gbo/tables - PostgreSQL credentials (host, port, database, username, password)
# - gbo/drive - MinIO/S3 credentials (accesskey, secret)
# - gbo/cache - Redis credentials (password)
# - gbo/directory - Zitadel credentials (url, project_id, client_id, client_secret)
# - gbo/email - Email credentials (username, password)
# - gbo/llm - LLM API keys (openai_key, anthropic_key, groq_key)
# - gbo/encryption - Encryption keys (master_key)
# - gbo/meet - LiveKit credentials (api_key, api_secret)
# - gbo/alm - Forgejo credentials (url, admin_password, runner_token)
# - gbo/vectordb - Qdrant credentials (url, api_key)
# - gbo/observability - InfluxDB credentials (url, org, bucket, token)
# =====================
# VAULT CONFIGURATION - ONLY THESE VARS ARE ALLOWED
# =====================
# Vault server address
VAULT_ADDR=https://localhost:8200
# Vault authentication token (generated during vault init)
# This will be populated automatically after first bootstrap
VAULT_TOKEN=
# Skip TLS verification for development (set to false in production)
VAULT_SKIP_VERIFY=true
# Cache TTL for secrets in seconds (default: 300 = 5 minutes)
VAULT_CACHE_TTL=300

View file

@ -0,0 +1,89 @@
name: BotServer CI
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
env:
CARGO_BUILD_JOBS: 8
CARGO_NET_RETRY: 10
jobs:
build:
runs-on: gbo
steps:
- name: Disable SSL verification
run: git config --global http.sslVerify false
- name: Setup Workspace
run: |
# Clone the main gb repository
git clone --depth 1 --branch main https://alm.pragmatismo.com.br/GeneralBots/gb.git workspace
cd workspace
git submodule update --init --depth 1 botlib
# Clone botserver separately
git clone --depth 1 --branch main https://alm.pragmatismo.com.br/GeneralBots/BotServer.git botserver
# Remove all members except botserver and botlib from workspace
sed -i '/"botapp",/d' Cargo.toml
sed -i '/"botdevice",/d' Cargo.toml
sed -i '/"bottest",/d' Cargo.toml
sed -i '/"botui",/d' Cargo.toml
sed -i '/"botbook",/d' Cargo.toml
sed -i '/"botmodels",/d' Cargo.toml
sed -i '/"botplugin",/d' Cargo.toml
sed -i '/"bottemplates",/d' Cargo.toml
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpq-dev libssl-dev liblzma-dev pkg-config
- name: Install Rust
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install sccache
run: |
wget https://github.com/mozilla/sccache/releases/download/v0.8.2/sccache-v0.8.2-x86_64-unknown-linux-musl.tar.gz
tar xzf sccache-v0.8.2-x86_64-unknown-linux-musl.tar.gz
mv sccache-v0.8.2-x86_64-unknown-linux-musl/sccache $HOME/.cargo/bin/sccache
chmod +x $HOME/.cargo/bin/sccache
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
$HOME/.cargo/bin/sccache --start-server || true
- name: Setup environment
run: sudo cp /opt/gbo/bin/system/.env . 2>/dev/null || true
- name: Build BotServer
working-directory: workspace
run: |
cargo build -p botserver -j 8 2>&1 | tee /tmp/build.log
ls -lh target/debug/botserver
sccache --show-stats || true
- name: Save build log
if: always()
run: |
sudo mkdir -p /opt/gbo/logs
sudo cp /tmp/build.log /opt/gbo/logs/botserver-$(date +%Y%m%d-%H%M%S).log || true
- name: Deploy
working-directory: workspace
run: |
lxc exec bot:pragmatismo-system -- systemctl stop system || true
sudo cp target/debug/botserver /opt/gbo/bin/system/
sudo chmod +x /opt/gbo/bin/system/botserver
lxc exec bot:pragmatismo-system -- systemctl start system || true

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
.tmp*
.tmp/*
*.log
target*
.env
*.env
work
*.out
bin
botserver-stack
*logfile*
*-log*
docs/book
*.rdb
botserver-installers
.git-rewrite

53
.product Normal file
View file

@ -0,0 +1,53 @@
# Product Configuration File
# This file defines white-label settings for the application.
#
# All occurrences of "General Bots" will be replaced by the 'name' value.
# Only apps listed in 'apps' will be active in the suite (and their APIs enabled).
# The 'theme' value sets the default theme for the UI.
# Product name (replaces "General Bots" throughout the application)
name=General Bots
# Active apps (comma-separated list)
# Available apps: chat, mail, calendar, drive, tasks, docs, paper, sheet, slides,
# meet, research, sources, analytics, admin, monitoring, settings
# Only listed apps will be visible in the UI and have their APIs enabled.
apps=chat,drive,tasks,sources,settings
# Search mechanism enabled
# Controls whether the omnibox/search toolbar is displayed in the suite
# Set to false to disable the search mechanism
search_enabled=false
# Menu launcher enabled
# Controls whether the apps menu launcher is displayed in the suite
# Set to false to hide the menu launcher button
# When the menu is empty (no apps to show), it will be automatically hidden
menu_launcher_enabled=false
# Default theme
# Available themes: dark, light, blue, purple, green, orange, sentient, cyberpunk,
# retrowave, vapordream, y2kglow, arcadeflash, discofever, grungeera,
# jazzage, mellowgold, midcenturymod, polaroidmemories, saturdaycartoons,
# seasidepostcard, typewriter, 3dbevel, xeroxui, xtreegold
theme=sentient
# Logo URL (optional - leave empty to use default)
# Can be a relative path or absolute URL
logo=
# Favicon URL (optional - leave empty to use default)
favicon=
# Primary color override (optional - hex color code)
# Example: #d4f505
primary_color=
# Support email (optional)
support_email=
# Documentation URL (optional)
docs_url=https://docs.pragmatismo.com.br
# Copyright text (optional - {year} will be replaced with current year)
copyright=© {year} {name}. All rights reserved.

41
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,41 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'botserver'",
"cargo": {
"args": ["build", "--bin=botserver", "--package=botserver"],
"filter": {
"name": "botserver",
"kind": "bin"
}
},
"args": ["--desktop"],
"env": {
"RUST_LOG": "trace,aws_sigv4=off,aws_smithy_checksums=off,mio=off,reqwest=off,aws_runtime=off,aws_smithy_http_client=off,rustls=off,hyper_util=off,aws_smithy_runtime=off,aws_smithy_runtime_api=off,tracing=off,aws_sdk_s3=off"
},
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'botserver'",
"cargo": {
"args": ["test", "--no-run", "--bin=botserver", "--package=botserver"],
"filter": {
"name": "botserver",
"kind": "bin"
}
},
"args": [],
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceFolder}"
}
]
}

16
.zed/debug.json Normal file
View file

@ -0,0 +1,16 @@
[
{
"label": "Debug BotServer",
"build": {
"command": "cargo",
"args": ["build"],
},
"program": "$ZED_WORKTREE_ROOT/target/debug/botserver",
"env": {
"RUST_LOG": "trace",
},
"sourceLanguages": ["rust"],
"request": "launch",
"adapter": "CodeLLDB",
},
]

222
3rdparty.toml Normal file
View file

@ -0,0 +1,222 @@
# Third-Party Dependencies Configuration
# ======================================
# This file lists all external downloads required by botserver.
#
# Caching Behavior:
# - On first run, files are downloaded from the URLs below
# - Downloaded files are cached in ./botserver-installers/
# - On subsequent runs, cached files are used instead of downloading
# - To force re-download, delete the cached file
#
# Offline Installation:
# - Pre-download all files to ./botserver-installers/
# - The installer will use cached files automatically
# - You can safely delete ./botserver-stack/ without losing downloads
[cache_settings]
# Directory where downloaded files are cached (relative to botserver root)
cache_dir = "botserver-installers"
# Components
# ==========
# Each component has:
# - url: Download URL
# - filename: Local filename in cache
# - sha256: Optional checksum for verification (empty = skip verification)
[components.drive]
name = "MinIO Object Storage"
url = "https://dl.min.io/server/minio/release/linux-amd64/minio"
filename = "minio"
sha256 = ""
[components.tables]
name = "PostgreSQL Database"
url = "https://github.com/theseus-rs/postgresql-binaries/releases/download/17.2.0/postgresql-17.2.0-x86_64-unknown-linux-gnu.tar.gz"
filename = "postgresql-17.2.0-x86_64-unknown-linux-gnu.tar.gz"
sha256 = ""
[components.cache]
name = "Valkey Cache (Redis-compatible)"
# Precompiled binary from download.valkey.io (jammy for GLIBC 2.36 compatibility)
url = "https://download.valkey.io/releases/valkey-8.1.5-jammy-x86_64.tar.gz"
filename = "valkey-8.1.5-jammy-x86_64.tar.gz"
sha256 = ""
[components.llm]
name = "Llama.cpp Server"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-ubuntu-x64.zip"
filename = "llama-b7345-bin-ubuntu-x64.zip"
sha256 = "91b066ecc53c20693a2d39703c12bc7a69c804b0768fee064d47df702f616e52"
[components.email]
name = "Stalwart Mail Server"
url = "https://github.com/stalwartlabs/mail-server/releases/download/v0.10.7/stalwart-mail-x86_64-linux.tar.gz"
filename = "stalwart-mail-x86_64-linux.tar.gz"
sha256 = ""
[components.proxy]
name = "Caddy Web Server"
url = "https://github.com/caddyserver/caddy/releases/download/v2.9.1/caddy_2.9.1_linux_amd64.tar.gz"
filename = "caddy_2.9.1_linux_amd64.tar.gz"
sha256 = ""
[components.directory]
name = "Zitadel Identity Provider"
url = "https://github.com/zitadel/zitadel/releases/download/v4.11.1/zitadel-linux-amd64.tar.gz"
filename = "zitadel-linux-amd64.tar.gz"
sha256 = ""
[components.alm]
name = "Forgejo Git Server"
url = "https://codeberg.org/forgejo/forgejo/releases/download/v14.0.2/forgejo-14.0.2-linux-amd64"
filename = "forgejo-14.0.2-linux-amd64"
sha256 = ""
[components.alm_ci]
name = "Forgejo Actions Runner"
url = "https://code.forgejo.org/forgejo/runner/releases/download/v6.3.1/forgejo-runner-6.3.1-linux-amd64"
filename = "forgejo-runner-6.3.1-linux-amd64"
sha256 = ""
[components.dns]
name = "CoreDNS Server"
url = "https://github.com/coredns/coredns/releases/download/v1.11.1/coredns_1.11.1_linux_amd64.tgz"
filename = "coredns_1.11.1_linux_amd64.tgz"
sha256 = ""
[components.webmail]
name = "Roundcube Webmail"
url = "https://github.com/roundcube/roundcubemail/releases/download/1.6.6/roundcubemail-1.6.6-complete.tar.gz"
filename = "roundcubemail-1.6.6-complete.tar.gz"
sha256 = ""
[components.meet]
name = "LiveKit Media Server"
url = "https://github.com/livekit/livekit/releases/download/v2.8.2/livekit_2.8.2_linux_amd64.tar.gz"
filename = "livekit_2.8.2_linux_amd64.tar.gz"
sha256 = ""
[components.table_editor]
name = "NocoDB"
url = "http://get.nocodb.com/linux-x64"
filename = "nocodb-linux-x64"
sha256 = ""
[components.vector_db]
name = "Qdrant Vector Database"
url = "https://github.com/qdrant/qdrant/releases/latest/download/qdrant-x86_64-unknown-linux-gnu.tar.gz"
filename = "qdrant-x86_64-unknown-linux-gnu.tar.gz"
sha256 = ""
[components.timeseries_db]
name = "InfluxDB Time Series Database"
url = "https://download.influxdata.com/influxdb/releases/influxdb2-2.7.5-linux-amd64.tar.gz"
filename = "influxdb2-2.7.5-linux-amd64.tar.gz"
sha256 = ""
[components.vault]
name = "HashiCorp Vault"
url = "https://releases.hashicorp.com/vault/1.15.4/vault_1.15.4_linux_amd64.zip"
filename = "vault_1.15.4_linux_amd64.zip"
sha256 = ""
[components.observability]
name = "Vector Log Aggregator"
url = "https://packages.timber.io/vector/0.35.0/vector-0.35.0-x86_64-unknown-linux-gnu.tar.gz"
filename = "vector-0.35.0-x86_64-unknown-linux-gnu.tar.gz"
sha256 = ""
# LLM Models
# ==========
# Large model files for AI/ML functionality
[models.deepseek_small]
name = "DeepSeek R1 Distill Qwen 1.5B (Q3_K_M)"
url = "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-1.5B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf"
filename = "DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf"
sha256 = ""
[models.bge_embedding]
name = "BGE Small EN v1.5 Embedding Model"
url = "https://huggingface.co/CompendiumLabs/bge-small-en-v1.5-gguf/resolve/main/bge-small-en-v1.5-f32.gguf"
filename = "bge-small-en-v1.5-f32.gguf"
sha256 = ""
# Platform-specific llama.cpp variants
# =====================================
# These are alternative builds for different platforms/GPU support
[components.llm_linux_vulkan]
name = "Llama.cpp Server (Linux Vulkan)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-ubuntu-vulkan-x64.zip"
filename = "llama-b7345-bin-ubuntu-vulkan-x64.zip"
sha256 = "03f0b3acbead2ddc23267073a8f8e0207937c849d3704c46c61cf167c1001442"
[components.llm_linux_s390x]
name = "Llama.cpp Server (Linux s390x)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-ubuntu-s390x.zip"
filename = "llama-b7345-bin-ubuntu-s390x.zip"
sha256 = "688ddad6996b1166eaaa76d5025e304c684116efe655e6e881d877505ecffccb"
[components.llm_macos_arm64]
name = "Llama.cpp Server (macOS ARM64)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-macos-arm64.zip"
filename = "llama-b7345-bin-macos-arm64.zip"
sha256 = "72ae9b4a4605aa1223d7aabaa5326c66c268b12d13a449fcc06f61099cd02a52"
[components.llm_macos_x64]
name = "Llama.cpp Server (macOS x64)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-macos-x64.zip"
filename = "llama-b7345-bin-macos-x64.zip"
sha256 = "bec6b805cf7533f66b38f29305429f521dcb2be6b25dbce73a18df448ec55cc5"
[components.llm_win_cpu_x64]
name = "Llama.cpp Server (Windows x64 CPU)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-win-cpu-x64.zip"
filename = "llama-b7345-bin-win-cpu-x64.zip"
sha256 = "ea449082c8e808a289d9a1e8331f90a0379ead4dd288a1b9a2d2c0a7151836cd"
[components.llm_win_cpu_arm64]
name = "Llama.cpp Server (Windows ARM64 CPU)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-win-cpu-arm64.zip"
filename = "llama-b7345-bin-win-cpu-arm64.zip"
sha256 = "91e3ff43c123c7c30decfe5a44c291827c1e47359abaa2fbad1eb5392b3a0d85"
[components.llm_win_cuda12]
name = "Llama.cpp Server (Windows CUDA 12.4)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-win-cuda-12.4-x64.zip"
filename = "llama-b7345-bin-win-cuda-12.4-x64.zip"
sha256 = "7a82aba2662fa7d4477a7a40894de002854bae1ab8b0039888577c9a2ca24cae"
[components.llm_win_cuda13]
name = "Llama.cpp Server (Windows CUDA 13.1)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-win-cuda-13.1-x64.zip"
filename = "llama-b7345-bin-win-cuda-13.1-x64.zip"
sha256 = "06ea715cefb07e9862394e6d1ffa066f4c33add536b1f1aa058723f86ae05572"
[components.llm_win_vulkan]
name = "Llama.cpp Server (Windows Vulkan)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/llama-b7345-bin-win-vulkan-x64.zip"
filename = "llama-b7345-bin-win-vulkan-x64.zip"
sha256 = "3e948bee438f46c8ea0a3faf0416549391ee945ffa624b25bc1f73d60d668679"
# CUDA runtime libraries (required for CUDA builds on Windows)
[components.cudart_win_cuda12]
name = "CUDA Runtime (Windows CUDA 12.4)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/cudart-llama-bin-win-cuda-12.4-x64.zip"
filename = "cudart-llama-bin-win-cuda-12.4-x64.zip"
sha256 = "8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6"
[components.cudart_win_cuda13]
name = "CUDA Runtime (Windows CUDA 13.1)"
url = "https://github.com/ggml-org/llama.cpp/releases/download/b7345/cudart-llama-bin-win-cuda-13.1-x64.zip"
filename = "cudart-llama-bin-win-cuda-13.1-x64.zip"
sha256 = "f96935e7e385e3b2d0189239077c10fe8fd7e95690fea4afec455b1b6c7e3f18"
# Optional larger models (uncomment to include)
# [models.gpt_oss_20b]
# name = "GPT-OSS 20B F16 (requires 16GB+ VRAM or MoE)"
# url = "https://huggingface.co/unsloth/gpt-oss-20b-GGUF/resolve/main/gpt-oss-20b-F16.gguf"
# filename = "gpt-oss-20b-F16.gguf"
# sha256 = ""

101
3rdparty/llm_releases.json vendored Normal file
View file

@ -0,0 +1,101 @@
{
"llama_cpp": {
"version": "b7345",
"base_url": "https://github.com/ggml-org/llama.cpp/releases/download",
"binaries": {
"linux": {
"x64": {
"cpu": "llama-{version}-bin-ubuntu-x64.zip",
"cpu_tar": "llama-{version}-bin-ubuntu-x64.tar.gz",
"vulkan": "llama-{version}-bin-ubuntu-vulkan-x64.zip",
"vulkan_tar": "llama-{version}-bin-ubuntu-vulkan-x64.tar.gz"
},
"s390x": {
"cpu": "llama-{version}-bin-ubuntu-s390x.zip",
"cpu_tar": "llama-{version}-bin-ubuntu-s390x.tar.gz"
}
},
"macos": {
"arm64": {
"cpu": "llama-{version}-bin-macos-arm64.zip",
"cpu_tar": "llama-{version}-bin-macos-arm64.tar.gz"
},
"x64": {
"cpu": "llama-{version}-bin-macos-x64.zip",
"cpu_tar": "llama-{version}-bin-macos-x64.tar.gz"
}
},
"windows": {
"x64": {
"cpu": "llama-{version}-bin-win-cpu-x64.zip",
"cuda_12": "llama-{version}-bin-win-cuda-12.4-x64.zip",
"cuda_13": "llama-{version}-bin-win-cuda-13.1-x64.zip",
"vulkan": "llama-{version}-bin-win-vulkan-x64.zip",
"sycl": "llama-{version}-bin-win-sycl-x64.zip",
"hip": "llama-{version}-bin-win-hip-radeon-x64.zip"
},
"arm64": {
"cpu": "llama-{version}-bin-win-cpu-arm64.zip",
"opencl_adreno": "llama-{version}-bin-win-opencl-adreno-arm64.zip"
}
},
"ios": {
"xcframework": "llama-{version}-xcframework.zip",
"xcframework_tar": "llama-{version}-xcframework.tar.gz"
}
},
"cuda_runtime": {
"windows": {
"cuda_12": "cudart-llama-bin-win-cuda-12.4-x64.zip",
"cuda_13": "cudart-llama-bin-win-cuda-13.1-x64.zip"
}
},
"checksums": {
"llama-b7345-bin-ubuntu-x64.zip": "sha256:91b066ecc53c20693a2d39703c12bc7a69c804b0768fee064d47df702f616e52",
"llama-b7345-bin-ubuntu-x64.tar.gz": "sha256:c5f4c8111887072a5687b42e0700116e93eddf14c5401fa7eba3ab0b8481ff4e",
"llama-b7345-bin-ubuntu-vulkan-x64.zip": "sha256:03f0b3acbead2ddc23267073a8f8e0207937c849d3704c46c61cf167c1001442",
"llama-b7345-bin-ubuntu-vulkan-x64.tar.gz": "sha256:9b02b406106cd20ea0568c43c28c587d7e4908b5b649e943adebb0e1ae726076",
"llama-b7345-bin-ubuntu-s390x.zip": "sha256:688ddad6996b1166eaaa76d5025e304c684116efe655e6e881d877505ecffccb",
"llama-b7345-bin-ubuntu-s390x.tar.gz": "sha256:118011b38b02fee21596ab5b1c40b56369da514645394b6528a466e18f4336f5",
"llama-b7345-bin-macos-arm64.zip": "sha256:72ae9b4a4605aa1223d7aabaa5326c66c268b12d13a449fcc06f61099cd02a52",
"llama-b7345-bin-macos-arm64.tar.gz": "sha256:dc7c6b64848180259db19eb5d8ee8424cffcbb053960e5c45d79db6b9ac4f40d",
"llama-b7345-bin-macos-x64.zip": "sha256:bec6b805cf7533f66b38f29305429f521dcb2be6b25dbce73a18df448ec55cc5",
"llama-b7345-bin-macos-x64.tar.gz": "sha256:9267a292f39a86b2ee5eaa553a06f4a2fda2aee35142cde40a9099432b304313",
"llama-b7345-bin-win-cpu-x64.zip": "sha256:ea449082c8e808a289d9a1e8331f90a0379ead4dd288a1b9a2d2c0a7151836cd",
"llama-b7345-bin-win-cpu-arm64.zip": "sha256:91e3ff43c123c7c30decfe5a44c291827c1e47359abaa2fbad1eb5392b3a0d85",
"llama-b7345-bin-win-cuda-12.4-x64.zip": "sha256:7a82aba2662fa7d4477a7a40894de002854bae1ab8b0039888577c9a2ca24cae",
"llama-b7345-bin-win-cuda-13.1-x64.zip": "sha256:06ea715cefb07e9862394e6d1ffa066f4c33add536b1f1aa058723f86ae05572",
"llama-b7345-bin-win-vulkan-x64.zip": "sha256:3e948bee438f46c8ea0a3faf0416549391ee945ffa624b25bc1f73d60d668679",
"llama-b7345-bin-win-sycl-x64.zip": "sha256:708ddb786cdeb43ceadaa57c0ca669ce05b86753bf859f5a95012c2ea481f9da",
"llama-b7345-bin-win-hip-radeon-x64.zip": "sha256:ba1fe643e27bae8dcdf6d7be459a6dc5d8385f179e71e749c53f52083c68e107",
"llama-b7345-bin-win-opencl-adreno-arm64.zip": "sha256:59d625d21fb64294b075c61ec1a5f01d394baf826bee2df847d0ea3ed21fa3f3",
"llama-b7345-xcframework.zip": "sha256:c94e870ba844e4938d6fccf0bfd64c9fe57884a14a3e2a4966e56e35a6cbaef4",
"llama-b7345-xcframework.tar.gz": "sha256:a542ceace2621d9d860f2ec64c1b2294ac71f292106b95dcaf239aec0a06dd55",
"cudart-llama-bin-win-cuda-12.4-x64.zip": "sha256:8c79a9b226de4b3cacfd1f83d24f962d0773be79f1e7b75c6af4ded7e32ae1d6",
"cudart-llama-bin-win-cuda-13.1-x64.zip": "sha256:f96935e7e385e3b2d0189239077c10fe8fd7e95690fea4afec455b1b6c7e3f18"
}
},
"models": {
"default_llm": {
"name": "DeepSeek-R1-Distill-Qwen-1.5B",
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-1.5B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf",
"filename": "DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf",
"size_mb": 1100,
"description": "Small reasoning model, good for CPU or minimal GPU (4GB VRAM)"
},
"default_embedding": {
"name": "BGE Small EN v1.5",
"url": "https://huggingface.co/CompendiumLabs/bge-small-en-v1.5-gguf/resolve/main/bge-small-en-v1.5-f32.gguf",
"filename": "bge-small-en-v1.5-f32.gguf",
"size_mb": 130,
"description": "Embedding model for vector search"
},
"large_llm": {
"name": "GPT-OSS 20B",
"url": "https://huggingface.co/unsloth/gpt-oss-20b-GGUF/resolve/main/gpt-oss-20b-F16.gguf",
"filename": "gpt-oss-20b-F16.gguf",
"size_mb": 40000,
"description": "Large model for GPU with 16GB+ VRAM"
}
}
}

466
3rdparty/mcp_servers.json vendored Normal file
View file

@ -0,0 +1,466 @@
{
"mcp_servers": [
{
"id": "azure-cosmos-db",
"name": "Azure Cosmos DB",
"description": "Enables Agents to interact with and retrieve data from Azure Cosmos DB accounts.",
"icon": "azure-cosmos-db",
"type": "Local",
"category": "Database",
"provider": "Microsoft"
},
{
"id": "azure-database-postgresql",
"name": "Azure Database for PostgreSQL",
"description": "Enables Agents to interact with and retrieve data from Azure Database for PostgreSQL resources using natural language prompts.",
"icon": "azure-database-postgresql",
"type": "Local",
"category": "Database",
"provider": "Microsoft"
},
{
"id": "azure-databricks-genie",
"name": "Azure Databricks Genie",
"description": "Azure Databricks Genie MCP server lets AI agents connect to Genie spaces so users can ask natural language questions and get specialized answers from their data easily.",
"icon": "azure-databricks-genie",
"type": "Remote",
"category": "Analytics",
"provider": "Microsoft"
},
{
"id": "azure-managed-redis",
"name": "Azure Managed Redis",
"description": "Azure Managed Redis MCP Server provides a natural language interface for agentic apps to interact with Azure Managed Redis—a high-speed, in-memory datastore that is ideal for low-latency use cases like agent memory, vector data store and semantic caching.",
"icon": "azure-managed-redis",
"type": "Local",
"category": "Database",
"provider": "Microsoft"
},
{
"id": "azure-sql",
"name": "Azure SQL MCP Server",
"description": "A secure, self-hosted MCP for interacting with SQL data (Azure SQL, SQL MI, SQL DW, SQL Server).",
"icon": "azure-sql",
"type": "Local",
"category": "Database",
"provider": "Microsoft"
},
{
"id": "elasticsearch",
"name": "Elasticsearch",
"description": "Search, retrieve, and analyze Elasticsearch data in developer and agentic workflows.",
"icon": "elasticsearch",
"type": "Remote",
"category": "Search",
"provider": "Elastic"
},
{
"id": "mongodb",
"name": "MongoDB MCP Server",
"description": "MongoDB MCP Server allows any MCP-aware LLM to connect to MongoDB Atlas for admin tasks and to MongoDB databases for data operations, all through natural language.",
"icon": "mongodb",
"type": "Local",
"category": "Database",
"provider": "MongoDB"
},
{
"id": "pinecone-assistant",
"name": "Pinecone Assistant MCP Server",
"description": "Pinecone Assistant MCP server helps prototype and deploy assistants that retrieve context-aware answers grounded in proprietary data.",
"icon": "pinecone",
"type": "Remote",
"category": "Vector Database",
"provider": "Pinecone"
},
{
"id": "vercel",
"name": "Vercel",
"description": "With Vercel MCP, you can explore projects, inspect failed deployments, fetch logs, and more right from your AI client.",
"icon": "vercel",
"type": "Remote",
"category": "Deployment",
"provider": "Vercel"
},
{
"id": "amplitude",
"name": "Amplitude MCP Server",
"description": "Search, access, and get insights on your Amplitude product analytics data.",
"icon": "amplitude",
"type": "Remote",
"category": "Analytics",
"provider": "Amplitude"
},
{
"id": "atlan",
"name": "Atlan",
"description": "The Atlan MCP server provides a set of tools that enable AI agents to work directly with Atlan metadata. These tools supply real-time context to AI environments, making it easier to search, explore, and update metadata without leaving your workflow.",
"icon": "atlan",
"type": "Remote",
"category": "Data Catalog",
"provider": "Atlan"
},
{
"id": "atlassian",
"name": "Atlassian",
"description": "Connect to Jira and Confluence for issue tracking and documentation.",
"icon": "atlassian",
"type": "Remote",
"category": "Productivity",
"provider": "Atlassian"
},
{
"id": "azure-language-foundry",
"name": "Azure Language in Foundry Tools",
"description": "The MCP server enables AI agents to access Azure Language in Foundry Tools for accurate, explainable and compliant NLP capabilities.",
"icon": "azure-language",
"type": "Remote",
"category": "AI/ML",
"provider": "Microsoft"
},
{
"id": "azure-speech",
"name": "Azure Speech MCP Server",
"description": "A hosted MCP server that exposes Azure Speech capabilities (speech-to-text, text-to-speech and streaming speech I/O) to agents and LLM workflows.",
"icon": "azure-speech",
"type": "Remote",
"category": "AI/ML",
"provider": "Microsoft"
},
{
"id": "box",
"name": "Box MCP Server",
"description": "Access and manage your Box content with AI-powered tools for file operations, collaboration, and metadata extraction.",
"icon": "box",
"type": "Remote",
"category": "Storage",
"provider": "Box"
},
{
"id": "cast-imaging",
"name": "CAST Imaging MCP Server",
"description": "Deterministic mapping of application architecture and code objects to support discovery, impact analysis, and technical debt remediation.",
"icon": "cast-imaging",
"type": "Remote",
"category": "DevOps",
"provider": "CAST"
},
{
"id": "celonis",
"name": "Celonis PI Graph MCP Server",
"description": "Agent toolkit that provides process intelligence context, action triggering, and write-back capabilities into Celonis.",
"icon": "celonis",
"type": "Remote",
"category": "Process Mining",
"provider": "Celonis"
},
{
"id": "exa",
"name": "Exa Web Search",
"description": "Exa MCP is a powerful web search and web crawling MCP. It lets you do real-time web searches, extract content from any URL, and even run deep research for detailed reports.",
"icon": "exa",
"type": "Remote",
"category": "Search",
"provider": "Exa"
},
{
"id": "factory-rca",
"name": "Factory RCA MCP",
"description": "Toolset for manufacturing root-cause analysis, anomaly detection, and telemetry-driven recommendations.",
"icon": "factory",
"type": "Remote",
"category": "Manufacturing",
"provider": "Factory"
},
{
"id": "github",
"name": "GitHub",
"description": "Access GitHub repositories, issues, and pull requests through secure API integration. If you need the GitHub MCP server to access your private repo, make sure you have installed the GitHub app.",
"icon": "github",
"type": "Remote",
"category": "Development",
"provider": "GitHub"
},
{
"id": "huggingface",
"name": "Hugging Face MCP Server",
"description": "Search through millions of Hugging Face models, datasets, applications and research papers, and use the Spaces applications you've selected.",
"icon": "huggingface",
"type": "Remote",
"category": "AI/ML",
"provider": "Hugging Face"
},
{
"id": "infobip-rcs",
"name": "Infobip RCS MCP server",
"description": "Infobip RCS MCP server enables seamless integration with our communication platform that allows you to reach your customers globally through RCS.",
"icon": "infobip",
"type": "Remote",
"category": "Communication",
"provider": "Infobip"
},
{
"id": "infobip-sms",
"name": "Infobip SMS MCP server",
"description": "The Infobip SMS MCP server enables agentic and developer workflows to send and manage SMS messages through Infobip's platform.",
"icon": "infobip",
"type": "Remote",
"category": "Communication",
"provider": "Infobip"
},
{
"id": "infobip-whatsapp",
"name": "Infobip WhatsApp MCP server",
"description": "Infobip WhatsApp MCP server enables seamless integration with our communication platform that allows you to reach your customers globally through WhatsApp.",
"icon": "infobip",
"type": "Remote",
"category": "Communication",
"provider": "Infobip"
},
{
"id": "intercom",
"name": "Intercom MCP Server",
"description": "Secure, read-only access to Intercom conversations and contacts for MCP-compatible AI tools.",
"icon": "intercom",
"type": "Remote",
"category": "Customer Support",
"provider": "Intercom"
},
{
"id": "marketnode",
"name": "Marketnode MCP Server",
"description": "AI-powered document data extraction, workflow automation, transaction management and tokenization for financial institutions and enterprises.",
"icon": "marketnode",
"type": "Remote",
"category": "Finance",
"provider": "Marketnode"
},
{
"id": "foundry",
"name": "Foundry MCP Server (preview)",
"description": "Foundry MCP Server (preview) offers instant access to model exploration, deployment of models and agents, and performance evaluation. This fully cloud-native MCP server is integrated with Visual Studio Code and Foundry agents, and secured by Microsoft Entra ID, RBAC, and tenant-level conditional access with Azure Policy for enterprise control.",
"icon": "foundry",
"type": "Remote",
"category": "AI/ML",
"provider": "Microsoft"
},
{
"id": "microsoft-enterprise",
"name": "Microsoft MCP Server for Enterprise",
"description": "Official Microsoft MCP Server to query Microsoft Entra data using natural language.",
"icon": "microsoft",
"type": "Remote",
"category": "Enterprise",
"provider": "Microsoft"
},
{
"id": "mihcm",
"name": "MiHCM MCP Server",
"description": "Provides secure access to employee and leave management data from the MiHCM HR platform through standardized MCP server.",
"icon": "mihcm",
"type": "Remote",
"category": "HR",
"provider": "MiHCM"
},
{
"id": "morningstar",
"name": "Morningstar MCP Server",
"description": "Access Morningstar data, research, and capabilities through specialized MCP tools for global securities.",
"icon": "morningstar",
"type": "Remote",
"category": "Finance",
"provider": "Morningstar"
},
{
"id": "microsoft-sentinel",
"name": "Microsoft Sentinel Data Exploration",
"description": "The data exploration tool collection in the Microsoft Sentinel MCP server lets you search for relevant tables and retrieve data from Microsoft Sentinel's data lake using natural language.",
"icon": "microsoft-sentinel",
"type": "Remote",
"category": "Security",
"provider": "Microsoft"
},
{
"id": "microsoft-learn",
"name": "Microsoft Learn",
"description": "AI assistant with real-time access to official Microsoft documentation.",
"icon": "microsoft-learn",
"type": "Remote",
"category": "Documentation",
"provider": "Microsoft"
},
{
"id": "neon",
"name": "Neon",
"description": "Manage and query Neon Postgres databases with natural language.",
"icon": "neon",
"type": "Remote",
"category": "Database",
"provider": "Neon"
},
{
"id": "netlify",
"name": "Netlify",
"description": "Deploy, secure, and manage websites with Netlify.",
"icon": "netlify",
"type": "Remote",
"category": "Deployment",
"provider": "Netlify"
},
{
"id": "pipedream",
"name": "Pipedream",
"description": "Securely connect to 10,000+ tools from 3,000+ APIs with Pipedream MCP.",
"icon": "pipedream",
"type": "Remote",
"category": "Integration",
"provider": "Pipedream"
},
{
"id": "postman",
"name": "Postman",
"description": "Postman's remote MCP server connects AI agents, assistants, and chatbots directly to your APIs on Postman.",
"icon": "postman",
"type": "Remote",
"category": "API",
"provider": "Postman"
},
{
"id": "sophos-intelix",
"name": "Sophos Intelix MCP Server",
"description": "Sophos Intelix delivers threat intelligence into analyst workflows, enabling agents to access file, URL, and IP reputation and threat analysis.",
"icon": "sophos",
"type": "Remote",
"category": "Security",
"provider": "Sophos"
},
{
"id": "stripe",
"name": "Stripe",
"description": "Payment processing and financial infrastructure tools.",
"icon": "stripe",
"type": "Remote",
"category": "Payments",
"provider": "Stripe"
},
{
"id": "supabase",
"name": "Supabase",
"description": "Connect your Supabase projects to AI agents: design tables and migrations; create database branches; build custom APIs with Edge Functions; retrieve logs and more.",
"icon": "supabase",
"type": "Remote",
"category": "Database",
"provider": "Supabase"
},
{
"id": "tavily",
"name": "Tavily MCP",
"description": "Real-time web search, extraction, crawling and mapping tools for agentic workflows with source citations.",
"icon": "tavily",
"type": "Remote",
"category": "Search",
"provider": "Tavily"
},
{
"id": "tomtom",
"name": "TomTom Maps",
"description": "Give your application real-time geospatial context from TomTom — including maps, routing, search, geocoding and traffic.",
"icon": "tomtom",
"type": "Remote",
"category": "Maps",
"provider": "TomTom"
},
{
"id": "wix",
"name": "Wix MCP",
"description": "Unified access to Wix's development ecosystem for documentation, implementation, and site management.",
"icon": "wix",
"type": "Remote",
"category": "Web Development",
"provider": "Wix"
},
{
"id": "10to8",
"name": "10to8 Appointment Scheduling",
"description": "10to8 is a powerful appointment management, communications & online booking system.",
"icon": "10to8",
"type": "Custom",
"category": "Scheduling",
"provider": "10to8"
},
{
"id": "1docstop",
"name": "1DocStop",
"description": "The best document management system for your web & mobile apps. Store, Manage, and Access all your documents whenever and wherever you are.",
"icon": "1docstop",
"type": "Custom",
"category": "Document Management",
"provider": "1DocStop"
},
{
"id": "1me-corporate",
"name": "1Me Corporate",
"description": "1Me is the easiest and fastest way to share your contact information. With 1Me, you can have an unlimited number of contact cards.",
"icon": "1me",
"type": "Custom",
"category": "Contact Management",
"provider": "1Me"
},
{
"id": "1pt",
"name": "1pt (Independent Publisher)",
"description": "1pt is a URL shortening service and hosts over 15,000+ redirects with 200,000+ visits.",
"icon": "1pt",
"type": "Custom",
"category": "URL Shortener",
"provider": "1pt"
}
],
"categories": [
"Database",
"Analytics",
"Search",
"Vector Database",
"Deployment",
"Data Catalog",
"Productivity",
"AI/ML",
"Storage",
"DevOps",
"Process Mining",
"Development",
"Communication",
"Customer Support",
"Finance",
"Enterprise",
"HR",
"Security",
"Documentation",
"Integration",
"API",
"Payments",
"Maps",
"Web Development",
"Scheduling",
"Document Management",
"Contact Management",
"URL Shortener",
"Manufacturing"
],
"types": [
{
"id": "Local",
"name": "MCP: Local",
"description": "Runs locally on your machine"
},
{
"id": "Remote",
"name": "MCP: Remote",
"description": "Hosted remote MCP server"
},
{
"id": "Custom",
"name": "Custom",
"description": "Custom integration"
}
]
}

235
Cargo.toml Normal file
View file

@ -0,0 +1,235 @@
[package]
name = "botserver"
version = "6.2.0"
edition = "2021"
resolver = "2"
[dependencies.botlib]
workspace = true
features = ["database", "i18n"]
[features]
# ===== DEFAULT =====
default = ["chat", "automation", "drive", "tasks", "cache", "directory", "llm", "crawler", "browser", "terminal", "editor", "mail", "whatsapp"]
browser = ["automation", "drive", "cache"]
terminal = ["automation", "drive", "cache"]
# ===== CORE INFRASTRUCTURE (Can be used standalone) =====
scripting = ["dep:rhai"]
automation = ["scripting", "dep:cron"]
drive = ["dep:aws-config", "dep:aws-sdk-s3", "dep:aws-smithy-async", "dep:pdf-extract", "dep:notify"]
cache = ["dep:redis"]
directory = []
crawler = ["drive", "cache"]
# ===== APPS (Each includes what it needs from core) =====
# Communication
chat = ["automation", "drive", "cache"]
people = ["automation", "drive", "cache"]
mail = ["automation", "drive", "cache", "dep:lettre", "dep:mailparse", "dep:imap"]
meet = ["automation", "drive", "cache"]
social = ["automation", "drive", "cache"]
# Productivity
calendar = ["automation", "drive", "cache"]
tasks = ["automation", "drive", "cache", "dep:cron"]
project = ["automation", "drive", "cache", "quick-xml"]
goals = ["automation", "drive", "cache"]
workspaces = ["automation", "drive", "cache"]
tickets = ["automation", "drive", "cache"]
billing = ["automation", "drive", "cache"]
# Documents
docs = ["automation", "drive", "cache", "docx-rs", "ooxmlsdk"]
sheet = ["automation", "drive", "cache", "calamine", "dep:rust_xlsxwriter", "dep:umya-spreadsheet"]
slides = ["automation", "drive", "cache", "ooxmlsdk"]
paper = ["automation", "drive", "cache"]
# Media
video = ["automation", "drive", "cache"]
player = ["automation", "drive", "cache"]
canvas = ["automation", "drive", "cache"]
# Learning
learn = ["automation", "drive", "cache", "crawler"]
research = ["automation", "drive", "cache", "llm", "vectordb"]
sources = ["automation", "drive", "cache"]
# Analytics
analytics = ["automation", "drive", "cache"]
dashboards = ["automation", "drive", "cache"]
monitoring = ["automation", "drive", "cache", "dep:sysinfo"]
# Development
designer = ["automation", "drive", "cache"]
editor = ["automation", "drive", "cache"]
# Admin
attendant = ["automation", "drive", "cache"]
security = ["automation", "drive", "cache"]
settings = ["automation", "drive", "cache"]
whatsapp = ["automation", "drive", "cache"]
telegram = ["automation", "drive", "cache"]
instagram = ["automation", "drive", "cache"]
msteams = ["automation", "drive", "cache"]
# Core Tech
llm = ["automation", "drive", "cache"]
vectordb = ["automation", "drive", "cache", "dep:qdrant-client"]
nvidia = ["automation", "drive", "cache"]
compliance = ["automation", "drive", "cache", "dep:csv"]
timeseries = ["automation", "drive", "cache"]
weba = ["automation", "drive", "cache"]
progress-bars = ["automation", "drive", "cache", "dep:indicatif"]
grpc = ["automation", "drive", "cache"]
jemalloc = ["automation", "drive", "cache", "dep:tikv-jemallocator", "dep:tikv-jemalloc-ctl"]
console = ["automation", "drive", "cache", "dep:crossterm", "dep:ratatui"]
# ===== BUNDLES (Optional - for convenience) =====
minimal = ["chat"]
lightweight = ["chat", "tasks", "people"]
full = ["chat", "people", "mail", "tasks", "calendar", "drive", "docs", "llm", "cache", "compliance"]
embed-ui = ["dep:rust-embed"]
[dependencies]
diesel_migrations = { workspace = true }
bigdecimal = { workspace = true }
# === CORE RUNTIME ===
aes-gcm = { workspace = true }
anyhow = { workspace = true }
argon2 = { workspace = true }
async-trait = { workspace = true }
axum = { workspace = true }
axum-server = { workspace = true }
base64 = { workspace = true }
chrono = { workspace = true, features = ["clock", "std"] }
color-eyre = { workspace = true }
diesel = { workspace = true, features = ["postgres", "uuid", "chrono", "serde_json", "r2d2", "numeric", "32-column-tables"] }
dirs = { workspace = true }
dotenvy = { workspace = true }
futures = { workspace = true }
futures-util = { workspace = true }
git2 = "0.19"
hex = { workspace = true }
hmac = { workspace = true }
log = { workspace = true }
num-format = { workspace = true }
once_cell = { workspace = true }
rand = { workspace = true }
regex = { workspace = true }
reqwest = { workspace = true, features = ["rustls-tls", "multipart", "stream", "json"] }
serde = { workspace = true, features = ["derive", "std"] }
serde_json = { workspace = true }
toml = { workspace = true }
sha2 = { workspace = true }
sha1 = { workspace = true }
tokio = { workspace = true, features = ["full", "process"] }
tower-http = { workspace = true, features = ["cors", "fs", "trace"] }
tracing = { workspace = true }
url = { workspace = true }
urlencoding = { workspace = true }
uuid = { workspace = true, features = ["v4", "v5"] }
# === TLS/SECURITY DEPENDENCIES ===
rustls = { workspace = true, features = ["ring", "std", "tls12"] }
tokio-rustls = { workspace = true }
rcgen = { workspace = true, features = ["crypto", "ring", "pem"] }
x509-parser = { workspace = true }
ring = { workspace = true }
ciborium = { workspace = true }
time = { workspace = true, features = ["formatting"] }
jsonwebtoken = { workspace = true }
# === APP-SPECIFIC DEPENDENCIES ===
# mail Integration (mail feature)
imap = { workspace = true, optional = true }
lettre = { workspace = true, optional = true }
mailparse = { workspace = true, optional = true }
# Vector Database (vectordb feature)
qdrant-client = { workspace = true, optional = true }
# Document Processing
docx-rs = { workspace = true, optional = true }
ooxmlsdk = { workspace = true, optional = true, features = ["parts"] }
calamine = { workspace = true, optional = true }
rust_xlsxwriter = { workspace = true, optional = true }
umya-spreadsheet = { workspace = true, optional = true }
# File Storage & Drive (drive feature)
aws-config = { workspace = true, features = ["behavior-version-latest", "rt-tokio", "rustls"], optional = true }
aws-sdk-s3 = { workspace = true, features = ["rt-tokio", "rustls"], optional = true }
aws-smithy-async = { workspace = true, optional = true }
pdf-extract = { workspace = true, optional = true }
quick-xml = { workspace = true, optional = true }
flate2 = { workspace = true }
zip = { workspace = true }
tar = { workspace = true }
# Task Management (tasks feature)
cron = { workspace = true, optional = true }
# Automation & Scripting (automation feature)
rhai = { workspace = true, optional = true }
# Compliance & Reporting (compliance feature)
csv = { workspace = true, optional = true }
# Console/TUI (console feature)
crossterm = { workspace = true, optional = true }
ratatui = { workspace = true, optional = true }
# QR Code Generation
png = { workspace = true }
qrcode = { workspace = true }
# Error handling
thiserror = { workspace = true }
# Caching/Sessions (cache feature)
redis = { workspace = true, features = ["tokio-comp"], optional = true }
# System Monitoring (monitoring feature)
sysinfo = { workspace = true, optional = true }
# UI Enhancement (progress-bars feature)
indicatif = { workspace = true, optional = true }
smartstring = { workspace = true }
# Memory allocator (jemalloc feature)
tikv-jemallocator = { workspace = true, optional = true }
tikv-jemalloc-ctl = { workspace = true, optional = true }
scopeguard = { workspace = true }
# Vault secrets management
vaultrs = { workspace = true }
# Calendar standards (RFC 5545)
icalendar = { workspace = true }
# Rate limiting
governor = { workspace = true }
# RSS feed parsing
rss = { workspace = true }
# HTML parsing/web scraping
scraper = { workspace = true }
walkdir = { workspace = true }
# File system monitoring (for local .gbai monitoring)
notify = { workspace = true, optional = true }
# Embedded static files
rust-embed = { workspace = true, optional = true }
[dev-dependencies]
mockito = { workspace = true }
tempfile = { workspace = true }
bigdecimal = { workspace = true }
[lints]
workspace = true

682
LICENSE Normal file
View file

@ -0,0 +1,682 @@
General Bots is licensed under a dual license. To check which license
edition of General bots you have installed, please ask info@pragmatismo.com.br
informing your Customer ID.
If you modify this Program, or any covered work, by combining it
with Packages entirely written by you, the licensors of this Program
grant you additional permission to convey those packages (.gbapp,
.gbkb, .gbtheme, .gbot) under terms of your choice, provided that
those terms do not place additional restrictions on the Program.
You are free to create either open-source as well private
packages to the General Bots platform. AGPL enforces contributions
to the server core so everyone wins.
The following terms are important regarding the General Bots
Community Edition, licensed under AGPL V3:
For General Bots community edition which stands under AGPL license the
following terms are significantly:
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

594
LOGGING_PLAN.md Normal file
View file

@ -0,0 +1,594 @@
# BotServer Cinema Viewer Logging Plan
## 🎬 The Cinema Viewer Philosophy
Think of your logs as a movie with different viewing modes:
- **INFO** = **The Movie** - Watch the main story unfold (production-ready)
- **DEBUG** = **Director's Commentary** - Behind-the-scenes details (troubleshooting)
- **TRACE** = **Raw Footage** - Every single take and detail (deep debugging)
- **WARN** = **Plot Holes** - Things that shouldn't happen but are recoverable
- **ERROR** = **Scene Failures** - Critical issues that break the narrative
---
## 📊 Log Level Definitions
### ERROR - Critical Failures
**When to use:** System cannot proceed, requires immediate attention
```rust
// ✅ GOOD - Actionable, clear context
error!("Database connection failed - retrying in 5s: {}", e);
error!("Authentication failed for user {}: invalid credentials", user_id);
error!("Stage 2 BUILD failed: {}", e);
// ❌ BAD - Vague, no context
error!("Error!");
error!("Failed");
error!("Something went wrong: {}", e);
```
### WARN - Recoverable Issues
**When to use:** Unexpected state but system can continue
```rust
// ✅ GOOD - Explains the issue and impact
warn!("Failed to create data directory: {}. Using fallback.", e);
warn!("LLM server not ready - deferring embedding generation");
warn!("Rate limit approaching for API key {}", key_id);
// ❌ BAD - Not actionable
warn!("Warning");
warn!("Something happened");
```
### INFO - The Main Story
**When to use:** Key events, state changes, business milestones
```rust
// ✅ GOOD - Tells a story, shows progress
info!("Pipeline starting - task: {}, intent: {}", task_id, intent);
info!("Stage 1 PLAN complete - {} nodes planned", node_count);
info!("User {} logged in from {}", user_id, ip_address);
info!("Server started on port {}", port);
// ❌ BAD - Too verbose, implementation details
info!("Entering function process_request");
info!("Variable x = {}", x);
info!("Loop iteration {}", i);
```
### DEBUG - Behind the Scenes
**When to use:** Troubleshooting information, decision points, state inspections
```rust
// ✅ GOOD - Helps diagnose issues
debug!("Request payload: {:?}", payload);
debug!("Using cache key: {}", cache_key);
debug!("Retry attempt {} of {}", attempt, max_retries);
debug!("Selected LLM model: {} for task type: {}", model, task_type);
// ❌ BAD - Too trivial
debug!("Variable assigned");
debug!("Function called");
```
### TRACE - Raw Footage
**When to use:** Step-by-step execution, loop iterations, detailed flow
```rust
// ✅ GOOD - Detailed execution path
trace!("Starting monitoring loop");
trace!("Processing file: {:?}", path);
trace!("Checking bot directory: {}", dir);
trace!("WebSocket message received: {} bytes", len);
// ❌ BAD - Noise without value
trace!("Line 100");
trace!("Got here");
trace!("...");
```
---
## 🎭 Logging Patterns by Module Type
### 1. Orchestration & Pipeline Modules
**Examples:** `auto_task/orchestrator.rs`, `auto_task/agent_executor.rs`
**INFO Level:** Show the story arc
```rust
info!("Pipeline starting - task: {}, intent: {}", task_id, intent);
info!("Stage 1 PLAN starting - Agent #1 analyzing request");
info!("Stage 1 PLAN complete - {} nodes planned", node_count);
info!("Stage 2 BUILD complete - {} resources, url: {}", count, url);
info!("Pipeline complete - task: {}, nodes: {}, resources: {}", task_id, nodes, resources);
```
**DEBUG Level:** Show decision points
```rust
debug!("Classified intent as: {:?}", classification);
debug!("Selected app template: {}", template_name);
debug!("Skipping stage 3 - no resources to review");
```
**TRACE Level:** Show execution flow
```rust
trace!("Broadcasting thought to UI: {}", thought);
trace!("Updating agent {} status: {} -> {}", id, old, new);
trace!("Sub-task generated: {}", task_name);
```
### 2. File Monitoring & Compilation
**Examples:** `drive/local_file_monitor.rs`, `drive/drive_monitor/mod.rs`
**INFO Level:** Key file operations
```rust
info!("Local file monitor started - watching /opt/gbo/data/*.gbai");
info!("Compiling bot: {} ({} files)", bot_name, file_count);
info!("Bot {} compiled successfully - {} tools generated", bot_name, tool_count);
```
**DEBUG Level:** File processing details
```rust
debug!("Detected change in: {:?}", path);
debug!("Recompiling {} - modification detected", bot_name);
debug!("Skipping {} - no changes detected", bot_name);
```
**TRACE Level:** File system operations
```rust
trace!("Scanning directory: {:?}", dir);
trace!("File modified: {:?} at {:?}", path, time);
trace!("Watching directory: {:?}", path);
```
### 3. Security & Authentication
**Examples:** `security/jwt.rs`, `security/api_keys.rs`, `security/sql_guard.rs`
**INFO Level:** Security events (always log these!)
```rust
info!("User {} logged in from {}", user_id, ip);
info!("API key {} created for user {}", key_id, user_id);
info!("Failed login attempt for user {} from {}", username, ip);
info!("Rate limit exceeded for IP: {}", ip);
```
**DEBUG Level:** Security checks
```rust
debug!("Validating JWT for user {}", user_id);
debug!("Checking API key permissions: {:?}", permissions);
debug!("SQL query sanitized: {}", safe_query);
```
**TRACE Level:** Security internals
```rust
trace!("Token expiry check: {} seconds remaining", remaining);
trace!("Permission check: {} -> {}", resource, allowed);
trace!("Hashing password with cost factor: {}", cost);
```
### 4. API Handlers
**Examples:** HTTP endpoint handlers in `core/`, `drive/`, etc.
**INFO Level:** Request lifecycle
```rust
info!("Request started: {} {} from {}", method, path, ip);
info!("Request completed: {} {} -> {} ({}ms)", method, path, status, duration);
info!("User {} created resource: {}", user_id, resource_id);
```
**DEBUG Level:** Request details
```rust
debug!("Request headers: {:?}", headers);
debug!("Request body: {:?}", body);
debug!("Response payload: {} bytes", size);
```
**TRACE Level:** Request processing
```rust
trace!("Parsing JSON body");
trace!("Validating request parameters");
trace!("Serializing response");
```
### 5. Database Operations
**Examples:** `core/shared/models/`, Diesel queries
**INFO Level:** Database lifecycle
```rust
info!("Database connection pool initialized ({} connections)", pool_size);
info!("Migration completed - {} tables updated", count);
info!("Database backup created: {}", backup_path);
```
**DEBUG Level:** Query information
```rust
debug!("Executing query: {}", query);
debug!("Query returned {} rows in {}ms", count, duration);
debug!("Cache miss for key: {}", key);
```
**TRACE Level:** Query details
```rust
trace!("Preparing statement: {}", sql);
trace!("Binding parameter {}: {:?}", index, value);
trace!("Fetching next row");
```
### 6. LLM & AI Operations
**Examples:** `llm/`, `core/kb/`
**INFO Level:** LLM operations
```rust
info!("LLM request started - model: {}, tokens: {}", model, estimated_tokens);
info!("LLM response received - {} tokens, {}ms", tokens, duration);
info!("Embedding generated - {} dimensions", dimensions);
info!("Knowledge base indexed - {} documents", doc_count);
```
**DEBUG Level:** LLM details
```rust
debug!("LLM prompt: {}", prompt_preview);
debug!("Using temperature: {}, max_tokens: {}", temp, max);
debug!("Selected model variant: {}", variant);
```
**TRACE Level:** LLM internals
```rust
trace!("Sending request to LLM API: {}", url);
trace!("Streaming token: {}", token);
trace!("Parsing LLM response chunk");
```
### 7. Startup & Initialization
**Examples:** `main.rs`, `main_module/bootstrap.rs`
**INFO Level:** Startup milestones
```rust
info!("Server starting on port {}", port);
info!("Database initialized - PostgreSQL connected");
info!("Cache initialized - Valkey connected");
info!("Secrets loaded from Vault");
info!("BotServer ready - {} bots loaded", bot_count);
```
**DEBUG Level:** Configuration details
```rust
debug!("Using config: {:?}", config);
debug!("Environment: {}", env);
debug!("Feature flags: {:?}", features);
```
**TRACE Level:** Initialization steps
```rust
trace!("Loading .env file");
trace!("Setting up signal handlers");
trace!("Initializing thread registry");
```
---
## 🎯 The Cinema Viewer Experience
### Level 1: Watching the Movie (INFO)
```bash
RUST_LOG=botserver=info
```
**What you see:**
```
INFO botserver: Server starting on port 8080
INFO botserver: Database initialized - PostgreSQL connected
INFO botserver: User alice@example.com logged in from 192.168.1.100
INFO botserver::auto_task::orchestrator: Pipeline starting - task: abc123, intent: Create CRM
INFO botserver::auto_task::orchestrator: Stage 1 PLAN complete - 5 nodes planned
INFO botserver::auto_task::orchestrator: Stage 2 BUILD complete - 12 resources, url: /apps/crm
INFO botserver::auto_task::orchestrator: Pipeline complete - task: abc123, nodes: 5, resources: 12
INFO botserver: User alice@example.com logged out
```
**Perfect for:** Production monitoring, understanding system flow
### Level 2: Director's Commentary (DEBUG)
```bash
RUST_LOG=botserver=debug
```
**What you see:** Everything from INFO plus:
```
DEBUG botserver::auto_task::orchestrator: Classified intent as: AppGeneration
DEBUG botserver::auto_task::orchestrator: Selected app template: crm
DEBUG botserver::security::jwt: Validating JWT for user alice@example.com
DEBUG botserver::drive::local_file_monitor: Detected change in: /opt/gbo/data/crm.gbai
DEBUG botserver::llm: Using temperature: 0.7, max_tokens: 2000
```
**Perfect for:** Troubleshooting issues, understanding decisions
### Level 3: Raw Footage (TRACE)
```bash
RUST_LOG=botserver=trace
```
**What you see:** Everything from DEBUG plus:
```
TRACE botserver::drive::local_file_monitor: Scanning directory: /opt/gbo/data
TRACE botserver::auto_task::orchestrator: Broadcasting thought to UI: Analyzing...
TRACE botserver::llm: Streaming token: Create
TRACE botserver::llm: Streaming token: a
TRACE botserver::llm: Streaming token: CRM
TRACE botserver::core::db: Preparing statement: SELECT * FROM bots
```
**Perfect for:** Deep debugging, performance analysis, finding bugs
---
## ✨ Best Practices
### 1. Tell a Story
```rust
// ✅ GOOD - Shows the narrative
info!("Pipeline starting - task: {}", task_id);
info!("Stage 1 PLAN complete - {} nodes planned", nodes);
info!("Stage 2 BUILD complete - {} resources", resources);
info!("Pipeline complete - app deployed at {}", url);
// ❌ BAD - Just data points
info!("Task started");
info!("Nodes: {}", nodes);
info!("Resources: {}", resources);
info!("Done");
```
### 2. Use Structured Data
```rust
// ✅ GOOD - Easy to parse and filter
info!("User {} logged in from {}", user_id, ip);
info!("Request completed: {} {} -> {} ({}ms)", method, path, status, duration);
// ❌ BAD - Hard to parse
info!("User login happened");
info!("Request finished successfully");
```
### 3. Include Context
```rust
// ✅ GOOD - Provides context
error!("Database connection failed for bot {}: {}", bot_id, e);
warn!("Rate limit approaching for user {}: {}/{} requests", user_id, count, limit);
// ❌ BAD - No context
error!("Connection failed: {}", e);
warn!("Rate limit warning");
```
### 4. Use Appropriate Levels
```rust
// ✅ GOOD - Right level for right information
info!("Server started on port {}", port); // Key event
debug!("Using config: {:?}", config); // Troubleshooting
trace!("Listening on socket {:?}", socket); // Deep detail
// ❌ BAD - Wrong levels
trace!("Server started"); // Too important for trace
info!("Loop iteration {}", i); // Too verbose for info
error!("Variable is null"); // Not an error
```
### 5. Avoid Noise
```rust
// ✅ GOOD - Meaningful information
debug!("Retry attempt {} of {} for API call", attempt, max);
// ❌ BAD - Just noise
debug!("Entering function");
debug!("Exiting function");
debug!("Variable assigned");
```
### 6. Log State Changes
```rust
// ✅ GOOD - Shows what changed
info!("User {} role changed: {} -> {}", user_id, old_role, new_role);
info!("Bot {} status: {} -> {}", bot_id, old_status, new_status);
// ❌ BAD - No before/after
info!("User role updated");
info!("Bot status changed");
```
### 7. Include Timings for Operations
```rust
// ✅ GOOD - Performance visibility
info!("Database migration completed in {}ms", duration);
info!("LLM response received - {} tokens, {}ms", tokens, duration);
debug!("Query executed in {}ms", duration);
// ❌ BAD - No performance data
info!("Migration completed");
info!("LLM response received");
```
---
## 🔧 Implementation Guide
### Step 1: Audit Current Logging
```bash
# Find all logging statements
find botserver/src -name "*.rs" -exec grep -n "info!\|debug!\|trace!\|warn!\|error!" {} +
# Count by level
grep -r "info!" botserver/src | wc -l
grep -r "debug!" botserver/src | wc -l
grep -r "trace!" botserver/src | wc -l
```
### Step 2: Categorize by Module
Create a spreadsheet or document listing:
- Module name
- Current log levels used
- Purpose of the module
- What story should it tell
### Step 3: Refactor Module by Module
Start with critical path modules:
1. **auto_task/orchestrator.rs** - Already done! ✅
2. **drive/local_file_monitor.rs** - File operations
3. **security/jwt.rs** - Authentication events
4. **main.rs** - Startup sequence
5. **core/bot/** - Bot lifecycle
### Step 4: Test Different Verbosity Levels
```bash
# Test INFO level (production)
RUST_LOG=botserver=info cargo run
# Test DEBUG level (troubleshooting)
RUST_LOG=botserver=debug cargo run
# Test TRACE level (development)
RUST_LOG=botserver=trace cargo run
```
### Step 5: Document Module-Specific Patterns
For each module, document:
- What story does it tell at INFO level?
- What troubleshooting info at DEBUG level?
- What raw details at TRACE level?
---
## 📋 Quick Reference Card
### Log Level Decision Tree
```
Is this a failure that stops execution?
└─ YES → ERROR
└─ NO → Is this unexpected but recoverable?
└─ YES → WARN
└─ NO → Is this a key business event?
└─ YES → INFO
└─ NO → Is this useful for troubleshooting?
└─ YES → DEBUG
└─ NO → Is this step-by-step execution detail?
└─ YES → TRACE
└─ NO → Don't log it!
```
### Module-Specific Cheat Sheet
| Module Type | INFO | DEBUG | TRACE |
|-------------|------|-------|-------|
| **Orchestration** | Stage start/complete, pipeline milestones | Decision points, classifications | UI broadcasts, state changes |
| **File Monitoring** | Monitor start, bot compiled | Changes detected, recompiles | File scans, timestamps |
| **Security** | Logins, key events, failures | Validations, permission checks | Token details, hash operations |
| **API Handlers** | Request start/end, resource changes | Headers, payloads | JSON parsing, serialization |
| **Database** | Connections, migrations | Queries, row counts | Statement prep, row fetching |
| **LLM** | Requests, responses, indexing | Prompts, parameters | Token streaming, chunking |
| **Startup** | Service ready, milestones | Config, environment | Init steps, signal handlers |
---
## 🎬 Example: Complete Pipeline Logging
Here's how a complete auto-task pipeline looks at different levels:
### INFO Level (The Movie)
```
INFO Pipeline starting - task: task-123, intent: Create a CRM system
INFO Stage 1 PLAN starting - Agent #1 analyzing request
INFO Stage 1 PLAN complete - 5 nodes planned
INFO Stage 2 BUILD starting - Agent #2 generating code
INFO Stage 2 BUILD complete - 12 resources, url: /apps/crm-system
INFO Stage 3 REVIEW starting - Agent #3 checking code quality
INFO Stage 3 REVIEW complete - all checks passed
INFO Stage 4 DEPLOY starting - Agent #4 deploying to /apps/crm-system
INFO Stage 4 DEPLOY complete - app live at /apps/crm-system
INFO Stage 5 MONITOR starting - Agent #1 setting up monitoring
INFO Stage 5 MONITOR complete - monitoring active
INFO Pipeline complete - task: task-123, nodes: 5, resources: 12, url: /apps/crm-system
```
### DEBUG Level (Director's Commentary)
```
INFO Pipeline starting - task: task-123, intent: Create a CRM system
DEBUG Classified intent as: AppGeneration
DEBUG Selected app template: crm_standard
INFO Stage 1 PLAN starting - Agent #1 analyzing request
DEBUG Generated 5 sub-tasks from intent
INFO Stage 1 PLAN complete - 5 nodes planned
INFO Stage 2 BUILD starting - Agent #2 generating code
DEBUG Using database schema: contacts, deals, activities
DEBUG Generated 3 tables, 8 pages, 1 tool
INFO Stage 2 BUILD complete - 12 resources, url: /apps/crm-system
...
```
### TRACE Level (Raw Footage)
```
INFO Pipeline starting - task: task-123, intent: Create a CRM system
DEBUG Classified intent as: AppGeneration
TRACE Extracting entities from: "Create a CRM system"
TRACE Found entity: CRM
TRACE Found entity: system
DEBUG Selected app template: crm_standard
INFO Stage 1 PLAN starting - Agent #1 analyzing request
TRACE Broadcasting thought to UI: Analyzing request...
TRACE Deriving plan sub-tasks
TRACE Sub-task 1: Create database schema
TRACE Sub-task 2: Generate list page
TRACE Sub-task 3: Generate form pages
TRACE Sub-task 4: Create BASIC tools
TRACE Sub-task 5: Setup navigation
DEBUG Generated 5 sub-tasks from intent
...
```
---
## 🎯 Goals & Metrics
### Success Criteria
1. **INFO logs tell a complete story** - Can understand system flow without DEBUG/TRACE
2. **DEBUG logs enable troubleshooting** - Can diagnose issues with context
3. **TRACE logs show execution details** - Can see step-by-step for deep debugging
4. **No log spam** - Production logs are concise and meaningful
5. **Consistent patterns** - Similar modules log similarly
### Metrics to Track
- Lines of logs per request at INFO level: < 20
- Lines of logs per request at DEBUG level: < 100
- Lines of logs per request at TRACE level: unlimited
- Error logs include context: 100%
- WARN logs explain impact: 100%
---
## 🚀 Next Steps
1. **Audit** current logging in all 341 files
2. **Prioritize** modules by criticality
3. **Refactor** module by module following this plan
4. **Test** at each log level
5. **Document** module-specific patterns
6. **Train** team on logging standards
7. **Monitor** log volume and usefulness
8. **Iterate** based on feedback
---
## 📚 References
- [Rust log crate documentation](https://docs.rs/log/)
- [env_logger documentation](https://docs.rs/env_logger/)
- [Structured logging best practices](https://www.honeycomb.io/blog/structured-logging/)
- [The Log: What every software engineer should know](https://blog.codinghorror.com/the-log-everything-manifesto/)
---
**Remember:** Good logging is like good cinematography - it should be invisible when done right, but tell a compelling story when you pay attention to it. 🎬

495
README.md Normal file
View file

@ -0,0 +1,495 @@
# General Bots - Enterprise-Grade LLM Orchestrator
**Version:** 6.2.0
**Purpose:** Main API server for General Bots (Axum + Diesel + Rhai BASIC)
---
![General Bot Logo](https://github.com/GeneralBots/botserver/blob/main/logo.png?raw=true)
## Overview
General Bots is a **self-hosted AI automation platform** and strongly-typed LLM conversational platform focused on convention over configuration and code-less approaches. It serves as the core API server handling LLM orchestration, business logic, database operations, and multi-channel communication.
For comprehensive documentation, see **[docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)** or the **[BotBook](../botbook)** for detailed guides, API references, and tutorials.
---
## 🚀 Quick Start
### Prerequisites
- **Rust** (1.75+) - [Install from rustup.rs](https://rustup.rs/)
- **Git** - [Download from git-scm.com](https://git-scm.com/downloads)
- **Mold** - `sudo apt-get install mold`
### Installation
```bash
git clone https://github.com/GeneralBots/botserver
cd botserver
cargo install sccache
sudo apt-get install mold # or build from source
cargo run
```
On first run, botserver automatically:
- Installs required components (PostgreSQL, S3 storage, Redis cache, LLM)
- Sets up database with migrations
- Downloads AI models
- Starts HTTP server at `http://localhost:9000`
### Command-Line Options
```bash
cargo run # Default: console UI + web server
cargo run -- --noconsole # Background service mode
cargo run -- --desktop # Desktop application (Tauri)
cargo run -- --tenant <name> # Specify tenant
cargo run -- --container # LXC container mode
```
---
## ✨ Key Features
### Multi-Vendor LLM API
Unified interface for OpenAI, Groq, Claude, Anthropic, and local models.
### MCP + LLM Tools Generation
Instant tool creation from code and functions - no complex configurations.
### Semantic Caching
Intelligent response caching achieving **70% cost reduction** on LLM calls.
### Web Automation Engine
Browser automation combined with AI intelligence for complex workflows.
### Enterprise Data Connectors
Native integrations with CRM, ERP, databases, and external services.
### Git-like Version Control
Full history with rollback capabilities for all configurations and data.
---
## 🎯 4 Essential Keywords
```basic
USE KB "kb-name" ' Load knowledge base into vector database
CLEAR KB "kb-name" ' Remove KB from session
USE TOOL "tool-name" ' Make tool available to LLM
CLEAR TOOLS ' Remove all tools from session
```
### Example Bot
```basic
' customer-support.bas
USE KB "support-docs"
USE TOOL "create-ticket"
USE TOOL "check-order"
SET CONTEXT "support" AS "You are a helpful customer support agent."
TALK "Welcome! How can I help you today?"
```
---
## 📁 Project Structure
```
src/
├── core/ # Bootstrap, config, routes
├── basic/ # Rhai BASIC interpreter
│ └── keywords/ # BASIC keyword implementations
├── security/ # Security modules
│ ├── command_guard.rs # Safe command execution
│ ├── error_sanitizer.rs # Error message sanitization
│ └── sql_guard.rs # SQL injection prevention
├── shared/ # Shared types, models
├── tasks/ # AutoTask system (2651 lines - NEEDS REFACTORING)
├── auto_task/ # App generator (2981 lines - NEEDS REFACTORING)
├── drive/ # File operations (1522 lines - NEEDS REFACTORING)
├── learn/ # Learning system (2306 lines - NEEDS REFACTORING)
└── attendance/ # LLM assistance (2053 lines - NEEDS REFACTORING)
migrations/ # Database migrations
botserver-stack/ # Stack deployment files
```
---
## ✅ ZERO TOLERANCE POLICY
**EVERY SINGLE WARNING MUST BE FIXED. NO EXCEPTIONS.**
### Absolute Prohibitions
```
❌ NEVER use #![allow()] or #[allow()] in source code
❌ NEVER use .unwrap() - use ? or proper error handling
❌ NEVER use .expect() - use ? or proper error handling
❌ NEVER use panic!() or unreachable!()
❌ NEVER use todo!() or unimplemented!()
❌ NEVER leave unused imports or dead code
❌ NEVER add comments - code must be self-documenting
❌ NEVER use CDN links - all assets must be local
❌ NEVER build SQL queries with format! - use parameterized queries
❌ NEVER pass user input to Command::new() without validation
❌ NEVER log passwords, tokens, API keys, or PII
```
---
## 🔐 Security Requirements
### Error Handling - CRITICAL DEBT
**Current Status**: 955 instances of `unwrap()`/`expect()` found in codebase
**Target**: 0 instances in production code (tests excluded)
```rust
// ❌ WRONG - Found 955 times in codebase
let value = something.unwrap();
let value = something.expect("msg");
// ✅ CORRECT - Required replacements
let value = something?;
let value = something.ok_or_else(|| Error::NotFound)?;
let value = something.unwrap_or_default();
let value = something.unwrap_or_else(|e| {
log::error!("Operation failed: {e}");
default_value
});
```
### Performance Issues - CRITICAL DEBT
**Current Status**: 12,973 excessive `clone()`/`to_string()` calls
**Target**: Minimize allocations, use references where possible
```rust
// ❌ WRONG - Excessive allocations
let name = user.name.clone();
let msg = format!("Hello {}", name.to_string());
// ✅ CORRECT - Minimize allocations
let name = &user.name;
let msg = format!("Hello {name}");
// ✅ CORRECT - Use Cow for conditional ownership
use std::borrow::Cow;
fn process_name(name: Cow<str>) -> String {
match name {
Cow::Borrowed(s) => s.to_uppercase(),
Cow::Owned(s) => s.to_uppercase(),
}
}
```
### SQL Injection Prevention
```rust
// ❌ WRONG
let query = format!("SELECT * FROM {}", table_name);
// ✅ CORRECT - whitelist validation
const ALLOWED_TABLES: &[&str] = &["users", "sessions"];
if !ALLOWED_TABLES.contains(&table_name) {
return Err(Error::InvalidTable);
}
```
### Command Injection Prevention
```rust
// ❌ WRONG
Command::new("tool").arg(user_input).output()?;
// ✅ CORRECT - Use SafeCommand
use crate::security::command_guard::SafeCommand;
SafeCommand::new("allowed_command")?
.arg("safe_arg")?
.execute()
```
### Error Responses - Use ErrorSanitizer
```rust
// ❌ WRONG
Json(json!({ "error": e.to_string() }))
format!("Database error: {}", e)
// ✅ CORRECT
use crate::security::error_sanitizer::log_and_sanitize;
let sanitized = log_and_sanitize(&e, "context", None);
(StatusCode::INTERNAL_SERVER_ERROR, sanitized)
```
---
## ✅ Mandatory Code Patterns
### Format Strings - Inline Variables
```rust
// ❌ WRONG
format!("Hello {}", name)
// ✅ CORRECT
format!("Hello {name}")
```
### Self Usage in Impl Blocks
```rust
// ❌ WRONG
impl MyStruct {
fn new() -> MyStruct { MyStruct { } }
}
// ✅ CORRECT
impl MyStruct {
fn new() -> Self { Self { } }
}
```
### Derive Eq with PartialEq
```rust
// ❌ WRONG
#[derive(PartialEq)]
struct MyStruct { }
// ✅ CORRECT
#[derive(PartialEq, Eq)]
struct MyStruct { }
```
### Option Handling
```rust
// ✅ CORRECT
opt.unwrap_or(default)
opt.unwrap_or_else(|| compute_default())
opt.map_or(default, |x| transform(x))
```
### Chrono DateTime
```rust
// ❌ WRONG
date.with_hour(9).unwrap().with_minute(0).unwrap()
// ✅ CORRECT
date.with_hour(9).and_then(|d| d.with_minute(0)).unwrap_or(date)
```
---
## 📏 File Size Limits - MANDATORY
### Maximum 450 Lines Per File
When a file grows beyond this limit:
1. **Identify logical groups** - Find related functions
2. **Create subdirectory module** - e.g., `handlers/`
3. **Split by responsibility:**
- `types.rs` - Structs, enums, type definitions
- `handlers.rs` - HTTP handlers and routes
- `operations.rs` - Core business logic
- `utils.rs` - Helper functions
- `mod.rs` - Re-exports and configuration
4. **Keep files focused** - Single responsibility
5. **Update mod.rs** - Re-export all public items
**NEVER let a single file exceed 450 lines - split proactively at 350 lines**
### Files Requiring Immediate Refactoring
| File | Lines | Target Split |
|------|-------|--------------|
| `auto_task/app_generator.rs` | 2981 | → 7 files |
| `tasks/mod.rs` | 2651 | → 6 files |
| `learn/mod.rs` | 2306 | → 5 files |
| `attendance/llm_assist.rs` | 2053 | → 5 files |
| `drive/mod.rs` | 1522 | → 4 files |
---
## 🗄️ Database Standards
- **TABLES AND INDEXES ONLY** (no stored procedures, nothing, no views, no triggers, no functions)
- **JSON columns:** use TEXT with `_json` suffix
- **ORM:** Use diesel - no sqlx
- **Migrations:** Located in `botserver/migrations/`
---
## 🎨 Frontend Rules
- **Use HTMX** - minimize JavaScript
- **NO external CDN** - all assets local
- **Server-side rendering** with Askama templates
---
## 📦 Key Dependencies
| Library | Version | Purpose |
|---------|---------|---------|
| axum | 0.7.5 | Web framework |
| diesel | 2.1 | PostgreSQL ORM |
| tokio | 1.41 | Async runtime |
| rhai | git | BASIC scripting |
| reqwest | 0.12 | HTTP client |
| serde | 1.0 | Serialization |
| askama | 0.12 | HTML Templates |
---
## 🚀 CI/CD Workflow
When configuring CI/CD pipelines (e.g., Forgejo Actions):
- **Minimal Checkout**: Clone only the root `gb` and the `botlib` submodule. Do NOT recursively clone everything.
- **BotServer Context**: Replace the empty `botserver` directory with the current set of files being tested.
**Example Step:**
```yaml
- name: Setup Workspace
run: |
# 1. Clone only the root workspace configuration
git clone --depth 1 <your-git-repo-url> workspace
# 2. Setup only the necessary dependencies (botlib)
cd workspace
git submodule update --init --depth 1 botlib
cd ..
# 3. Inject current BotServer code
rm -rf workspace/botserver
mv botserver workspace/botserver
```
---
## 📚 Documentation
### Documentation Structure
```
docs/
├── api/ # API documentation
│ ├── README.md # API overview
│ ├── rest-endpoints.md # HTTP endpoints
│ └── websocket.md # Real-time communication
├── guides/ # How-to guides
│ ├── getting-started.md # Quick start
│ ├── deployment.md # Production setup
│ └── templates.md # Using templates
└── reference/ # Technical reference
├── basic-language.md # BASIC keywords
├── configuration.md # Config options
└── architecture.md # System design
```
### Additional Resources
- **[docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)** - Full online documentation
- **[BotBook](../botbook)** - Local comprehensive guide with tutorials and examples
- **[API Reference](docs/api/README.md)** - REST and WebSocket endpoints
- **[BASIC Language](docs/reference/basic-language.md)** - Dialog scripting reference
---
## 🔗 Related Projects
| Project | Description |
|---------|-------------|
| [botui](https://github.com/GeneralBots/botui) | Pure web UI (HTMX-based) |
| [botapp](https://github.com/GeneralBots/botapp) | Tauri desktop wrapper |
| [botlib](https://github.com/GeneralBots/botlib) | Shared Rust library |
| [botbook](https://github.com/GeneralBots/botbook) | Documentation |
| [bottemplates](https://github.com/GeneralBots/bottemplates) | Templates and examples |
---
## 🛡️ Security
- **AGPL-3.0 License** - True open source with contribution requirements
- **Self-hosted** - Your data stays on your infrastructure
- **Enterprise-grade** - 5+ years of stability
- **No vendor lock-in** - Open protocols and standards
Report security issues to: **security@pragmatismo.com.br**
---
## 🤝 Contributing
We welcome contributions! Please read our contributing guidelines before submitting PRs.
### Contributors
<a href="https://github.com/generalbots/botserver/graphs/contributors">
<img src="https://contrib.rocks/image?repo=generalbots/botserver" />
</a>
---
## 🔑 Remember
- **ZERO WARNINGS** - Fix every clippy warning
- **ZERO COMMENTS** - No comments, no doc comments
- **NO ALLOW IN CODE** - Configure exceptions in Cargo.toml only
- **NO DEAD CODE** - Delete unused code
- **NO UNWRAP/EXPECT** - Use ? or combinators (955 instances to fix)
- **MINIMIZE CLONES** - Avoid excessive allocations (12,973 instances to optimize)
- **PARAMETERIZED SQL** - Never format! for queries
- **VALIDATE COMMANDS** - Never pass raw user input
- **INLINE FORMAT ARGS** - `format!("{name}")` not `format!("{}", name)`
- **USE SELF** - In impl blocks, use Self not type name
- **FILE SIZE LIMIT** - Max 450 lines per file, refactor at 350 lines
- **Version 6.2.0** - Do not change without approval
- **GIT WORKFLOW** - ALWAYS push to ALL repositories (github, pragmatismo)
---
## 🚨 Immediate Action Required
1. **Replace 955 unwrap()/expect() calls** with proper error handling
2. **Optimize 12,973 clone()/to_string() calls** for performance
3. **Refactor 5 large files** following refactoring plan
4. **Add missing error handling** in critical paths
5. **Implement proper logging** instead of panicking
---
## 📄 License
General Bot Copyright (c) pragmatismo.com.br. All rights reserved.
Licensed under the **AGPL-3.0**.
According to our dual licensing model, this program can be used either under the terms of the GNU Affero General Public License, version 3, or under a proprietary license.
---
## 🔗 Links
- **Website:** [pragmatismo.com.br](https://pragmatismo.com.br)
- **Documentation:** [docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)
- **GitHub:** [github.com/GeneralBots/botserver](https://github.com/GeneralBots/botserver)
- **Stack Overflow:** Tag questions with `generalbots`
- **Video Tutorial:** [7 AI General Bots LLM Templates](https://www.youtube.com/watch?v=KJgvUPXi3Fw)
---
**General Bots Code Name:** [Guaribas](https://en.wikipedia.org/wiki/Guaribas)
> "No one should have to do work that can be done by a machine." - Roberto Mangabeira Unger

89
add-req.sh Executable file
View file

@ -0,0 +1,89 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR"
OUTPUT_FILE="/tmp/prompt.out"
echo "Consolidated LLM Context" > "$OUTPUT_FILE"
prompts=(
"./prompts/dev/platform/shared.md"
"./prompts/dev/platform/cli.md"
"./prompts/dev/platform/ide.md"
"./Cargo.toml"
)
for file in "${prompts[@]}"; do
if [ -f "$file" ]; then
cat "$file" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
fi
done
dirs=(
"auth"
#"automation"
#"basic"
#"bootstrap"
"bot"
#"channels"
#"config"
#"context"
#"drive_monitor"
"email"
"file"
#"kb"
"llm"
#"llm_models"
"meet"
#"org"
#"package_manager"
#"riot_compiler"
"session"
"shared"
#"tests"
#"tools"
#"ui"
#"ui_tree"
#"web_server"
#"web_automation"
)
for dir in "${dirs[@]}"; do
find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read -r file; do
echo "$file" >> "$OUTPUT_FILE"
cat "$file" >> "$OUTPUT_FILE"
done
done
# Additional specific files
files=(
"$PROJECT_ROOT/src/main.rs"
#"$PROJECT_ROOT/src/basic/keywords/mod.rs"
)
for file in "${files[@]}"; do
echo "$file" >> "$OUTPUT_FILE"
cat "$file" >> "$OUTPUT_FILE"
done
# Remove all blank lines and reduce whitespace greater than 1 space
sed -i 's/[[:space:]]*$//' "$OUTPUT_FILE"
sed -i '/^$/d' "$OUTPUT_FILE"
sed -i 's/ \+/ /g' "$OUTPUT_FILE"
# Calculate and display token count (approximation: words * 1.3)
WORD_COUNT=$(wc -w < "$OUTPUT_FILE")
TOKEN_COUNT=$(echo "$WORD_COUNT * 1.3 / 1" | bc)
FILE_SIZE=$(wc -c < "$OUTPUT_FILE")
echo "" >> "$OUTPUT_FILE"
echo "Approximate token count: $TOKEN_COUNT"
echo "Context size: $FILE_SIZE bytes"
cat "$OUTPUT_FILE" | xclip -selection clipboard
echo "Content copied to clipboard (xclip)"
rm -f "$OUTPUT_FILE"

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 B

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
window.searchData = {"kinds":{"128":"Class","512":"Constructor","1024":"Property","2048":"Method"},"rows":[{"id":0,"kind":128,"name":"RootData","url":"classes/rootdata.html","classes":"tsd-kind-class"},{"id":1,"kind":512,"name":"constructor","url":"classes/rootdata.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"RootData"},{"id":2,"kind":1024,"name":"publicAddress","url":"classes/rootdata.html#publicaddress","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":3,"kind":1024,"name":"server","url":"classes/rootdata.html#server","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":4,"kind":1024,"name":"sysPackages","url":"classes/rootdata.html#syspackages","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":5,"kind":1024,"name":"appPackages","url":"classes/rootdata.html#apppackages","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":6,"kind":1024,"name":"minService","url":"classes/rootdata.html#minservice","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":7,"kind":1024,"name":"bootInstance","url":"classes/rootdata.html#bootinstance","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":8,"kind":1024,"name":"minInstances","url":"classes/rootdata.html#mininstances","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":9,"kind":1024,"name":"minBoot","url":"classes/rootdata.html#minboot","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":10,"kind":1024,"name":"wwwroot","url":"classes/rootdata.html#wwwroot","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":11,"kind":1024,"name":"entryPointDialog","url":"classes/rootdata.html#entrypointdialog","classes":"tsd-kind-property tsd-parent-kind-class","parent":"RootData"},{"id":12,"kind":128,"name":"GBServer","url":"classes/gbserver.html","classes":"tsd-kind-class"},{"id":13,"kind":1024,"name":"globals","url":"classes/gbserver.html#globals","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-static","parent":"GBServer"},{"id":14,"kind":2048,"name":"run","url":"classes/gbserver.html#run","classes":"tsd-kind-method tsd-parent-kind-class tsd-is-static","parent":"GBServer"},{"id":15,"kind":512,"name":"constructor","url":"classes/gbserver.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"GBServer"}],"index":{"version":"2.3.9","fields":["name","parent"],"fieldVectors":[["name/0",[0,3.075]],["parent/0",[]],["name/1",[1,19.169]],["parent/1",[0,0.291]],["name/2",[2,24.277]],["parent/2",[0,0.291]],["name/3",[3,24.277]],["parent/3",[0,0.291]],["name/4",[4,24.277]],["parent/4",[0,0.291]],["name/5",[5,24.277]],["parent/5",[0,0.291]],["name/6",[6,24.277]],["parent/6",[0,0.291]],["name/7",[7,24.277]],["parent/7",[0,0.291]],["name/8",[8,24.277]],["parent/8",[0,0.291]],["name/9",[9,24.277]],["parent/9",[0,0.291]],["name/10",[10,24.277]],["parent/10",[0,0.291]],["name/11",[11,24.277]],["parent/11",[0,0.291]],["name/12",[12,13.291]],["parent/12",[]],["name/13",[13,24.277]],["parent/13",[12,1.256]],["name/14",[14,24.277]],["parent/14",[12,1.256]],["name/15",[1,19.169]],["parent/15",[12,1.256]]],"invertedIndex":[["apppackages",{"_index":5,"name":{"5":{}},"parent":{}}],["bootinstance",{"_index":7,"name":{"7":{}},"parent":{}}],["constructor",{"_index":1,"name":{"1":{},"15":{}},"parent":{}}],["entrypointdialog",{"_index":11,"name":{"11":{}},"parent":{}}],["gbserver",{"_index":12,"name":{"12":{}},"parent":{"13":{},"14":{},"15":{}}}],["globals",{"_index":13,"name":{"13":{}},"parent":{}}],["minboot",{"_index":9,"name":{"9":{}},"parent":{}}],["mininstances",{"_index":8,"name":{"8":{}},"parent":{}}],["minservice",{"_index":6,"name":{"6":{}},"parent":{}}],["publicaddress",{"_index":2,"name":{"2":{}},"parent":{}}],["rootdata",{"_index":0,"name":{"0":{}},"parent":{"1":{},"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"8":{},"9":{},"10":{},"11":{}}}],["run",{"_index":14,"name":{"14":{}},"parent":{}}],["server",{"_index":3,"name":{"3":{}},"parent":{}}],["syspackages",{"_index":4,"name":{"4":{}},"parent":{}}],["wwwroot",{"_index":10,"name":{"10":{}},"parent":{}}]],"pipeline":[]}}

7
build.rs Normal file
View file

@ -0,0 +1,7 @@
fn main() {
if std::path::Path::new("../botui/ui/suite/").exists() {
println!("cargo:rerun-if-changed=../botui/ui/suite/");
}
println!("cargo:rerun-if-changed=3rdparty.toml");
println!("cargo:rerun-if-changed=.env.embedded");
}

5
diesel.toml Normal file
View file

@ -0,0 +1,5 @@
[migrations_directory]
dir = "migrations"
[print_schema]
file = "src/shared/schema.rs"

View file

@ -0,0 +1,57 @@
# Multi-Agent Workflows Guide
## Creating Workflows
### Basic Workflow Structure
```basic
ORCHESTRATE WORKFLOW "workflow-name"
STEP 1: BOT "analyzer" "process input"
STEP 2: BOT "validator" "check results"
END WORKFLOW
```
### Human Approval Integration
```basic
STEP 3: HUMAN APPROVAL FROM "manager@company.com"
TIMEOUT 1800 ' 30 minutes
ON TIMEOUT: ESCALATE TO "director@company.com"
```
### Parallel Processing
```basic
STEP 4: PARALLEL
BRANCH A: BOT "processor-1" "handle batch-a"
BRANCH B: BOT "processor-2" "handle batch-b"
END PARALLEL
```
### Event-Driven Coordination
```basic
ON EVENT "data-ready" DO
CONTINUE WORKFLOW AT STEP 5
END ON
PUBLISH EVENT "processing-complete"
```
### Cross-Bot Memory Sharing
```basic
BOT SHARE MEMORY "successful-patterns" WITH "learning-bot"
BOT SYNC MEMORY FROM "master-knowledge-bot"
```
## Best Practices
1. **Keep workflows focused** - Max 10 steps per workflow
2. **Use meaningful names** - Clear bot and step names
3. **Add timeouts** - Always set timeouts for human approvals
4. **Share knowledge** - Use memory sharing for bot learning
5. **Handle events** - Use event system for loose coupling
## Workflow Persistence
Workflows automatically survive server restarts. State is stored in PostgreSQL and recovered on startup.
## Visual Designer
Use the drag-and-drop designer at `/designer/workflow` to create workflows visually. The designer generates BASIC code automatically.

View file

@ -0,0 +1,308 @@
# Tools vs Bots: When to Use Each
**Chapter 4: Understanding the Difference Between Function Calls and AI Agents**
---
## Overview
General Bots provides two ways to extend your bot's capabilities:
- **TOOLs** - Simple functions with input/output
- **BOTs** - Intelligent AI agents that can reason and remember
Understanding when to use each is crucial for building efficient, cost-effective automation.
## Quick Comparison
| Feature | TOOL | BOT |
|---------|------|-----|
| **Purpose** | Data operations | Decision making |
| **Intelligence** | None (function) | Full LLM reasoning |
| **Speed** | Fast (10-100ms) | Slower (1-5 seconds) |
| **Cost** | Free | LLM tokens ($0.001-0.01) |
| **Input** | Structured data | Natural language |
| **Output** | Structured data | Conversational response |
| **Memory** | Stateless | Remembers context |
## Tools: The Function Approach
### What Are Tools?
Tools are **stateless functions** that perform specific operations:
```basic
' Tool usage - direct function call
USE TOOL "check-order"
result = CALL TOOL "check-order" WITH order_id="12345"
' Returns: {"status": "delivered", "amount": 899}
```
### When to Use Tools
✅ **Perfect for:**
- Database queries
- API calls
- Calculations
- Data transformations
- Real-time operations
```basic
' Examples of good tool usage
USE TOOL "get-weather"
weather = CALL TOOL "get-weather" WITH city="São Paulo"
USE TOOL "calculate-tax"
tax = CALL TOOL "calculate-tax" WITH amount=100, region="BR"
USE TOOL "send-email"
CALL TOOL "send-email" WITH to="user@example.com", subject="Order Confirmed"
```
### Tool Limitations
❌ **Cannot:**
- Make decisions
- Understand context
- Remember previous calls
- Handle ambiguous input
- Provide explanations
## Bots: The AI Agent Approach
### What Are Bots?
Bots are **intelligent agents** that can reason, remember, and make decisions:
```basic
' Bot usage - conversational interaction
ADD BOT "order-specialist"
response = ASK BOT "order-specialist" ABOUT "Customer says order 12345 arrived damaged. What should we do?"
' Returns: Detailed analysis with reasoning and recommendation
```
### When to Use Bots
✅ **Perfect for:**
- Complex decision making
- Natural language understanding
- Multi-step reasoning
- Context-aware responses
- Customer service scenarios
```basic
' Examples of good bot usage
ADD BOT "financial-advisor"
advice = ASK BOT "financial-advisor" ABOUT "Customer wants refund after 60 days but threatens legal action"
ADD BOT "technical-support"
solution = ASK BOT "technical-support" ABOUT "User can't login, tried password reset twice"
ADD BOT "content-moderator"
decision = ASK BOT "content-moderator" ABOUT "Review this user comment for policy violations"
```
### Bot Capabilities
✅ **Can:**
- Analyze complex situations
- Remember conversation history
- Use multiple tools internally
- Provide detailed explanations
- Handle edge cases
## Real-World Example: Order Processing
### Scenario
Customer contacts support: *"My laptop order #12345 arrived broken. I need this fixed immediately as I have a presentation tomorrow."*
### Tool-Only Approach (Limited)
```basic
' Simple but inflexible
USE TOOL "check-order"
order = CALL TOOL "check-order" WITH order_id="12345"
USE TOOL "check-warranty"
warranty = CALL TOOL "check-warranty" WITH order_id="12345"
IF order.status = "delivered" AND warranty.valid = true THEN
TALK "You're eligible for replacement"
ELSE
TALK "Please contact manager"
END IF
```
**Problems:**
- No understanding of urgency ("presentation tomorrow")
- No consideration of customer history
- Rigid, rule-based responses
- Cannot handle edge cases
### Bot Approach (Intelligent)
```basic
' Intelligent and flexible
ADD BOT "support-specialist"
response = ASK BOT "support-specialist" ABOUT "Customer says laptop order #12345 arrived broken. They have presentation tomorrow and need immediate help."
```
**Bot's internal reasoning:**
1. Uses `check-order` tool → Order delivered 2 days ago, $1,299 laptop
2. Uses `check-warranty` tool → Premium warranty valid
3. Uses `customer-history` tool → VIP customer, 8 previous orders
4. **Analyzes urgency** → Presentation tomorrow = time-sensitive
5. **Considers options** → Replacement (2-day shipping) vs immediate refund for local purchase
6. **Makes recommendation** → "Given urgency and VIP status, authorize immediate refund so customer can buy locally, plus expedited replacement as backup"
## Hybrid Approach: Best of Both Worlds
**Recommended pattern: Bots use Tools internally**
```basic
' support-specialist.bas - Bot implementation
USE TOOL "check-order"
USE TOOL "check-warranty"
USE TOOL "customer-history"
USE TOOL "inventory-check"
USE KB "support-policies"
WHEN ASKED ABOUT order_issue DO
' Gather data using tools (fast, cheap)
order = CALL TOOL "check-order" WITH order_id
warranty = CALL TOOL "check-warranty" WITH order_id
customer = CALL TOOL "customer-history" WITH customer_id
' Apply AI reasoning (intelligent, contextual)
urgency = ANALYZE urgency FROM user_message
customer_value = CALCULATE value FROM customer.total_orders
IF urgency = "high" AND customer_value = "vip" THEN
recommendation = "Expedited resolution with manager approval"
ELSE IF warranty.type = "premium" THEN
recommendation = "Standard replacement process"
ELSE
recommendation = "Store credit or repair option"
END IF
RETURN detailed_response WITH reasoning AND next_steps
END WHEN
```
## Performance Guidelines
### Tool Performance
- **Latency:** 10-100ms
- **Cost:** $0 (no LLM calls)
- **Throughput:** 1000+ operations/second
- **Use for:** High-frequency, simple operations
### Bot Performance
- **Latency:** 1-5 seconds
- **Cost:** $0.001-0.01 per interaction
- **Throughput:** 10-100 interactions/second
- **Use for:** Complex, high-value decisions
## Decision Framework
### Use TOOL when:
1. **Operation is deterministic** - Same input always produces same output
2. **Speed is critical** - Real-time responses needed
3. **Cost matters** - High-frequency operations
4. **Data is structured** - Clear input/output format
### Use BOT when:
1. **Context matters** - Previous conversation affects response
2. **Reasoning required** - Multiple factors to consider
3. **Natural language input** - Ambiguous or conversational requests
4. **Edge cases exist** - Situations requiring judgment
### Use HYBRID when:
1. **Complex workflows** - Multiple steps with decision points
2. **Data + Intelligence** - Need both fast data access and smart reasoning
3. **Scalability important** - Balance cost and capability
## Common Patterns
### Pattern 1: Data Retrieval
```basic
' TOOL: Simple lookup
price = CALL TOOL "get-price" WITH product_id="laptop-123"
' BOT: Contextual pricing
ADD BOT "pricing-advisor"
quote = ASK BOT "pricing-advisor" ABOUT "Customer wants bulk discount for 50 laptops, they're a returning enterprise client"
```
### Pattern 2: Validation
```basic
' TOOL: Rule-based validation
valid = CALL TOOL "validate-email" WITH email="user@domain.com"
' BOT: Contextual validation
ADD BOT "content-reviewer"
assessment = ASK BOT "content-reviewer" ABOUT "Is this product review appropriate for our family-friendly site?"
```
### Pattern 3: Workflow Orchestration
```basic
' Hybrid: Bot coordinates, tools execute
ORCHESTRATE WORKFLOW "order-processing"
STEP 1: CALL TOOL "validate-payment" WITH payment_info
STEP 2: BOT "fraud-detector" ANALYZES transaction_pattern
STEP 3: CALL TOOL "reserve-inventory" WITH product_id
STEP 4: BOT "shipping-optimizer" SELECTS best_carrier
STEP 5: CALL TOOL "send-confirmation" WITH order_details
END WORKFLOW
```
## Best Practices
### 1. Start Simple, Add Intelligence
```basic
' Phase 1: Tool-based (fast to implement)
result = CALL TOOL "process-refund" WITH order_id, amount
' Phase 2: Add bot intelligence (when complexity grows)
ADD BOT "refund-specialist"
decision = ASK BOT "refund-specialist" ABOUT "Customer wants refund but policy expired, they're threatening bad review"
```
### 2. Cache Bot Responses
```basic
' Expensive bot call
ADD BOT "product-recommender"
recommendations = ASK BOT "product-recommender" ABOUT "Best laptop for gaming under $1000"
' Cache result for similar queries
REMEMBER "gaming-laptop-under-1000" AS recommendations
```
### 3. Fallback Patterns
```basic
' Try bot first, fallback to tool
TRY
response = ASK BOT "smart-assistant" ABOUT user_query
CATCH bot_error
' Fallback to simple tool
response = CALL TOOL "keyword-search" WITH query=user_query
END TRY
```
## Summary
**Tools** are your **workhorses** - fast, reliable, cost-effective for data operations.
**Bots** are your **brain trust** - intelligent, contextual, perfect for complex decisions.
**Hybrid approach** gives you the best of both: use tools for speed and bots for intelligence.
Choose based on your specific needs:
- Need speed? → Tool
- Need intelligence? → Bot
- Need both? → Bot that uses tools
The key is understanding that **tools and bots complement each other** - they're not competing solutions, but different tools for different jobs in your AI automation toolkit.
---
**Next:** [Chapter 5: Building Multi-Agent Workflows](workflows.md)

View file

@ -0,0 +1,74 @@
# BASIC Language Reference - Version 6.2.0
## New Workflow Orchestration Keywords
### ORCHESTRATE WORKFLOW
Creates multi-step workflows with bot coordination.
**Syntax:**
```basic
ORCHESTRATE WORKFLOW "workflow-name"
STEP 1: BOT "bot-name" "action"
STEP 2: HUMAN APPROVAL FROM "email@domain.com" TIMEOUT 1800
STEP 3: PARALLEL
BRANCH A: BOT "bot-a" "process"
BRANCH B: BOT "bot-b" "process"
END PARALLEL
END WORKFLOW
```
**Features:**
- Workflow state persists through server restarts
- Variables automatically passed between steps
- Human approval integration with timeouts
- Parallel processing support
### Event System
**ON EVENT**
```basic
ON EVENT "event-name" DO
TALK "Event received"
END ON
```
**PUBLISH EVENT**
```basic
PUBLISH EVENT "event-name"
```
**WAIT FOR EVENT**
```basic
WAIT FOR EVENT "approval-received" TIMEOUT 3600
```
### Enhanced Memory
**BOT SHARE MEMORY**
```basic
BOT SHARE MEMORY "key" WITH "target-bot"
```
**BOT SYNC MEMORY**
```basic
BOT SYNC MEMORY FROM "source-bot"
```
### Enhanced LLM (Feature-gated)
**Optimized LLM Calls**
```basic
result = LLM "Analyze data" WITH OPTIMIZE FOR "speed"
result = LLM "Complex task" WITH MAX_COST 0.05 MAX_LATENCY 2000
```
## File Type Detection
The designer automatically detects:
- **Tools**: Simple input/output functions
- **Workflows**: Multi-step orchestration
- **Regular Bots**: Conversational interfaces
## Backward Compatibility
All existing BASIC keywords continue to work unchanged. New keywords extend functionality without breaking existing `.gbai` packages.

99
fix-errors.sh Executable file
View file

@ -0,0 +1,99 @@
#!/bin/bash
set -e # Exit on error
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR"
OUTPUT_FILE="/tmp/prompt.out"
# Check required commands
command -v cargo >/dev/null 2>&1 || { echo "cargo is required but not installed" >&2; exit 1; }
command -v xclip >/dev/null 2>&1 || { echo "xclip is required but not installed" >&2; exit 1; }
echo "Please, fix this consolidated LLM Context" > "$OUTPUT_FILE"
prompts=(
"./PROMPT.md"
"./Cargo.toml"
)
# Validate files exist
for file in "${prompts[@]}"; do
if [ ! -f "$file" ]; then
echo "Required file not found: $file" >&2
exit 1
fi
done
for file in "${prompts[@]}"; do
cat "$file" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
done
dirs=(
)
for dir in "${dirs[@]}"; do
if [ -d "$PROJECT_ROOT/src/$dir" ]; then
find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read -r file; do
if [ -f "$file" ]; then
echo "$file" >> "$OUTPUT_FILE"
cat "$file" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
fi
done
fi
done
# Also append the specific files you mentioned
echo "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
cat "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
# Files with config import errors
error_files=(
"src/main.rs"
"src/basic/keywords/kb_statistics.rs"
"src/core/bootstrap/mod.rs"
"src/core/kb/kb_indexer.rs"
"src/core/kb/website_crawler_service.rs"
"src/core/shared/utils.rs"
"src/multimodal/mod.rs"
"src/console/status_panel.rs"
"src/drive/drive_monitor/mod.rs"
"src/email/mod.rs"
"src/llm/cache.rs"
"src/llm/local.rs"
"src/llm/episodic_memory.rs"
"src/basic/keywords/create_site.rs"
"src/basic/keywords/save_from_unstructured.rs"
)
for file in "${error_files[@]}"; do
echo "$PROJECT_ROOT/$file" >> "$OUTPUT_FILE"
cat "$PROJECT_ROOT/$file" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
echo "---" >> "$OUTPUT_FILE"
done
echo "" >> "$OUTPUT_FILE"
echo "Compiling..."
cargo build --message-format=short 2>&1 | grep -E 'error' >> "$OUTPUT_FILE"
# Calculate and display token count (approximation: words * 1.3)
WORD_COUNT=$(wc -w < "$OUTPUT_FILE") || { echo "Error counting words" >&2; exit 1; }
TOKEN_COUNT=$(echo "$WORD_COUNT * 1.3 / 1" | bc) || { echo "Error calculating tokens" >&2; exit 1; }
FILE_SIZE=$(wc -c < "$OUTPUT_FILE") || { echo "Error getting file size" >&2; exit 1; }
echo "" >> "$OUTPUT_FILE"
echo "Approximate token count: $TOKEN_COUNT"
echo "Context size: $FILE_SIZE bytes"
if ! cat "$OUTPUT_FILE" | xclip -selection clipboard; then
echo "Error copying to clipboard" >&2
exit 1
fi
echo "Content copied to clipboard (xclip)"
rm -f "$OUTPUT_FILE"

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,213 +0,0 @@
<!doctype html>
<html class="default no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>General Bots Open Core</title>
<meta name="description" content="Documentation for General Bots Open Core">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="assets/css/main.css">
<script async src="assets/js/search.js" id="search-script"></script>
</head>
<body>
<header>
<div class="tsd-page-toolbar">
<div class="container">
<div class="table-wrap">
<div class="table-cell" id="tsd-search" data-index="assets/js/search.json" data-base=".">
<div class="field">
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
<input id="tsd-search-field" type="text" />
</div>
<ul class="results">
<li class="state loading">Preparing search index...</li>
<li class="state failure">The search index is not available</li>
</ul>
<a href="index.html" class="title">General Bots Open Core</a>
</div>
<div class="table-cell" id="tsd-widgets">
<div id="tsd-filter">
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
<div class="tsd-filter-group">
<div class="tsd-select" id="tsd-filter-visibility">
<span class="tsd-select-label">All</span>
<ul class="tsd-select-list">
<li data-value="public">Public</li>
<li data-value="protected">Public/Protected</li>
<li data-value="private" class="selected">All</li>
</ul>
</div>
<input type="checkbox" id="tsd-filter-inherited" checked />
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
<input type="checkbox" id="tsd-filter-externals" checked />
<label class="tsd-widget" for="tsd-filter-externals">Externals</label>
</div>
</div>
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
</div>
</div>
</div>
</div>
<div class="tsd-page-title">
<div class="container">
<h1>General Bots Open Core</h1>
</div>
</div>
</header>
<div class="container container-main">
<div class="row">
<div class="col-8 col-content">
<div class="tsd-panel tsd-typography">
<table>
<thead>
<tr>
<th>Area</th>
<th>Status</th>
</tr>
</thead>
<tbody><tr>
<td>Releases</td>
<td><a href="https://www.npmjs.com/package/botserver/"><img src="https://img.shields.io/npm/dt/botserver.svg?logo=npm&label=botserver" alt="General Bots"></a> <a href="https://www.npmjs.com/package/botlib/"><img src="https://img.shields.io/npm/dt/botlib.svg?logo=npm&label=botlib" alt=".gbapp lib"></a> <a href="https://github.com/semantic-release/semantic-release"><img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" alt="semantic-release"></a></td>
</tr>
<tr>
<td>Community</td>
<td><a href="https://stackoverflow.com/questions/tagged/generalbots"><img src="https://img.shields.io/stackexchange/stackoverflow/t/generalbots.svg" alt="StackExchange"></a> <a href="https://badges.frapsoft.com"><img src="https://badges.frapsoft.com/os/v2/open-source.svg" alt="Open-source"></a> <a href="http://makeapullrequest.com"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs Welcome"></a> <a href="https://github.com/GeneralBots/BotServer/blob/master/LICENSE.txt"><img src="https://img.shields.io/badge/license-AGPL-blue.svg" alt="License"></a></td>
</tr>
<tr>
<td>Management</td>
<td><a href="https://gitHub.com/GeneralBots/BotServer/graphs/commit-activity"><img src="https://img.shields.io/badge/Maintained%3F-yes-green.svg" alt="Maintenance"></a></td>
</tr>
<tr>
<td>Security</td>
<td><a href="https://snyk.io/test/github/GeneralBots/BotServer"><img src="https://snyk.io/test/github/GeneralBots/BotServer/badge.svg" alt="Known Vulnerabilities"></a></td>
</tr>
<tr>
<td>Building &amp; Quality</td>
<td><a href="https://travis-ci.com/GeneralBots/BotServer"><img src="https://travis-ci.com/GeneralBots/BotServer.svg?branch=master" alt="Build Status"></a> <a href="https://coveralls.io/github/GeneralBots/BotServer"><img src="https://coveralls.io/repos/github/GeneralBots/BotServer/badge.svg" alt="Coverage Status"></a> <a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square" alt="code style: prettier"></a></td>
</tr>
<tr>
<td>Packaging</td>
<td><a href="https://badge.fury.io"><img src="https://badge.fury.io/js/botserver.svg" alt="forthebadge"></a> <a href="https://github.com/GeneralBots/BotServer/releases/latest"><img src="https://camo.githubusercontent.com/0150c0f148d50fe9750ebc5d313581da699a8c50/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7a69702d646f776e6c6f61642d626c75652e737667" alt="ZipFile"></a> <a href="https://david-dm.org"><img src="https://david-dm.org/GeneralBots/botserver.svg" alt="Dependencies"></a> <a href="http://commitizen.github.io/cz-cli/"><img src="https://img.shields.io/badge/commitizen-friendly-brightgreen.svg" alt="Commitizen friendly"></a></td>
</tr>
<tr>
<td>Samples</td>
<td><a href="https://github.com/GeneralBots/BotServer/tree/master/packages/default.gbdialog">VBA</a> or <a href="https://github.com/GeneralBots/AzureADPasswordReset.gbapp"><img src="https://badges.frapsoft.com/typescript/code/typescript.svg?v=101" alt="TypeScript"></a></td>
</tr>
<tr>
<td><a href="https://github.com/lpicanco/docker-botserver">Docker Image</a></td>
<td><img src="https://img.shields.io/docker/automated/lpicanco/botserver.svg" alt="Docker Automated build"> <img src="https://img.shields.io/docker/build/lpicanco/botserver.svg" alt="Docker Build Status"> <img src="https://img.shields.io/microbadger/image-size/lpicanco/botserver.svg" alt="MicroBadger Size"> <img src="https://img.shields.io/microbadger/layers/lpicanco/botserver.svg" alt="MicroBadger Layers"> <img src="https://img.shields.io/docker/pulls/lpicanco/botserver.svg" alt="Docker Pulls"> <br/> <em>Provided by <a href="https://github.com/lpicanco/docker-botserver">@lpicanco</a></em></td>
</tr>
</tbody></table>
<a href="#general-bots" id="general-bots" style="color: inherit; text-decoration: none;">
<h2>General Bots</h2>
</a>
<p><img src="https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/logo.png" alt="General Bot Logo"></p>
<p>General Bot is a strongly typed package based chat bot server focused in convention over configuration and code-less approaches, which brings software packages and application server concepts to help parallel bot development.</p>
<a href="#what-is-a-bot-server" id="what-is-a-bot-server" style="color: inherit; text-decoration: none;">
<h2>What is a Bot Server?</h2>
</a>
<p>Bot Server accelerates the process of developing a bot. It provisions all code
base, resources and deployment to the cloud, and gives you templates you can
choose from whenever you need a new bot. The server has a database and service
backend allowing you to further modify your bot package directly by downloading
a zip file, editing and uploading it back to the server (deploying process) with
no code. The Bot Server also provides a framework to develop bot packages in a more
advanced fashion writing custom code in editors like Visual Studio Code, Atom or Brackets.</p>
<p>Everyone can create bots by just copying and pasting some files and using their
favorite tools from Office (or any text editor) or Photoshop (or any image
editor). BASIC can be used to build custom dialogs so Bot can be extended just like VBA for Excel (currently in alpha).</p>
<p><img src="https://raw.githubusercontent.com/GeneralBots/BotBook/master/images/general-bots-reference-architecture.png" alt="General Bot Reference Architecture"></p>
<a href="#samples" id="samples" style="color: inherit; text-decoration: none;">
<h2>Samples</h2>
</a>
<p>Several samples, including a Bot for AD Password Reset, are avaiable on the <a href="https://github.com/GeneralBots">repository list</a>.</p>
<a href="#guide" id="guide" style="color: inherit; text-decoration: none;">
<h2>Guide</h2>
</a>
<p><a href="https://github.com/GeneralBots/BotBook/tree/master/book">Read the General Bots BotBook Guide</a>.</p>
<a href="#videos" id="videos" style="color: inherit; text-decoration: none;">
<h1>Videos</h1>
</a>
<p>Now with the General Bots server you can press F5 on Visual Studio to get a bot factory on your environment* published on November 10th, 2018.</p>
<p><a href="https://www.youtube.com/watch?v=AfKTwljoMOs"><img src="https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/docs/images/video-01-thumb.jpg" alt="General Bot Video"></a></p>
<p>See how easy is to use &#39;hear&#39; and &#39;talk&#39; to build Microsoft BOT Framework v4 logic with plain BASIC * published on December 3rd, 2018.</p>
<p><a href="https://www.youtube.com/watch?v=yX1sF9n9628"><img src="https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/docs/images/video-02-thumb.jpg" alt="See how easy is to use &#39;hear&#39; and &#39;talk&#39; to build Microsoft BOT Framework v4 logic with plain BASIC"></a></p>
<a href="#contributing" id="contributing" style="color: inherit; text-decoration: none;">
<h1>Contributing</h1>
</a>
<p>This project welcomes contributions and suggestions.
See our <a href="https://github.com/pragmatismo-io/BotServer/blob/master/CONTRIBUTING.md">Contribution Guidelines</a> for more details.</p>
<a href="#reporting-security-issues" id="reporting-security-issues" style="color: inherit; text-decoration: none;">
<h1>Reporting Security Issues</h1>
</a>
<p>Security issues and bugs should be reported privately, via email, to the Pragmatismo.io Security
team at <a href="mailto:security@pragmatismo.io">security@pragmatismo.io</a>. You should
receive a response within 24 hours. If for some reason you do not, please follow up via
email to ensure we received your original message. </p>
<a href="#license-amp-warranty" id="license-amp-warranty" style="color: inherit; text-decoration: none;">
<h1>License &amp; Warranty</h1>
</a>
<p>General Bot Copyright (c) Pragmatismo.io. All rights reserved.
Licensed under the AGPL-3.0. </p>
<p>According to our dual licensing model, this program can be used either
under the terms of the GNU Affero General Public License, version 3,
or under a proprietary license. </p>
<p>The texts of the GNU Affero General Public License with an additional
permission and of our proprietary license can be found at and
in the LICENSE file you have received along with this program.</p>
<p>This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.</p>
<p>&quot;General Bot&quot; is a registered trademark of Pragmatismo.io.
The licensing of the program under the AGPLv3 does not imply a
trademark license. Therefore any rights, title and interest in
our trademarks remain entirely with us.</p>
<p><a href="https://stackoverflow.com/questions/ask?tags=generalbots">:speech_balloon: Ask a question</a> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="https://github.com/GeneralBots/BotBook">:book: Read the Docs</a></p>
<p>General Bots Code Name is <a href="https://en.wikipedia.org/wiki/Guaribas">Guaribas</a>, the name of a city in Brazil, state of Piaui.
<a href="http://www.robertounger.com/en/">Roberto Mangabeira Unger</a>: &quot;No one should have to do work that can be done by a machine&quot;.</p>
</div>
</div>
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
<nav class="tsd-navigation primary">
<ul>
<li class=" ">
<a href="modules.html">Exports</a>
</li>
</ul>
</nav>
<nav class="tsd-navigation secondary menu-sticky">
<ul class="before-current">
<li class=" tsd-kind-class">
<a href="classes/gbserver.html" class="tsd-kind-icon">GBServer</a>
</li>
<li class=" tsd-kind-class">
<a href="classes/rootdata.html" class="tsd-kind-icon">Root<wbr>Data</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<footer class="with-border-bottom">
<div class="container">
<h2>Legend</h2>
<div class="tsd-legend-group">
<ul class="tsd-legend">
<li class="tsd-kind-constructor tsd-parent-kind-class"><span class="tsd-kind-icon">Constructor</span></li>
<li class="tsd-kind-property tsd-parent-kind-class"><span class="tsd-kind-icon">Property</span></li>
</ul>
<ul class="tsd-legend">
<li class="tsd-kind-property tsd-parent-kind-class tsd-is-static"><span class="tsd-kind-icon">Static property</span></li>
<li class="tsd-kind-method tsd-parent-kind-class tsd-is-static"><span class="tsd-kind-icon">Static method</span></li>
</ul>
</div>
</div>
</footer>
<div class="container tsd-generator">
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
</div>
<div class="overlay"></div>
<script src="assets/js/main.js"></script>
</body>
</html>

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,483 @@
DROP TABLE public.usage_analytics;
DROP TABLE public.message_history;
DROP TABLE public.context_injections;
DROP TABLE public.whatsapp_numbers;
DROP TABLE public.user_sessions;
DROP TABLE public.bot_channels;
DROP TABLE public.users;
DROP TABLE public.tools;
DROP TABLE public.system_automations;
DROP TABLE public.organizations;
DROP TABLE public.clicks;
DROP TABLE public.bots;
DROP INDEX idx_bot_memories_key;
DROP INDEX idx_bot_memories_bot_id;
DROP TABLE bot_memories;
-- Drop triggers
DROP TRIGGER IF EXISTS update_basic_tools_updated_at ON basic_tools;
DROP TRIGGER IF EXISTS update_kb_collections_updated_at ON kb_collections;
DROP TRIGGER IF EXISTS update_kb_documents_updated_at ON kb_documents;
-- Drop function
DROP FUNCTION IF EXISTS update_updated_at_column;
-- Drop indexes
DROP INDEX IF EXISTS idx_basic_tools_active;
DROP INDEX IF EXISTS idx_basic_tools_name;
DROP INDEX IF EXISTS idx_basic_tools_bot_id;
DROP INDEX IF EXISTS idx_kb_collections_name;
DROP INDEX IF EXISTS idx_kb_collections_bot_id;
DROP INDEX IF EXISTS idx_kb_documents_indexed_at;
DROP INDEX IF EXISTS idx_kb_documents_hash;
DROP INDEX IF EXISTS idx_kb_documents_collection;
DROP INDEX IF EXISTS idx_kb_documents_bot_id;
-- Drop tables
DROP TABLE IF EXISTS basic_tools;
DROP TABLE IF EXISTS kb_collections;
DROP TABLE IF EXISTS kb_documents;
-- Drop indexes
DROP INDEX IF EXISTS idx_session_tool_name;
DROP INDEX IF EXISTS idx_session_tool_session;
DROP INDEX IF EXISTS idx_user_kb_website;
DROP INDEX IF EXISTS idx_user_kb_name;
DROP INDEX IF EXISTS idx_user_kb_bot_id;
DROP INDEX IF EXISTS idx_user_kb_user_id;
-- Drop tables
DROP TABLE IF EXISTS session_tool_associations;
DROP TABLE IF EXISTS user_kb_associations;
-- Drop indexes first
DROP INDEX IF EXISTS idx_gbot_sync_bot;
DROP INDEX IF EXISTS idx_component_logs_created;
DROP INDEX IF EXISTS idx_component_logs_level;
DROP INDEX IF EXISTS idx_component_logs_component;
DROP INDEX IF EXISTS idx_component_status;
DROP INDEX IF EXISTS idx_component_name;
DROP INDEX IF EXISTS idx_connection_config_active;
DROP INDEX IF EXISTS idx_connection_config_name;
DROP INDEX IF EXISTS idx_connection_config_bot;
DROP INDEX IF EXISTS idx_model_config_default;
DROP INDEX IF EXISTS idx_model_config_active;
DROP INDEX IF EXISTS idx_model_config_type;
DROP INDEX IF EXISTS idx_bot_config_key;
DROP INDEX IF EXISTS idx_bot_config_bot;
DROP INDEX IF EXISTS idx_tenant_config_key;
DROP INDEX IF EXISTS idx_tenant_config_tenant;
DROP INDEX IF EXISTS idx_server_config_type;
DROP INDEX IF EXISTS idx_server_config_key;
-- Drop tables
DROP TABLE IF EXISTS gbot_config_sync;
DROP TABLE IF EXISTS component_logs;
DROP TABLE IF EXISTS component_installations;
DROP TABLE IF EXISTS connection_configurations;
DROP TABLE IF EXISTS model_configurations;
DROP TABLE IF EXISTS bot_configuration;
DROP TABLE IF EXISTS tenant_configuration;
DROP TABLE IF EXISTS server_configuration;
-- Remove added columns if they exist
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'user_sessions' AND column_name = 'tenant_id'
) THEN
ALTER TABLE user_sessions DROP COLUMN tenant_id;
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'bots' AND column_name = 'tenant_id'
) THEN
ALTER TABLE bots DROP COLUMN tenant_id;
END IF;
END $$;
-- Drop tenant indexes if they exist
DROP INDEX IF EXISTS idx_user_sessions_tenant;
DROP INDEX IF EXISTS idx_bots_tenant;
-- Remove default tenant
DELETE FROM tenants WHERE slug = 'default';
-- Revert clicks table changes
CREATE TABLE IF NOT EXISTS public.old_clicks (
campaign_id text NOT NULL,
email text NOT NULL,
updated_at timestamptz DEFAULT now() NULL,
CONSTRAINT clicks_campaign_id_email_key UNIQUE (campaign_id, email)
);
INSERT INTO public.old_clicks (campaign_id, email, updated_at)
SELECT campaign_id, email, updated_at FROM public.clicks;
DROP TABLE public.clicks;
ALTER TABLE public.old_clicks RENAME TO clicks;
-- Remove system_automations constraints and indexes
DROP INDEX IF EXISTS idx_system_automations_bot_kind_param;
ALTER TABLE public.system_automations DROP CONSTRAINT IF EXISTS system_automations_bot_kind_param_unique;
DROP INDEX IF EXISTS idx_system_automations_bot_id;
ALTER TABLE public.system_automations DROP COLUMN IF EXISTS bot_id;
DROP INDEX IF EXISTS idx_system_automations_name;
ALTER TABLE public.system_automations DROP COLUMN IF EXISTS name;
-- Remove bot_configuration constraint
ALTER TABLE bot_configuration DROP CONSTRAINT IF EXISTS bot_configuration_config_key_unique;
-- Drop login tokens table
DROP TABLE IF EXISTS public.user_login_tokens;
-- Drop user preferences table
DROP TABLE IF EXISTS public.user_preferences;
-- Remove session enhancement
ALTER TABLE public.user_sessions
DROP CONSTRAINT IF EXISTS user_sessions_email_account_id_fkey,
DROP COLUMN IF EXISTS active_email_account_id;
-- Drop email folders table
DROP TABLE IF EXISTS public.email_folders;
-- Drop email drafts table
DROP TABLE IF EXISTS public.email_drafts;
-- Drop user email accounts table
DROP TABLE IF EXISTS public.user_email_accounts;
-- Drop triggers
DROP TRIGGER IF EXISTS update_directory_users_updated_at ON public.directory_users;
DROP TRIGGER IF EXISTS update_oauth_applications_updated_at ON public.oauth_applications;
-- Drop function if no other triggers use it
DROP FUNCTION IF EXISTS update_updated_at_column() CASCADE;
-- Drop tables in reverse order of dependencies
DROP TABLE IF EXISTS public.bot_access CASCADE;
DROP TABLE IF EXISTS public.oauth_applications CASCADE;
DROP TABLE IF EXISTS public.directory_users CASCADE;
-- Drop indexes
DROP INDEX IF EXISTS idx_bots_org_id;
-- Remove columns from bots table
ALTER TABLE public.bots
DROP CONSTRAINT IF EXISTS bots_org_id_fkey,
DROP COLUMN IF EXISTS org_id,
DROP COLUMN IF EXISTS is_default;
-- Note: We don't delete the default organization or bot data as they may have other relationships
-- The application should handle orphaned data appropriately
-- Drop session_website_associations table and related indexes
DROP TABLE IF EXISTS session_website_associations;
-- Drop website_crawls table and related objects
DROP TRIGGER IF EXISTS website_crawls_updated_at_trigger ON website_crawls;
DROP FUNCTION IF EXISTS update_website_crawls_updated_at();
DROP TABLE IF EXISTS website_crawls;
-- Rollback Migration: 6.1.0 Enterprise Features
-- WARNING: This will delete all enterprise feature data!
-- NOTE: TABLES AND INDEXES ONLY - No views, triggers, or functions per project standards
-- Includes rollback for: config ID fixes, connected accounts, bot hierarchy, monitors
-- ============================================================================
-- ROLLBACK: Bot Hierarchy and Monitors (from 6.1.3)
-- ============================================================================
-- Drop comments first
COMMENT ON TABLE public.user_organizations IS NULL;
COMMENT ON TABLE public.email_received_events IS NULL;
COMMENT ON TABLE public.folder_change_events IS NULL;
COMMENT ON TABLE public.folder_monitors IS NULL;
COMMENT ON TABLE public.email_monitors IS NULL;
COMMENT ON COLUMN public.bots.inherit_parent_config IS NULL;
COMMENT ON COLUMN public.bots.enabled_tabs_json IS NULL;
COMMENT ON COLUMN public.bots.parent_bot_id IS NULL;
COMMENT ON TABLE public.system_automations IS NULL;
-- Drop user organizations table
DROP INDEX IF EXISTS idx_user_orgs_default;
DROP INDEX IF EXISTS idx_user_orgs_org;
DROP INDEX IF EXISTS idx_user_orgs_user;
DROP TABLE IF EXISTS public.user_organizations;
-- Drop email received events table
DROP INDEX IF EXISTS idx_email_events_received;
DROP INDEX IF EXISTS idx_email_events_processed;
DROP INDEX IF EXISTS idx_email_events_monitor;
DROP TABLE IF EXISTS public.email_received_events;
-- Drop folder change events table
DROP INDEX IF EXISTS idx_folder_events_created;
DROP INDEX IF EXISTS idx_folder_events_processed;
DROP INDEX IF EXISTS idx_folder_events_monitor;
DROP TABLE IF EXISTS public.folder_change_events;
-- Drop folder monitors table
DROP INDEX IF EXISTS idx_folder_monitors_account_email;
DROP INDEX IF EXISTS idx_folder_monitors_active;
DROP INDEX IF EXISTS idx_folder_monitors_provider;
DROP INDEX IF EXISTS idx_folder_monitors_bot_id;
DROP TABLE IF EXISTS public.folder_monitors;
-- Drop email monitors table
DROP INDEX IF EXISTS idx_email_monitors_active;
DROP INDEX IF EXISTS idx_email_monitors_email;
DROP INDEX IF EXISTS idx_email_monitors_bot_id;
DROP TABLE IF EXISTS public.email_monitors;
-- Remove bot hierarchy columns
DROP INDEX IF EXISTS idx_bots_parent_bot_id;
ALTER TABLE public.bots DROP COLUMN IF EXISTS inherit_parent_config;
ALTER TABLE public.bots DROP COLUMN IF EXISTS enabled_tabs_json;
ALTER TABLE public.bots DROP COLUMN IF EXISTS parent_bot_id;
-- ============================================================================
-- ROLLBACK: Connected Accounts (from 6.1.2)
-- ============================================================================
DROP INDEX IF EXISTS idx_account_sync_items_unique;
DROP INDEX IF EXISTS idx_account_sync_items_embedding;
DROP INDEX IF EXISTS idx_account_sync_items_date;
DROP INDEX IF EXISTS idx_account_sync_items_type;
DROP INDEX IF EXISTS idx_account_sync_items_account;
DROP TABLE IF EXISTS account_sync_items;
DROP INDEX IF EXISTS idx_session_account_assoc_unique;
DROP INDEX IF EXISTS idx_session_account_assoc_active;
DROP INDEX IF EXISTS idx_session_account_assoc_account;
DROP INDEX IF EXISTS idx_session_account_assoc_session;
DROP TABLE IF EXISTS session_account_associations;
DROP INDEX IF EXISTS idx_connected_accounts_bot_email;
DROP INDEX IF EXISTS idx_connected_accounts_status;
DROP INDEX IF EXISTS idx_connected_accounts_provider;
DROP INDEX IF EXISTS idx_connected_accounts_email;
DROP INDEX IF EXISTS idx_connected_accounts_user_id;
DROP INDEX IF EXISTS idx_connected_accounts_bot_id;
DROP TABLE IF EXISTS connected_accounts;
-- ============================================================================
-- ROLLBACK: Config ID Type Fixes (from 6.1.1)
-- Revert UUID columns back to TEXT
-- ============================================================================
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'bot_configuration'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE bot_configuration
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'server_configuration'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE server_configuration
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'tenant_configuration'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE tenant_configuration
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'model_configurations'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE model_configurations
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'connection_configurations'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE connection_configurations
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'component_installations'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE component_installations
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'component_logs'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE component_logs
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'gbot_config_sync'
AND column_name = 'id'
AND data_type = 'uuid') THEN
ALTER TABLE gbot_config_sync
ALTER COLUMN id TYPE TEXT USING id::text;
END IF;
END $$;
-- ============================================================================
-- FEATURE TABLES MOVED TO DEDICATED MIGRATIONS
-- ============================================================================
-- Drop triggers and functions
DROP TRIGGER IF EXISTS external_connections_updated_at_trigger ON external_connections;
DROP FUNCTION IF EXISTS update_external_connections_updated_at();
DROP TRIGGER IF EXISTS dynamic_table_definitions_updated_at_trigger ON dynamic_table_definitions;
DROP FUNCTION IF EXISTS update_dynamic_table_definitions_updated_at();
-- Drop indexes
DROP INDEX IF EXISTS idx_external_connections_name;
DROP INDEX IF EXISTS idx_external_connections_bot_id;
DROP INDEX IF EXISTS idx_dynamic_table_fields_name;
DROP INDEX IF EXISTS idx_dynamic_table_fields_table_id;
DROP INDEX IF EXISTS idx_dynamic_table_definitions_connection;
DROP INDEX IF EXISTS idx_dynamic_table_definitions_name;
DROP INDEX IF EXISTS idx_dynamic_table_definitions_bot_id;
-- Drop tables (order matters due to foreign keys)
DROP TABLE IF EXISTS external_connections;
DROP TABLE IF EXISTS dynamic_table_fields;
DROP TABLE IF EXISTS dynamic_table_definitions;
-- Rollback Migration: 6.1.1 AutoTask System
-- Description: Drop tables for the AutoTask system
-- Drop indexes first (automatically dropped with tables, but explicit for clarity)
-- Drop designer_pending_changes
DROP INDEX IF EXISTS idx_designer_pending_changes_expires_at;
DROP INDEX IF EXISTS idx_designer_pending_changes_bot_id;
DROP TABLE IF EXISTS designer_pending_changes;
-- Drop designer_changes
DROP INDEX IF EXISTS idx_designer_changes_created_at;
DROP INDEX IF EXISTS idx_designer_changes_bot_id;
DROP TABLE IF EXISTS designer_changes;
-- Drop intent_classifications
DROP INDEX IF EXISTS idx_intent_classifications_created_at;
DROP INDEX IF EXISTS idx_intent_classifications_intent_type;
DROP INDEX IF EXISTS idx_intent_classifications_bot_id;
DROP TABLE IF EXISTS intent_classifications;
-- Drop generated_apps
DROP INDEX IF EXISTS idx_generated_apps_is_active;
DROP INDEX IF EXISTS idx_generated_apps_name;
DROP INDEX IF EXISTS idx_generated_apps_bot_id;
DROP TABLE IF EXISTS generated_apps;
-- Drop safety_audit_log
DROP INDEX IF EXISTS idx_safety_audit_log_created_at;
DROP INDEX IF EXISTS idx_safety_audit_log_outcome;
DROP INDEX IF EXISTS idx_safety_audit_log_task_id;
DROP INDEX IF EXISTS idx_safety_audit_log_bot_id;
DROP TABLE IF EXISTS safety_audit_log;
-- Drop task_decisions
DROP INDEX IF EXISTS idx_task_decisions_status;
DROP INDEX IF EXISTS idx_task_decisions_task_id;
DROP INDEX IF EXISTS idx_task_decisions_bot_id;
DROP TABLE IF EXISTS task_decisions;
-- Drop task_approvals
DROP INDEX IF EXISTS idx_task_approvals_expires_at;
DROP INDEX IF EXISTS idx_task_approvals_status;
DROP INDEX IF EXISTS idx_task_approvals_task_id;
DROP INDEX IF EXISTS idx_task_approvals_bot_id;
DROP TABLE IF EXISTS task_approvals;
-- Drop execution_plans
DROP INDEX IF EXISTS idx_execution_plans_intent_type;
DROP INDEX IF EXISTS idx_execution_plans_status;
DROP INDEX IF EXISTS idx_execution_plans_task_id;
DROP INDEX IF EXISTS idx_execution_plans_bot_id;
DROP TABLE IF EXISTS execution_plans;
-- Drop auto_tasks
DROP INDEX IF EXISTS idx_auto_tasks_created_at;
DROP INDEX IF EXISTS idx_auto_tasks_priority;
DROP INDEX IF EXISTS idx_auto_tasks_status;
DROP INDEX IF EXISTS idx_auto_tasks_session_id;
DROP INDEX IF EXISTS idx_auto_tasks_bot_id;
DROP TABLE IF EXISTS auto_tasks;
-- Drop pending_info
DROP INDEX IF EXISTS idx_pending_info_is_filled;
DROP INDEX IF EXISTS idx_pending_info_config_key;
DROP INDEX IF EXISTS idx_pending_info_bot_id;
DROP TABLE IF EXISTS pending_info;
-- Rollback: Remove role-based access control columns from dynamic tables
-- Migration: 6.1.2_table_role_access
-- Remove columns from dynamic_table_definitions
ALTER TABLE dynamic_table_definitions
DROP COLUMN IF EXISTS read_roles,
DROP COLUMN IF EXISTS write_roles;
-- Remove columns from dynamic_table_fields
ALTER TABLE dynamic_table_fields
DROP COLUMN IF EXISTS read_roles,
DROP COLUMN IF EXISTS write_roles;
-- Rollback Migration: Knowledge Base Sources
-- Drop triggers first
DROP TRIGGER IF EXISTS update_knowledge_sources_updated_at ON knowledge_sources;
-- Drop indexes
DROP INDEX IF EXISTS idx_knowledge_sources_bot_id;
DROP INDEX IF EXISTS idx_knowledge_sources_status;
DROP INDEX IF EXISTS idx_knowledge_sources_collection;
DROP INDEX IF EXISTS idx_knowledge_sources_content_hash;
DROP INDEX IF EXISTS idx_knowledge_sources_created_at;
DROP INDEX IF EXISTS idx_knowledge_chunks_source_id;
DROP INDEX IF EXISTS idx_knowledge_chunks_chunk_index;
DROP INDEX IF EXISTS idx_knowledge_chunks_content_fts;
DROP INDEX IF EXISTS idx_knowledge_chunks_embedding;
DROP INDEX IF EXISTS idx_research_search_history_bot_id;
DROP INDEX IF EXISTS idx_research_search_history_user_id;
DROP INDEX IF EXISTS idx_research_search_history_created_at;
-- Drop tables (order matters due to foreign key constraints)
DROP TABLE IF EXISTS research_search_history;
DROP TABLE IF EXISTS knowledge_chunks;
DROP TABLE IF EXISTS knowledge_sources;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
DROP INDEX IF EXISTS idx_bots_tenant_id;
DROP INDEX IF EXISTS idx_bots_database_name;
ALTER TABLE bots DROP COLUMN IF EXISTS tenant_id;
ALTER TABLE bots DROP COLUMN IF EXISTS database_name;

View file

@ -0,0 +1,8 @@
ALTER TABLE bots ADD COLUMN IF NOT EXISTS database_name VARCHAR(255) NULL;
ALTER TABLE bots ADD COLUMN IF NOT EXISTS tenant_id UUID NULL;
CREATE INDEX IF NOT EXISTS idx_bots_database_name ON bots(database_name);
CREATE INDEX IF NOT EXISTS idx_bots_tenant_id ON bots(tenant_id);
COMMENT ON COLUMN bots.database_name IS 'Name of the PostgreSQL database for this bot (bot_{name})';
COMMENT ON COLUMN bots.tenant_id IS 'Tenant/organization ID for multi-tenant isolation';

View file

@ -0,0 +1,36 @@
DROP INDEX IF EXISTS idx_product_variants_sku;
DROP INDEX IF EXISTS idx_product_variants_product;
DROP INDEX IF EXISTS idx_inventory_movements_created;
DROP INDEX IF EXISTS idx_inventory_movements_product;
DROP INDEX IF EXISTS idx_inventory_movements_org_bot;
DROP INDEX IF EXISTS idx_price_list_items_service;
DROP INDEX IF EXISTS idx_price_list_items_product;
DROP INDEX IF EXISTS idx_price_list_items_list;
DROP INDEX IF EXISTS idx_price_lists_default;
DROP INDEX IF EXISTS idx_price_lists_active;
DROP INDEX IF EXISTS idx_price_lists_org_bot;
DROP INDEX IF EXISTS idx_product_categories_slug;
DROP INDEX IF EXISTS idx_product_categories_parent;
DROP INDEX IF EXISTS idx_product_categories_org_bot;
DROP INDEX IF EXISTS idx_services_active;
DROP INDEX IF EXISTS idx_services_category;
DROP INDEX IF EXISTS idx_services_org_bot;
DROP INDEX IF EXISTS idx_products_org_sku;
DROP INDEX IF EXISTS idx_products_sku;
DROP INDEX IF EXISTS idx_products_active;
DROP INDEX IF EXISTS idx_products_category;
DROP INDEX IF EXISTS idx_products_org_bot;
DROP TABLE IF EXISTS product_variants;
DROP TABLE IF EXISTS inventory_movements;
DROP TABLE IF EXISTS price_list_items;
DROP TABLE IF EXISTS price_lists;
DROP TABLE IF EXISTS product_categories;
DROP TABLE IF EXISTS services;
DROP TABLE IF EXISTS products;

View file

@ -0,0 +1,139 @@
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
sku VARCHAR(100),
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
product_type VARCHAR(50) NOT NULL DEFAULT 'physical',
price DECIMAL(15,2) NOT NULL DEFAULT 0,
cost DECIMAL(15,2),
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
unit VARCHAR(50) NOT NULL DEFAULT 'unit',
stock_quantity INTEGER NOT NULL DEFAULT 0,
low_stock_threshold INTEGER NOT NULL DEFAULT 10,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
images JSONB NOT NULL DEFAULT '[]',
attributes JSONB NOT NULL DEFAULT '{}',
weight DECIMAL(10,2),
dimensions JSONB,
barcode VARCHAR(100),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE services (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
service_type VARCHAR(50) NOT NULL DEFAULT 'hourly',
hourly_rate DECIMAL(15,2),
fixed_price DECIMAL(15,2),
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
duration_minutes INTEGER,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
attributes JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE product_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
parent_id UUID REFERENCES product_categories(id) ON DELETE SET NULL,
slug VARCHAR(255),
image_url TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE price_lists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
is_default BOOLEAN NOT NULL DEFAULT FALSE,
valid_from DATE,
valid_until DATE,
customer_group VARCHAR(100),
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE price_list_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
price_list_id UUID NOT NULL REFERENCES price_lists(id) ON DELETE CASCADE,
product_id UUID REFERENCES products(id) ON DELETE CASCADE,
service_id UUID REFERENCES services(id) ON DELETE CASCADE,
price DECIMAL(15,2) NOT NULL,
min_quantity INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE inventory_movements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
movement_type VARCHAR(50) NOT NULL,
quantity INTEGER NOT NULL,
reference_type VARCHAR(50),
reference_id UUID,
notes TEXT,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE product_variants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
sku VARCHAR(100),
name VARCHAR(255) NOT NULL,
price_adjustment DECIMAL(15,2) NOT NULL DEFAULT 0,
stock_quantity INTEGER NOT NULL DEFAULT 0,
attributes JSONB NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_products_org_bot ON products(org_id, bot_id);
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_products_active ON products(is_active);
CREATE INDEX idx_products_sku ON products(sku);
CREATE UNIQUE INDEX idx_products_org_sku ON products(org_id, sku) WHERE sku IS NOT NULL;
CREATE INDEX idx_services_org_bot ON services(org_id, bot_id);
CREATE INDEX idx_services_category ON services(category);
CREATE INDEX idx_services_active ON services(is_active);
CREATE INDEX idx_product_categories_org_bot ON product_categories(org_id, bot_id);
CREATE INDEX idx_product_categories_parent ON product_categories(parent_id);
CREATE INDEX idx_product_categories_slug ON product_categories(slug);
CREATE INDEX idx_price_lists_org_bot ON price_lists(org_id, bot_id);
CREATE INDEX idx_price_lists_active ON price_lists(is_active);
CREATE INDEX idx_price_lists_default ON price_lists(is_default);
CREATE INDEX idx_price_list_items_list ON price_list_items(price_list_id);
CREATE INDEX idx_price_list_items_product ON price_list_items(product_id);
CREATE INDEX idx_price_list_items_service ON price_list_items(service_id);
CREATE INDEX idx_inventory_movements_org_bot ON inventory_movements(org_id, bot_id);
CREATE INDEX idx_inventory_movements_product ON inventory_movements(product_id);
CREATE INDEX idx_inventory_movements_created ON inventory_movements(created_at DESC);
CREATE INDEX idx_product_variants_product ON product_variants(product_id);
CREATE INDEX idx_product_variants_sku ON product_variants(sku);

View file

@ -0,0 +1,43 @@
DROP INDEX IF EXISTS idx_people_time_off_status;
DROP INDEX IF EXISTS idx_people_time_off_dates;
DROP INDEX IF EXISTS idx_people_time_off_person;
DROP INDEX IF EXISTS idx_people_time_off_org_bot;
DROP INDEX IF EXISTS idx_people_person_skills_skill;
DROP INDEX IF EXISTS idx_people_person_skills_person;
DROP INDEX IF EXISTS idx_people_skills_category;
DROP INDEX IF EXISTS idx_people_skills_org_bot;
DROP INDEX IF EXISTS idx_people_departments_org_code;
DROP INDEX IF EXISTS idx_people_departments_head;
DROP INDEX IF EXISTS idx_people_departments_parent;
DROP INDEX IF EXISTS idx_people_departments_org_bot;
DROP INDEX IF EXISTS idx_people_org_chart_reports_to;
DROP INDEX IF EXISTS idx_people_org_chart_person;
DROP INDEX IF EXISTS idx_people_org_chart_org;
DROP INDEX IF EXISTS idx_people_team_members_person;
DROP INDEX IF EXISTS idx_people_team_members_team;
DROP INDEX IF EXISTS idx_people_teams_leader;
DROP INDEX IF EXISTS idx_people_teams_parent;
DROP INDEX IF EXISTS idx_people_teams_org_bot;
DROP INDEX IF EXISTS idx_people_org_email;
DROP INDEX IF EXISTS idx_people_user;
DROP INDEX IF EXISTS idx_people_active;
DROP INDEX IF EXISTS idx_people_manager;
DROP INDEX IF EXISTS idx_people_department;
DROP INDEX IF EXISTS idx_people_email;
DROP INDEX IF EXISTS idx_people_org_bot;
DROP TABLE IF EXISTS people_time_off;
DROP TABLE IF EXISTS people_person_skills;
DROP TABLE IF EXISTS people_skills;
DROP TABLE IF EXISTS people_departments;
DROP TABLE IF EXISTS people_org_chart;
DROP TABLE IF EXISTS people_team_members;
DROP TABLE IF EXISTS people_teams;
DROP TABLE IF EXISTS people;

View file

@ -0,0 +1,160 @@
CREATE TABLE people (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
user_id UUID,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(50),
mobile VARCHAR(50),
job_title VARCHAR(255),
department VARCHAR(255),
manager_id UUID REFERENCES people(id) ON DELETE SET NULL,
office_location VARCHAR(255),
hire_date DATE,
birthday DATE,
avatar_url TEXT,
bio TEXT,
skills TEXT[] NOT NULL DEFAULT '{}',
social_links JSONB NOT NULL DEFAULT '{}',
custom_fields JSONB NOT NULL DEFAULT '{}',
timezone VARCHAR(50),
locale VARCHAR(10),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
last_seen_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE people_teams (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
leader_id UUID REFERENCES people(id) ON DELETE SET NULL,
parent_team_id UUID REFERENCES people_teams(id) ON DELETE SET NULL,
color VARCHAR(20),
icon VARCHAR(50),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE people_team_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES people_teams(id) ON DELETE CASCADE,
person_id UUID NOT NULL REFERENCES people(id) ON DELETE CASCADE,
role VARCHAR(100),
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(team_id, person_id)
);
CREATE TABLE people_org_chart (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
person_id UUID NOT NULL REFERENCES people(id) ON DELETE CASCADE,
reports_to_id UUID REFERENCES people(id) ON DELETE SET NULL,
position_title VARCHAR(255),
position_level INTEGER NOT NULL DEFAULT 0,
position_order INTEGER NOT NULL DEFAULT 0,
effective_from DATE,
effective_until DATE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(org_id, person_id, effective_from)
);
CREATE TABLE people_departments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
code VARCHAR(50),
parent_id UUID REFERENCES people_departments(id) ON DELETE SET NULL,
head_id UUID REFERENCES people(id) ON DELETE SET NULL,
cost_center VARCHAR(50),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE people_skills (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
category VARCHAR(100),
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE people_person_skills (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
person_id UUID NOT NULL REFERENCES people(id) ON DELETE CASCADE,
skill_id UUID NOT NULL REFERENCES people_skills(id) ON DELETE CASCADE,
proficiency_level INTEGER NOT NULL DEFAULT 1,
years_experience DECIMAL(4,1),
verified_by UUID REFERENCES people(id) ON DELETE SET NULL,
verified_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(person_id, skill_id)
);
CREATE TABLE people_time_off (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
person_id UUID NOT NULL REFERENCES people(id) ON DELETE CASCADE,
time_off_type VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
start_date DATE NOT NULL,
end_date DATE NOT NULL,
hours_requested DECIMAL(5,1),
reason TEXT,
approved_by UUID REFERENCES people(id) ON DELETE SET NULL,
approved_at TIMESTAMPTZ,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_people_org_bot ON people(org_id, bot_id);
CREATE INDEX idx_people_email ON people(email);
CREATE INDEX idx_people_department ON people(department);
CREATE INDEX idx_people_manager ON people(manager_id);
CREATE INDEX idx_people_active ON people(is_active);
CREATE INDEX idx_people_user ON people(user_id);
CREATE UNIQUE INDEX idx_people_org_email ON people(org_id, email) WHERE email IS NOT NULL;
CREATE INDEX idx_people_teams_org_bot ON people_teams(org_id, bot_id);
CREATE INDEX idx_people_teams_parent ON people_teams(parent_team_id);
CREATE INDEX idx_people_teams_leader ON people_teams(leader_id);
CREATE INDEX idx_people_team_members_team ON people_team_members(team_id);
CREATE INDEX idx_people_team_members_person ON people_team_members(person_id);
CREATE INDEX idx_people_org_chart_org ON people_org_chart(org_id, bot_id);
CREATE INDEX idx_people_org_chart_person ON people_org_chart(person_id);
CREATE INDEX idx_people_org_chart_reports_to ON people_org_chart(reports_to_id);
CREATE INDEX idx_people_departments_org_bot ON people_departments(org_id, bot_id);
CREATE INDEX idx_people_departments_parent ON people_departments(parent_id);
CREATE INDEX idx_people_departments_head ON people_departments(head_id);
CREATE UNIQUE INDEX idx_people_departments_org_code ON people_departments(org_id, code) WHERE code IS NOT NULL;
CREATE INDEX idx_people_skills_org_bot ON people_skills(org_id, bot_id);
CREATE INDEX idx_people_skills_category ON people_skills(category);
CREATE INDEX idx_people_person_skills_person ON people_person_skills(person_id);
CREATE INDEX idx_people_person_skills_skill ON people_person_skills(skill_id);
CREATE INDEX idx_people_time_off_org_bot ON people_time_off(org_id, bot_id);
CREATE INDEX idx_people_time_off_person ON people_time_off(person_id);
CREATE INDEX idx_people_time_off_dates ON people_time_off(start_date, end_date);
CREATE INDEX idx_people_time_off_status ON people_time_off(status);

View file

@ -0,0 +1,43 @@
DROP INDEX IF EXISTS idx_attendant_session_wrap_up_session;
DROP INDEX IF EXISTS idx_attendant_wrap_up_codes_org_code;
DROP INDEX IF EXISTS idx_attendant_wrap_up_codes_org_bot;
DROP INDEX IF EXISTS idx_attendant_tags_org_name;
DROP INDEX IF EXISTS idx_attendant_tags_org_bot;
DROP INDEX IF EXISTS idx_attendant_canned_shortcut;
DROP INDEX IF EXISTS idx_attendant_canned_org_bot;
DROP INDEX IF EXISTS idx_attendant_transfers_session;
DROP INDEX IF EXISTS idx_attendant_agent_status_status;
DROP INDEX IF EXISTS idx_attendant_agent_status_org;
DROP INDEX IF EXISTS idx_attendant_queue_agents_agent;
DROP INDEX IF EXISTS idx_attendant_queue_agents_queue;
DROP INDEX IF EXISTS idx_attendant_session_messages_created;
DROP INDEX IF EXISTS idx_attendant_session_messages_session;
DROP INDEX IF EXISTS idx_attendant_sessions_number;
DROP INDEX IF EXISTS idx_attendant_sessions_created;
DROP INDEX IF EXISTS idx_attendant_sessions_customer;
DROP INDEX IF EXISTS idx_attendant_sessions_queue;
DROP INDEX IF EXISTS idx_attendant_sessions_agent;
DROP INDEX IF EXISTS idx_attendant_sessions_status;
DROP INDEX IF EXISTS idx_attendant_sessions_org_bot;
DROP INDEX IF EXISTS idx_attendant_queues_active;
DROP INDEX IF EXISTS idx_attendant_queues_org_bot;
DROP TABLE IF EXISTS attendant_session_wrap_up;
DROP TABLE IF EXISTS attendant_wrap_up_codes;
DROP TABLE IF EXISTS attendant_tags;
DROP TABLE IF EXISTS attendant_canned_responses;
DROP TABLE IF EXISTS attendant_transfers;
DROP TABLE IF EXISTS attendant_agent_status;
DROP TABLE IF EXISTS attendant_queue_agents;
DROP TABLE IF EXISTS attendant_session_messages;
DROP TABLE IF EXISTS attendant_sessions;
DROP TABLE IF EXISTS attendant_queues;

View file

@ -0,0 +1,183 @@
CREATE TABLE attendant_queues (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
priority INTEGER NOT NULL DEFAULT 0,
max_wait_minutes INTEGER NOT NULL DEFAULT 30,
auto_assign BOOLEAN NOT NULL DEFAULT TRUE,
working_hours JSONB NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE attendant_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
session_number VARCHAR(50) NOT NULL,
channel VARCHAR(50) NOT NULL,
customer_id UUID,
customer_name VARCHAR(255),
customer_email VARCHAR(255),
customer_phone VARCHAR(50),
status VARCHAR(50) NOT NULL DEFAULT 'waiting',
priority INTEGER NOT NULL DEFAULT 0,
agent_id UUID,
queue_id UUID REFERENCES attendant_queues(id) ON DELETE SET NULL,
subject VARCHAR(500),
initial_message TEXT,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
assigned_at TIMESTAMPTZ,
first_response_at TIMESTAMPTZ,
ended_at TIMESTAMPTZ,
wait_time_seconds INTEGER,
handle_time_seconds INTEGER,
satisfaction_rating INTEGER,
satisfaction_comment TEXT,
tags TEXT[] NOT NULL DEFAULT '{}',
metadata JSONB NOT NULL DEFAULT '{}',
notes TEXT,
transfer_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE attendant_session_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES attendant_sessions(id) ON DELETE CASCADE,
sender_type VARCHAR(20) NOT NULL,
sender_id UUID,
sender_name VARCHAR(255),
content TEXT NOT NULL,
content_type VARCHAR(50) NOT NULL DEFAULT 'text',
attachments JSONB NOT NULL DEFAULT '[]',
is_internal BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE attendant_queue_agents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
queue_id UUID NOT NULL REFERENCES attendant_queues(id) ON DELETE CASCADE,
agent_id UUID NOT NULL,
max_concurrent INTEGER NOT NULL DEFAULT 3,
priority INTEGER NOT NULL DEFAULT 0,
skills TEXT[] NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(queue_id, agent_id)
);
CREATE TABLE attendant_agent_status (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
agent_id UUID NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'offline',
status_message VARCHAR(255),
current_sessions INTEGER NOT NULL DEFAULT 0,
max_sessions INTEGER NOT NULL DEFAULT 5,
last_activity_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
break_started_at TIMESTAMPTZ,
break_reason VARCHAR(255),
available_since TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(org_id, agent_id)
);
CREATE TABLE attendant_transfers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES attendant_sessions(id) ON DELETE CASCADE,
from_agent_id UUID,
to_agent_id UUID,
to_queue_id UUID REFERENCES attendant_queues(id) ON DELETE SET NULL,
reason VARCHAR(255),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE attendant_canned_responses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
shortcut VARCHAR(50),
category VARCHAR(100),
queue_id UUID REFERENCES attendant_queues(id) ON DELETE SET NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
usage_count INTEGER NOT NULL DEFAULT 0,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE attendant_tags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(100) NOT NULL,
color VARCHAR(20),
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE attendant_wrap_up_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
code VARCHAR(50) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
requires_notes BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE attendant_session_wrap_up (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES attendant_sessions(id) ON DELETE CASCADE,
wrap_up_code_id UUID REFERENCES attendant_wrap_up_codes(id) ON DELETE SET NULL,
notes TEXT,
follow_up_required BOOLEAN NOT NULL DEFAULT FALSE,
follow_up_date DATE,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(session_id)
);
CREATE INDEX idx_attendant_queues_org_bot ON attendant_queues(org_id, bot_id);
CREATE INDEX idx_attendant_queues_active ON attendant_queues(is_active);
CREATE INDEX idx_attendant_sessions_org_bot ON attendant_sessions(org_id, bot_id);
CREATE INDEX idx_attendant_sessions_status ON attendant_sessions(status);
CREATE INDEX idx_attendant_sessions_agent ON attendant_sessions(agent_id);
CREATE INDEX idx_attendant_sessions_queue ON attendant_sessions(queue_id);
CREATE INDEX idx_attendant_sessions_customer ON attendant_sessions(customer_id);
CREATE INDEX idx_attendant_sessions_created ON attendant_sessions(created_at DESC);
CREATE UNIQUE INDEX idx_attendant_sessions_number ON attendant_sessions(org_id, session_number);
CREATE INDEX idx_attendant_session_messages_session ON attendant_session_messages(session_id);
CREATE INDEX idx_attendant_session_messages_created ON attendant_session_messages(created_at);
CREATE INDEX idx_attendant_queue_agents_queue ON attendant_queue_agents(queue_id);
CREATE INDEX idx_attendant_queue_agents_agent ON attendant_queue_agents(agent_id);
CREATE INDEX idx_attendant_agent_status_org ON attendant_agent_status(org_id, bot_id);
CREATE INDEX idx_attendant_agent_status_status ON attendant_agent_status(status);
CREATE INDEX idx_attendant_transfers_session ON attendant_transfers(session_id);
CREATE INDEX idx_attendant_canned_org_bot ON attendant_canned_responses(org_id, bot_id);
CREATE INDEX idx_attendant_canned_shortcut ON attendant_canned_responses(shortcut);
CREATE INDEX idx_attendant_tags_org_bot ON attendant_tags(org_id, bot_id);
CREATE UNIQUE INDEX idx_attendant_tags_org_name ON attendant_tags(org_id, bot_id, name);
CREATE INDEX idx_attendant_wrap_up_codes_org_bot ON attendant_wrap_up_codes(org_id, bot_id);
CREATE UNIQUE INDEX idx_attendant_wrap_up_codes_org_code ON attendant_wrap_up_codes(org_id, bot_id, code);
CREATE INDEX idx_attendant_session_wrap_up_session ON attendant_session_wrap_up(session_id);

View file

@ -0,0 +1,26 @@
DROP INDEX IF EXISTS idx_calendar_shares_email;
DROP INDEX IF EXISTS idx_calendar_shares_user;
DROP INDEX IF EXISTS idx_calendar_shares_calendar;
DROP INDEX IF EXISTS idx_calendar_event_reminders_pending;
DROP INDEX IF EXISTS idx_calendar_event_reminders_event;
DROP INDEX IF EXISTS idx_calendar_event_attendees_email;
DROP INDEX IF EXISTS idx_calendar_event_attendees_event;
DROP INDEX IF EXISTS idx_calendar_events_recurrence;
DROP INDEX IF EXISTS idx_calendar_events_status;
DROP INDEX IF EXISTS idx_calendar_events_time_range;
DROP INDEX IF EXISTS idx_calendar_events_owner;
DROP INDEX IF EXISTS idx_calendar_events_calendar;
DROP INDEX IF EXISTS idx_calendar_events_org_bot;
DROP INDEX IF EXISTS idx_calendars_primary;
DROP INDEX IF EXISTS idx_calendars_owner;
DROP INDEX IF EXISTS idx_calendars_org_bot;
DROP TABLE IF EXISTS calendar_shares;
DROP TABLE IF EXISTS calendar_event_reminders;
DROP TABLE IF EXISTS calendar_event_attendees;
DROP TABLE IF EXISTS calendar_events;
DROP TABLE IF EXISTS calendars;

View file

@ -0,0 +1,95 @@
CREATE TABLE calendars (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
owner_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
color VARCHAR(20) DEFAULT '#3b82f6',
timezone VARCHAR(100) DEFAULT 'UTC',
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
is_visible BOOLEAN NOT NULL DEFAULT TRUE,
is_shared BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE calendar_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
calendar_id UUID NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,
owner_id UUID NOT NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
location VARCHAR(500),
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ NOT NULL,
all_day BOOLEAN NOT NULL DEFAULT FALSE,
recurrence_rule TEXT,
recurrence_id UUID REFERENCES calendar_events(id) ON DELETE SET NULL,
color VARCHAR(20),
status VARCHAR(50) NOT NULL DEFAULT 'confirmed',
visibility VARCHAR(50) NOT NULL DEFAULT 'default',
busy_status VARCHAR(50) NOT NULL DEFAULT 'busy',
reminders JSONB NOT NULL DEFAULT '[]',
attendees JSONB NOT NULL DEFAULT '[]',
conference_data JSONB,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE calendar_event_attendees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES calendar_events(id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
name VARCHAR(255),
status VARCHAR(50) NOT NULL DEFAULT 'needs-action',
role VARCHAR(50) NOT NULL DEFAULT 'req-participant',
rsvp_time TIMESTAMPTZ,
comment TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE calendar_event_reminders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES calendar_events(id) ON DELETE CASCADE,
reminder_type VARCHAR(50) NOT NULL DEFAULT 'notification',
minutes_before INTEGER NOT NULL,
is_sent BOOLEAN NOT NULL DEFAULT FALSE,
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE calendar_shares (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
calendar_id UUID NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,
shared_with_user_id UUID,
shared_with_email VARCHAR(255),
permission VARCHAR(50) NOT NULL DEFAULT 'read',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(calendar_id, shared_with_user_id),
UNIQUE(calendar_id, shared_with_email)
);
CREATE INDEX idx_calendars_org_bot ON calendars(org_id, bot_id);
CREATE INDEX idx_calendars_owner ON calendars(owner_id);
CREATE INDEX idx_calendars_primary ON calendars(owner_id, is_primary) WHERE is_primary = TRUE;
CREATE INDEX idx_calendar_events_org_bot ON calendar_events(org_id, bot_id);
CREATE INDEX idx_calendar_events_calendar ON calendar_events(calendar_id);
CREATE INDEX idx_calendar_events_owner ON calendar_events(owner_id);
CREATE INDEX idx_calendar_events_time_range ON calendar_events(start_time, end_time);
CREATE INDEX idx_calendar_events_status ON calendar_events(status);
CREATE INDEX idx_calendar_events_recurrence ON calendar_events(recurrence_id) WHERE recurrence_id IS NOT NULL;
CREATE INDEX idx_calendar_event_attendees_event ON calendar_event_attendees(event_id);
CREATE INDEX idx_calendar_event_attendees_email ON calendar_event_attendees(email);
CREATE INDEX idx_calendar_event_reminders_event ON calendar_event_reminders(event_id);
CREATE INDEX idx_calendar_event_reminders_pending ON calendar_event_reminders(is_sent, minutes_before) WHERE is_sent = FALSE;
CREATE INDEX idx_calendar_shares_calendar ON calendar_shares(calendar_id);
CREATE INDEX idx_calendar_shares_user ON calendar_shares(shared_with_user_id) WHERE shared_with_user_id IS NOT NULL;
CREATE INDEX idx_calendar_shares_email ON calendar_shares(shared_with_email) WHERE shared_with_email IS NOT NULL;

View file

@ -0,0 +1,43 @@
DROP INDEX IF EXISTS idx_okr_activity_created;
DROP INDEX IF EXISTS idx_okr_activity_user;
DROP INDEX IF EXISTS idx_okr_activity_key_result;
DROP INDEX IF EXISTS idx_okr_activity_objective;
DROP INDEX IF EXISTS idx_okr_activity_org_bot;
DROP INDEX IF EXISTS idx_okr_comments_parent;
DROP INDEX IF EXISTS idx_okr_comments_key_result;
DROP INDEX IF EXISTS idx_okr_comments_objective;
DROP INDEX IF EXISTS idx_okr_comments_org_bot;
DROP INDEX IF EXISTS idx_okr_templates_system;
DROP INDEX IF EXISTS idx_okr_templates_category;
DROP INDEX IF EXISTS idx_okr_templates_org_bot;
DROP INDEX IF EXISTS idx_okr_alignments_parent;
DROP INDEX IF EXISTS idx_okr_alignments_child;
DROP INDEX IF EXISTS idx_okr_alignments_org_bot;
DROP INDEX IF EXISTS idx_okr_checkins_created;
DROP INDEX IF EXISTS idx_okr_checkins_user;
DROP INDEX IF EXISTS idx_okr_checkins_key_result;
DROP INDEX IF EXISTS idx_okr_checkins_org_bot;
DROP INDEX IF EXISTS idx_okr_key_results_due_date;
DROP INDEX IF EXISTS idx_okr_key_results_status;
DROP INDEX IF EXISTS idx_okr_key_results_owner;
DROP INDEX IF EXISTS idx_okr_key_results_objective;
DROP INDEX IF EXISTS idx_okr_key_results_org_bot;
DROP INDEX IF EXISTS idx_okr_objectives_status;
DROP INDEX IF EXISTS idx_okr_objectives_period;
DROP INDEX IF EXISTS idx_okr_objectives_parent;
DROP INDEX IF EXISTS idx_okr_objectives_owner;
DROP INDEX IF EXISTS idx_okr_objectives_org_bot;
DROP TABLE IF EXISTS okr_activity_log;
DROP TABLE IF EXISTS okr_comments;
DROP TABLE IF EXISTS okr_templates;
DROP TABLE IF EXISTS okr_alignments;
DROP TABLE IF EXISTS okr_checkins;
DROP TABLE IF EXISTS okr_key_results;
DROP TABLE IF EXISTS okr_objectives;

View file

@ -0,0 +1,150 @@
CREATE TABLE okr_objectives (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
owner_id UUID NOT NULL,
parent_id UUID REFERENCES okr_objectives(id) ON DELETE SET NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
period VARCHAR(50) NOT NULL,
period_start DATE,
period_end DATE,
status VARCHAR(50) NOT NULL DEFAULT 'draft',
progress DECIMAL(5,2) NOT NULL DEFAULT 0,
visibility VARCHAR(50) NOT NULL DEFAULT 'team',
weight DECIMAL(3,2) NOT NULL DEFAULT 1.0,
tags TEXT[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE okr_key_results (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
objective_id UUID NOT NULL REFERENCES okr_objectives(id) ON DELETE CASCADE,
owner_id UUID NOT NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
metric_type VARCHAR(50) NOT NULL,
start_value DECIMAL(15,2) NOT NULL DEFAULT 0,
target_value DECIMAL(15,2) NOT NULL,
current_value DECIMAL(15,2) NOT NULL DEFAULT 0,
unit VARCHAR(50),
weight DECIMAL(3,2) NOT NULL DEFAULT 1.0,
status VARCHAR(50) NOT NULL DEFAULT 'not_started',
due_date DATE,
scoring_type VARCHAR(50) NOT NULL DEFAULT 'linear',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE okr_checkins (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
key_result_id UUID NOT NULL REFERENCES okr_key_results(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
previous_value DECIMAL(15,2),
new_value DECIMAL(15,2) NOT NULL,
note TEXT,
confidence VARCHAR(50),
blockers TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE okr_alignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
child_objective_id UUID NOT NULL REFERENCES okr_objectives(id) ON DELETE CASCADE,
parent_objective_id UUID NOT NULL REFERENCES okr_objectives(id) ON DELETE CASCADE,
alignment_type VARCHAR(50) NOT NULL DEFAULT 'supports',
weight DECIMAL(3,2) NOT NULL DEFAULT 1.0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(child_objective_id, parent_objective_id)
);
CREATE TABLE okr_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
objective_template JSONB NOT NULL DEFAULT '{}',
key_result_templates JSONB NOT NULL DEFAULT '[]',
is_system BOOLEAN NOT NULL DEFAULT FALSE,
usage_count INTEGER NOT NULL DEFAULT 0,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE okr_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
objective_id UUID REFERENCES okr_objectives(id) ON DELETE CASCADE,
key_result_id UUID REFERENCES okr_key_results(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
content TEXT NOT NULL,
parent_comment_id UUID REFERENCES okr_comments(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT okr_comments_target_check CHECK (
(objective_id IS NOT NULL AND key_result_id IS NULL) OR
(objective_id IS NULL AND key_result_id IS NOT NULL)
)
);
CREATE TABLE okr_activity_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
objective_id UUID REFERENCES okr_objectives(id) ON DELETE CASCADE,
key_result_id UUID REFERENCES okr_key_results(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
activity_type VARCHAR(50) NOT NULL,
description TEXT,
old_value TEXT,
new_value TEXT,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_okr_objectives_org_bot ON okr_objectives(org_id, bot_id);
CREATE INDEX idx_okr_objectives_owner ON okr_objectives(owner_id);
CREATE INDEX idx_okr_objectives_parent ON okr_objectives(parent_id) WHERE parent_id IS NOT NULL;
CREATE INDEX idx_okr_objectives_period ON okr_objectives(period, period_start, period_end);
CREATE INDEX idx_okr_objectives_status ON okr_objectives(status);
CREATE INDEX idx_okr_key_results_org_bot ON okr_key_results(org_id, bot_id);
CREATE INDEX idx_okr_key_results_objective ON okr_key_results(objective_id);
CREATE INDEX idx_okr_key_results_owner ON okr_key_results(owner_id);
CREATE INDEX idx_okr_key_results_status ON okr_key_results(status);
CREATE INDEX idx_okr_key_results_due_date ON okr_key_results(due_date) WHERE due_date IS NOT NULL;
CREATE INDEX idx_okr_checkins_org_bot ON okr_checkins(org_id, bot_id);
CREATE INDEX idx_okr_checkins_key_result ON okr_checkins(key_result_id);
CREATE INDEX idx_okr_checkins_user ON okr_checkins(user_id);
CREATE INDEX idx_okr_checkins_created ON okr_checkins(created_at DESC);
CREATE INDEX idx_okr_alignments_org_bot ON okr_alignments(org_id, bot_id);
CREATE INDEX idx_okr_alignments_child ON okr_alignments(child_objective_id);
CREATE INDEX idx_okr_alignments_parent ON okr_alignments(parent_objective_id);
CREATE INDEX idx_okr_templates_org_bot ON okr_templates(org_id, bot_id);
CREATE INDEX idx_okr_templates_category ON okr_templates(category);
CREATE INDEX idx_okr_templates_system ON okr_templates(is_system) WHERE is_system = TRUE;
CREATE INDEX idx_okr_comments_org_bot ON okr_comments(org_id, bot_id);
CREATE INDEX idx_okr_comments_objective ON okr_comments(objective_id) WHERE objective_id IS NOT NULL;
CREATE INDEX idx_okr_comments_key_result ON okr_comments(key_result_id) WHERE key_result_id IS NOT NULL;
CREATE INDEX idx_okr_comments_parent ON okr_comments(parent_comment_id) WHERE parent_comment_id IS NOT NULL;
CREATE INDEX idx_okr_activity_org_bot ON okr_activity_log(org_id, bot_id);
CREATE INDEX idx_okr_activity_objective ON okr_activity_log(objective_id) WHERE objective_id IS NOT NULL;
CREATE INDEX idx_okr_activity_key_result ON okr_activity_log(key_result_id) WHERE key_result_id IS NOT NULL;
CREATE INDEX idx_okr_activity_user ON okr_activity_log(user_id);
CREATE INDEX idx_okr_activity_created ON okr_activity_log(created_at DESC);

View file

@ -0,0 +1,25 @@
DROP INDEX IF EXISTS idx_canvas_comments_unresolved;
DROP INDEX IF EXISTS idx_canvas_comments_parent;
DROP INDEX IF EXISTS idx_canvas_comments_element;
DROP INDEX IF EXISTS idx_canvas_comments_canvas;
DROP INDEX IF EXISTS idx_canvas_versions_number;
DROP INDEX IF EXISTS idx_canvas_versions_canvas;
DROP INDEX IF EXISTS idx_canvas_collaborators_user;
DROP INDEX IF EXISTS idx_canvas_collaborators_canvas;
DROP INDEX IF EXISTS idx_canvas_elements_z_index;
DROP INDEX IF EXISTS idx_canvas_elements_type;
DROP INDEX IF EXISTS idx_canvas_elements_canvas;
DROP INDEX IF EXISTS idx_canvases_template;
DROP INDEX IF EXISTS idx_canvases_public;
DROP INDEX IF EXISTS idx_canvases_created_by;
DROP INDEX IF EXISTS idx_canvases_org_bot;
DROP TABLE IF EXISTS canvas_comments;
DROP TABLE IF EXISTS canvas_versions;
DROP TABLE IF EXISTS canvas_collaborators;
DROP TABLE IF EXISTS canvas_elements;
DROP TABLE IF EXISTS canvases;

View file

@ -0,0 +1,90 @@
CREATE TABLE canvases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
width INTEGER NOT NULL DEFAULT 1920,
height INTEGER NOT NULL DEFAULT 1080,
background_color VARCHAR(20) DEFAULT '#ffffff',
thumbnail_url TEXT,
is_public BOOLEAN NOT NULL DEFAULT FALSE,
is_template BOOLEAN NOT NULL DEFAULT FALSE,
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE canvas_elements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canvas_id UUID NOT NULL REFERENCES canvases(id) ON DELETE CASCADE,
element_type VARCHAR(50) NOT NULL,
x DOUBLE PRECISION NOT NULL DEFAULT 0,
y DOUBLE PRECISION NOT NULL DEFAULT 0,
width DOUBLE PRECISION NOT NULL DEFAULT 100,
height DOUBLE PRECISION NOT NULL DEFAULT 100,
rotation DOUBLE PRECISION NOT NULL DEFAULT 0,
z_index INTEGER NOT NULL DEFAULT 0,
locked BOOLEAN NOT NULL DEFAULT FALSE,
properties JSONB NOT NULL DEFAULT '{}',
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE canvas_collaborators (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canvas_id UUID NOT NULL REFERENCES canvases(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
permission VARCHAR(50) NOT NULL DEFAULT 'view',
added_by UUID,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(canvas_id, user_id)
);
CREATE TABLE canvas_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canvas_id UUID NOT NULL REFERENCES canvases(id) ON DELETE CASCADE,
version_number INTEGER NOT NULL,
name VARCHAR(255),
elements_snapshot JSONB NOT NULL DEFAULT '[]',
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(canvas_id, version_number)
);
CREATE TABLE canvas_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canvas_id UUID NOT NULL REFERENCES canvases(id) ON DELETE CASCADE,
element_id UUID REFERENCES canvas_elements(id) ON DELETE CASCADE,
parent_comment_id UUID REFERENCES canvas_comments(id) ON DELETE CASCADE,
author_id UUID NOT NULL,
content TEXT NOT NULL,
x_position DOUBLE PRECISION,
y_position DOUBLE PRECISION,
resolved BOOLEAN NOT NULL DEFAULT FALSE,
resolved_by UUID,
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_canvases_org_bot ON canvases(org_id, bot_id);
CREATE INDEX idx_canvases_created_by ON canvases(created_by);
CREATE INDEX idx_canvases_public ON canvases(is_public) WHERE is_public = TRUE;
CREATE INDEX idx_canvases_template ON canvases(is_template) WHERE is_template = TRUE;
CREATE INDEX idx_canvas_elements_canvas ON canvas_elements(canvas_id);
CREATE INDEX idx_canvas_elements_type ON canvas_elements(element_type);
CREATE INDEX idx_canvas_elements_z_index ON canvas_elements(canvas_id, z_index);
CREATE INDEX idx_canvas_collaborators_canvas ON canvas_collaborators(canvas_id);
CREATE INDEX idx_canvas_collaborators_user ON canvas_collaborators(user_id);
CREATE INDEX idx_canvas_versions_canvas ON canvas_versions(canvas_id);
CREATE INDEX idx_canvas_versions_number ON canvas_versions(canvas_id, version_number DESC);
CREATE INDEX idx_canvas_comments_canvas ON canvas_comments(canvas_id);
CREATE INDEX idx_canvas_comments_element ON canvas_comments(element_id) WHERE element_id IS NOT NULL;
CREATE INDEX idx_canvas_comments_parent ON canvas_comments(parent_comment_id) WHERE parent_comment_id IS NOT NULL;
CREATE INDEX idx_canvas_comments_unresolved ON canvas_comments(canvas_id, resolved) WHERE resolved = FALSE;

View file

@ -0,0 +1,39 @@
DROP INDEX IF EXISTS idx_workspace_templates_system;
DROP INDEX IF EXISTS idx_workspace_templates_category;
DROP INDEX IF EXISTS idx_workspace_templates_org_bot;
DROP INDEX IF EXISTS idx_workspace_comment_reactions_comment;
DROP INDEX IF EXISTS idx_workspace_comments_unresolved;
DROP INDEX IF EXISTS idx_workspace_comments_parent;
DROP INDEX IF EXISTS idx_workspace_comments_block;
DROP INDEX IF EXISTS idx_workspace_comments_page;
DROP INDEX IF EXISTS idx_workspace_comments_workspace;
DROP INDEX IF EXISTS idx_workspace_page_permissions_user;
DROP INDEX IF EXISTS idx_workspace_page_permissions_page;
DROP INDEX IF EXISTS idx_workspace_page_versions_number;
DROP INDEX IF EXISTS idx_workspace_page_versions_page;
DROP INDEX IF EXISTS idx_workspace_pages_position;
DROP INDEX IF EXISTS idx_workspace_pages_public;
DROP INDEX IF EXISTS idx_workspace_pages_template;
DROP INDEX IF EXISTS idx_workspace_pages_parent;
DROP INDEX IF EXISTS idx_workspace_pages_workspace;
DROP INDEX IF EXISTS idx_workspace_members_role;
DROP INDEX IF EXISTS idx_workspace_members_user;
DROP INDEX IF EXISTS idx_workspace_members_workspace;
DROP INDEX IF EXISTS idx_workspaces_created_by;
DROP INDEX IF EXISTS idx_workspaces_org_bot;
DROP TABLE IF EXISTS workspace_templates;
DROP TABLE IF EXISTS workspace_comment_reactions;
DROP TABLE IF EXISTS workspace_comments;
DROP TABLE IF EXISTS workspace_page_permissions;
DROP TABLE IF EXISTS workspace_page_versions;
DROP TABLE IF EXISTS workspace_pages;
DROP TABLE IF EXISTS workspace_members;
DROP TABLE IF EXISTS workspaces;

View file

@ -0,0 +1,141 @@
CREATE TABLE workspaces (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
icon_type VARCHAR(20) DEFAULT 'emoji',
icon_value VARCHAR(100),
cover_image TEXT,
settings JSONB NOT NULL DEFAULT '{}',
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE workspace_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'member',
invited_by UUID,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(workspace_id, user_id)
);
CREATE TABLE workspace_pages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
parent_id UUID REFERENCES workspace_pages(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
icon_type VARCHAR(20),
icon_value VARCHAR(100),
cover_image TEXT,
content JSONB NOT NULL DEFAULT '[]',
properties JSONB NOT NULL DEFAULT '{}',
is_template BOOLEAN NOT NULL DEFAULT FALSE,
template_id UUID REFERENCES workspace_pages(id) ON DELETE SET NULL,
is_public BOOLEAN NOT NULL DEFAULT FALSE,
public_edit BOOLEAN NOT NULL DEFAULT FALSE,
position INTEGER NOT NULL DEFAULT 0,
created_by UUID NOT NULL,
last_edited_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE workspace_page_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
page_id UUID NOT NULL REFERENCES workspace_pages(id) ON DELETE CASCADE,
version_number INTEGER NOT NULL,
title VARCHAR(500) NOT NULL,
content JSONB NOT NULL DEFAULT '[]',
change_summary TEXT,
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(page_id, version_number)
);
CREATE TABLE workspace_page_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
page_id UUID NOT NULL REFERENCES workspace_pages(id) ON DELETE CASCADE,
user_id UUID,
role VARCHAR(50),
permission VARCHAR(50) NOT NULL DEFAULT 'view',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(page_id, user_id),
UNIQUE(page_id, role)
);
CREATE TABLE workspace_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
page_id UUID NOT NULL REFERENCES workspace_pages(id) ON DELETE CASCADE,
block_id UUID,
parent_comment_id UUID REFERENCES workspace_comments(id) ON DELETE CASCADE,
author_id UUID NOT NULL,
content TEXT NOT NULL,
resolved BOOLEAN NOT NULL DEFAULT FALSE,
resolved_by UUID,
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE workspace_comment_reactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
comment_id UUID NOT NULL REFERENCES workspace_comments(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
emoji VARCHAR(20) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(comment_id, user_id, emoji)
);
CREATE TABLE workspace_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
icon_type VARCHAR(20),
icon_value VARCHAR(100),
cover_image TEXT,
content JSONB NOT NULL DEFAULT '[]',
is_system BOOLEAN NOT NULL DEFAULT FALSE,
usage_count INTEGER NOT NULL DEFAULT 0,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_workspaces_org_bot ON workspaces(org_id, bot_id);
CREATE INDEX idx_workspaces_created_by ON workspaces(created_by);
CREATE INDEX idx_workspace_members_workspace ON workspace_members(workspace_id);
CREATE INDEX idx_workspace_members_user ON workspace_members(user_id);
CREATE INDEX idx_workspace_members_role ON workspace_members(role);
CREATE INDEX idx_workspace_pages_workspace ON workspace_pages(workspace_id);
CREATE INDEX idx_workspace_pages_parent ON workspace_pages(parent_id) WHERE parent_id IS NOT NULL;
CREATE INDEX idx_workspace_pages_template ON workspace_pages(is_template) WHERE is_template = TRUE;
CREATE INDEX idx_workspace_pages_public ON workspace_pages(is_public) WHERE is_public = TRUE;
CREATE INDEX idx_workspace_pages_position ON workspace_pages(workspace_id, parent_id, position);
CREATE INDEX idx_workspace_page_versions_page ON workspace_page_versions(page_id);
CREATE INDEX idx_workspace_page_versions_number ON workspace_page_versions(page_id, version_number DESC);
CREATE INDEX idx_workspace_page_permissions_page ON workspace_page_permissions(page_id);
CREATE INDEX idx_workspace_page_permissions_user ON workspace_page_permissions(user_id) WHERE user_id IS NOT NULL;
CREATE INDEX idx_workspace_comments_workspace ON workspace_comments(workspace_id);
CREATE INDEX idx_workspace_comments_page ON workspace_comments(page_id);
CREATE INDEX idx_workspace_comments_block ON workspace_comments(block_id) WHERE block_id IS NOT NULL;
CREATE INDEX idx_workspace_comments_parent ON workspace_comments(parent_comment_id) WHERE parent_comment_id IS NOT NULL;
CREATE INDEX idx_workspace_comments_unresolved ON workspace_comments(page_id, resolved) WHERE resolved = FALSE;
CREATE INDEX idx_workspace_comment_reactions_comment ON workspace_comment_reactions(comment_id);
CREATE INDEX idx_workspace_templates_org_bot ON workspace_templates(org_id, bot_id);
CREATE INDEX idx_workspace_templates_category ON workspace_templates(category);
CREATE INDEX idx_workspace_templates_system ON workspace_templates(is_system) WHERE is_system = TRUE;

View file

@ -0,0 +1,63 @@
DROP INDEX IF EXISTS idx_social_hashtags_popular;
DROP INDEX IF EXISTS idx_social_hashtags_tag;
DROP INDEX IF EXISTS idx_social_hashtags_org_bot;
DROP INDEX IF EXISTS idx_social_bookmarks_post;
DROP INDEX IF EXISTS idx_social_bookmarks_user;
DROP INDEX IF EXISTS idx_social_praises_created;
DROP INDEX IF EXISTS idx_social_praises_to;
DROP INDEX IF EXISTS idx_social_praises_from;
DROP INDEX IF EXISTS idx_social_praises_org_bot;
DROP INDEX IF EXISTS idx_social_announcements_pinned;
DROP INDEX IF EXISTS idx_social_announcements_priority;
DROP INDEX IF EXISTS idx_social_announcements_active;
DROP INDEX IF EXISTS idx_social_announcements_org_bot;
DROP INDEX IF EXISTS idx_social_poll_votes_user;
DROP INDEX IF EXISTS idx_social_poll_votes_poll;
DROP INDEX IF EXISTS idx_social_poll_options_poll;
DROP INDEX IF EXISTS idx_social_polls_post;
DROP INDEX IF EXISTS idx_social_reactions_user;
DROP INDEX IF EXISTS idx_social_reactions_comment;
DROP INDEX IF EXISTS idx_social_reactions_post;
DROP INDEX IF EXISTS idx_social_comments_created;
DROP INDEX IF EXISTS idx_social_comments_author;
DROP INDEX IF EXISTS idx_social_comments_parent;
DROP INDEX IF EXISTS idx_social_comments_post;
DROP INDEX IF EXISTS idx_social_posts_hashtags;
DROP INDEX IF EXISTS idx_social_posts_created;
DROP INDEX IF EXISTS idx_social_posts_announcement;
DROP INDEX IF EXISTS idx_social_posts_pinned;
DROP INDEX IF EXISTS idx_social_posts_visibility;
DROP INDEX IF EXISTS idx_social_posts_parent;
DROP INDEX IF EXISTS idx_social_posts_community;
DROP INDEX IF EXISTS idx_social_posts_author;
DROP INDEX IF EXISTS idx_social_posts_org_bot;
DROP INDEX IF EXISTS idx_social_community_members_role;
DROP INDEX IF EXISTS idx_social_community_members_user;
DROP INDEX IF EXISTS idx_social_community_members_community;
DROP INDEX IF EXISTS idx_social_communities_owner;
DROP INDEX IF EXISTS idx_social_communities_featured;
DROP INDEX IF EXISTS idx_social_communities_visibility;
DROP INDEX IF EXISTS idx_social_communities_slug;
DROP INDEX IF EXISTS idx_social_communities_org_bot;
DROP TABLE IF EXISTS social_hashtags;
DROP TABLE IF EXISTS social_bookmarks;
DROP TABLE IF EXISTS social_praises;
DROP TABLE IF EXISTS social_announcements;
DROP TABLE IF EXISTS social_poll_votes;
DROP TABLE IF EXISTS social_poll_options;
DROP TABLE IF EXISTS social_polls;
DROP TABLE IF EXISTS social_reactions;
DROP TABLE IF EXISTS social_comments;
DROP TABLE IF EXISTS social_posts;
DROP TABLE IF EXISTS social_community_members;
DROP TABLE IF EXISTS social_communities;

View file

@ -0,0 +1,219 @@
CREATE TABLE social_communities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT,
cover_image TEXT,
icon TEXT,
visibility VARCHAR(50) NOT NULL DEFAULT 'public',
join_policy VARCHAR(50) NOT NULL DEFAULT 'open',
owner_id UUID NOT NULL,
member_count INTEGER NOT NULL DEFAULT 0,
post_count INTEGER NOT NULL DEFAULT 0,
is_official BOOLEAN NOT NULL DEFAULT FALSE,
is_featured BOOLEAN NOT NULL DEFAULT FALSE,
settings JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
archived_at TIMESTAMPTZ
);
CREATE TABLE social_community_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
community_id UUID NOT NULL REFERENCES social_communities(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'member',
notifications_enabled BOOLEAN NOT NULL DEFAULT TRUE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_seen_at TIMESTAMPTZ,
UNIQUE(community_id, user_id)
);
CREATE TABLE social_posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
author_id UUID NOT NULL,
community_id UUID REFERENCES social_communities(id) ON DELETE CASCADE,
parent_id UUID REFERENCES social_posts(id) ON DELETE CASCADE,
content TEXT NOT NULL,
content_type VARCHAR(50) NOT NULL DEFAULT 'text',
attachments JSONB NOT NULL DEFAULT '[]',
mentions JSONB NOT NULL DEFAULT '[]',
hashtags TEXT[] NOT NULL DEFAULT '{}',
visibility VARCHAR(50) NOT NULL DEFAULT 'public',
is_announcement BOOLEAN NOT NULL DEFAULT FALSE,
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
poll_id UUID,
reaction_counts JSONB NOT NULL DEFAULT '{}',
comment_count INTEGER NOT NULL DEFAULT 0,
share_count INTEGER NOT NULL DEFAULT 0,
view_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
edited_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ
);
CREATE TABLE social_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_id UUID NOT NULL REFERENCES social_posts(id) ON DELETE CASCADE,
parent_comment_id UUID REFERENCES social_comments(id) ON DELETE CASCADE,
author_id UUID NOT NULL,
content TEXT NOT NULL,
mentions JSONB NOT NULL DEFAULT '[]',
reaction_counts JSONB NOT NULL DEFAULT '{}',
reply_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
edited_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ
);
CREATE TABLE social_reactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_id UUID REFERENCES social_posts(id) ON DELETE CASCADE,
comment_id UUID REFERENCES social_comments(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
reaction_type VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT social_reactions_target_check CHECK (
(post_id IS NOT NULL AND comment_id IS NULL) OR
(post_id IS NULL AND comment_id IS NOT NULL)
),
UNIQUE(post_id, user_id, reaction_type),
UNIQUE(comment_id, user_id, reaction_type)
);
CREATE TABLE social_polls (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_id UUID NOT NULL REFERENCES social_posts(id) ON DELETE CASCADE,
question TEXT NOT NULL,
allow_multiple BOOLEAN NOT NULL DEFAULT FALSE,
allow_add_options BOOLEAN NOT NULL DEFAULT FALSE,
anonymous BOOLEAN NOT NULL DEFAULT FALSE,
total_votes INTEGER NOT NULL DEFAULT 0,
ends_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE social_poll_options (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
poll_id UUID NOT NULL REFERENCES social_polls(id) ON DELETE CASCADE,
text VARCHAR(500) NOT NULL,
vote_count INTEGER NOT NULL DEFAULT 0,
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE social_poll_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
poll_id UUID NOT NULL REFERENCES social_polls(id) ON DELETE CASCADE,
option_id UUID NOT NULL REFERENCES social_poll_options(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(poll_id, option_id, user_id)
);
CREATE TABLE social_announcements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
author_id UUID NOT NULL,
title VARCHAR(500) NOT NULL,
content TEXT NOT NULL,
priority VARCHAR(50) NOT NULL DEFAULT 'normal',
target_audience JSONB NOT NULL DEFAULT '{}',
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
requires_acknowledgment BOOLEAN NOT NULL DEFAULT FALSE,
acknowledged_by JSONB NOT NULL DEFAULT '[]',
starts_at TIMESTAMPTZ,
ends_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE social_praises (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
from_user_id UUID NOT NULL,
to_user_id UUID NOT NULL,
badge_type VARCHAR(50) NOT NULL,
message TEXT,
is_public BOOLEAN NOT NULL DEFAULT TRUE,
post_id UUID REFERENCES social_posts(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE social_bookmarks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
post_id UUID NOT NULL REFERENCES social_posts(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, post_id)
);
CREATE TABLE social_hashtags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
tag VARCHAR(100) NOT NULL,
post_count INTEGER NOT NULL DEFAULT 0,
last_used_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(org_id, bot_id, tag)
);
CREATE INDEX idx_social_communities_org_bot ON social_communities(org_id, bot_id);
CREATE INDEX idx_social_communities_slug ON social_communities(slug);
CREATE INDEX idx_social_communities_visibility ON social_communities(visibility);
CREATE INDEX idx_social_communities_featured ON social_communities(is_featured) WHERE is_featured = TRUE;
CREATE INDEX idx_social_communities_owner ON social_communities(owner_id);
CREATE INDEX idx_social_community_members_community ON social_community_members(community_id);
CREATE INDEX idx_social_community_members_user ON social_community_members(user_id);
CREATE INDEX idx_social_community_members_role ON social_community_members(community_id, role);
CREATE INDEX idx_social_posts_org_bot ON social_posts(org_id, bot_id);
CREATE INDEX idx_social_posts_author ON social_posts(author_id);
CREATE INDEX idx_social_posts_community ON social_posts(community_id) WHERE community_id IS NOT NULL;
CREATE INDEX idx_social_posts_parent ON social_posts(parent_id) WHERE parent_id IS NOT NULL;
CREATE INDEX idx_social_posts_visibility ON social_posts(visibility);
CREATE INDEX idx_social_posts_pinned ON social_posts(community_id, is_pinned) WHERE is_pinned = TRUE;
CREATE INDEX idx_social_posts_announcement ON social_posts(is_announcement) WHERE is_announcement = TRUE;
CREATE INDEX idx_social_posts_created ON social_posts(created_at DESC);
CREATE INDEX idx_social_posts_hashtags ON social_posts USING GIN(hashtags);
CREATE INDEX idx_social_comments_post ON social_comments(post_id);
CREATE INDEX idx_social_comments_parent ON social_comments(parent_comment_id) WHERE parent_comment_id IS NOT NULL;
CREATE INDEX idx_social_comments_author ON social_comments(author_id);
CREATE INDEX idx_social_comments_created ON social_comments(created_at DESC);
CREATE INDEX idx_social_reactions_post ON social_reactions(post_id) WHERE post_id IS NOT NULL;
CREATE INDEX idx_social_reactions_comment ON social_reactions(comment_id) WHERE comment_id IS NOT NULL;
CREATE INDEX idx_social_reactions_user ON social_reactions(user_id);
CREATE INDEX idx_social_polls_post ON social_polls(post_id);
CREATE INDEX idx_social_poll_options_poll ON social_poll_options(poll_id);
CREATE INDEX idx_social_poll_votes_poll ON social_poll_votes(poll_id);
CREATE INDEX idx_social_poll_votes_user ON social_poll_votes(user_id);
CREATE INDEX idx_social_announcements_org_bot ON social_announcements(org_id, bot_id);
CREATE INDEX idx_social_announcements_active ON social_announcements(starts_at, ends_at);
CREATE INDEX idx_social_announcements_priority ON social_announcements(priority);
CREATE INDEX idx_social_announcements_pinned ON social_announcements(is_pinned) WHERE is_pinned = TRUE;
CREATE INDEX idx_social_praises_org_bot ON social_praises(org_id, bot_id);
CREATE INDEX idx_social_praises_from ON social_praises(from_user_id);
CREATE INDEX idx_social_praises_to ON social_praises(to_user_id);
CREATE INDEX idx_social_praises_created ON social_praises(created_at DESC);
CREATE INDEX idx_social_bookmarks_user ON social_bookmarks(user_id);
CREATE INDEX idx_social_bookmarks_post ON social_bookmarks(post_id);
CREATE INDEX idx_social_hashtags_org_bot ON social_hashtags(org_id, bot_id);
CREATE INDEX idx_social_hashtags_tag ON social_hashtags(tag);
CREATE INDEX idx_social_hashtags_popular ON social_hashtags(org_id, bot_id, post_count DESC);

View file

@ -0,0 +1,34 @@
DROP INDEX IF EXISTS idx_research_exports_status;
DROP INDEX IF EXISTS idx_research_exports_project;
DROP INDEX IF EXISTS idx_research_collaborators_user;
DROP INDEX IF EXISTS idx_research_collaborators_project;
DROP INDEX IF EXISTS idx_research_citations_style;
DROP INDEX IF EXISTS idx_research_citations_source;
DROP INDEX IF EXISTS idx_research_findings_status;
DROP INDEX IF EXISTS idx_research_findings_type;
DROP INDEX IF EXISTS idx_research_findings_project;
DROP INDEX IF EXISTS idx_research_notes_tags;
DROP INDEX IF EXISTS idx_research_notes_type;
DROP INDEX IF EXISTS idx_research_notes_source;
DROP INDEX IF EXISTS idx_research_notes_project;
DROP INDEX IF EXISTS idx_research_sources_verified;
DROP INDEX IF EXISTS idx_research_sources_type;
DROP INDEX IF EXISTS idx_research_sources_project;
DROP INDEX IF EXISTS idx_research_projects_tags;
DROP INDEX IF EXISTS idx_research_projects_status;
DROP INDEX IF EXISTS idx_research_projects_owner;
DROP INDEX IF EXISTS idx_research_projects_org_bot;
DROP TABLE IF EXISTS research_exports;
DROP TABLE IF EXISTS research_collaborators;
DROP TABLE IF EXISTS research_citations;
DROP TABLE IF EXISTS research_findings;
DROP TABLE IF EXISTS research_notes;
DROP TABLE IF EXISTS research_sources;
DROP TABLE IF EXISTS research_projects;

View file

@ -0,0 +1,118 @@
CREATE TABLE research_projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'active',
owner_id UUID NOT NULL,
tags TEXT[] NOT NULL DEFAULT '{}',
settings JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE research_sources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES research_projects(id) ON DELETE CASCADE,
source_type VARCHAR(50) NOT NULL,
name VARCHAR(500) NOT NULL,
url TEXT,
content TEXT,
summary TEXT,
metadata JSONB NOT NULL DEFAULT '{}',
credibility_score INTEGER,
is_verified BOOLEAN NOT NULL DEFAULT FALSE,
added_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE research_notes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES research_projects(id) ON DELETE CASCADE,
source_id UUID REFERENCES research_sources(id) ON DELETE SET NULL,
title VARCHAR(500),
content TEXT NOT NULL,
note_type VARCHAR(50) NOT NULL DEFAULT 'general',
tags TEXT[] NOT NULL DEFAULT '{}',
highlight_text TEXT,
highlight_position JSONB,
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE research_findings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES research_projects(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
content TEXT NOT NULL,
finding_type VARCHAR(50) NOT NULL DEFAULT 'insight',
confidence_level VARCHAR(50),
supporting_sources JSONB NOT NULL DEFAULT '[]',
related_findings JSONB NOT NULL DEFAULT '[]',
status VARCHAR(50) NOT NULL DEFAULT 'draft',
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE research_citations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source_id UUID NOT NULL REFERENCES research_sources(id) ON DELETE CASCADE,
citation_style VARCHAR(50) NOT NULL DEFAULT 'apa',
formatted_citation TEXT NOT NULL,
bibtex TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE research_collaborators (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES research_projects(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'viewer',
invited_by UUID,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(project_id, user_id)
);
CREATE TABLE research_exports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES research_projects(id) ON DELETE CASCADE,
export_type VARCHAR(50) NOT NULL,
format VARCHAR(50) NOT NULL,
file_url TEXT,
file_size INTEGER,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
CREATE INDEX idx_research_projects_org_bot ON research_projects(org_id, bot_id);
CREATE INDEX idx_research_projects_owner ON research_projects(owner_id);
CREATE INDEX idx_research_projects_status ON research_projects(status);
CREATE INDEX idx_research_projects_tags ON research_projects USING GIN(tags);
CREATE INDEX idx_research_sources_project ON research_sources(project_id);
CREATE INDEX idx_research_sources_type ON research_sources(source_type);
CREATE INDEX idx_research_sources_verified ON research_sources(is_verified) WHERE is_verified = TRUE;
CREATE INDEX idx_research_notes_project ON research_notes(project_id);
CREATE INDEX idx_research_notes_source ON research_notes(source_id) WHERE source_id IS NOT NULL;
CREATE INDEX idx_research_notes_type ON research_notes(note_type);
CREATE INDEX idx_research_notes_tags ON research_notes USING GIN(tags);
CREATE INDEX idx_research_findings_project ON research_findings(project_id);
CREATE INDEX idx_research_findings_type ON research_findings(finding_type);
CREATE INDEX idx_research_findings_status ON research_findings(status);
CREATE INDEX idx_research_citations_source ON research_citations(source_id);
CREATE INDEX idx_research_citations_style ON research_citations(citation_style);
CREATE INDEX idx_research_collaborators_project ON research_collaborators(project_id);
CREATE INDEX idx_research_collaborators_user ON research_collaborators(user_id);
CREATE INDEX idx_research_exports_project ON research_exports(project_id);
CREATE INDEX idx_research_exports_status ON research_exports(status);

View file

@ -0,0 +1,13 @@
DROP INDEX IF EXISTS idx_dashboard_filters_dashboard;
DROP INDEX IF EXISTS idx_dashboard_data_sources_dashboard;
DROP INDEX IF EXISTS idx_dashboard_data_sources_org_bot;
DROP INDEX IF EXISTS idx_dashboard_widgets_dashboard;
DROP INDEX IF EXISTS idx_dashboards_template;
DROP INDEX IF EXISTS idx_dashboards_public;
DROP INDEX IF EXISTS idx_dashboards_owner;
DROP INDEX IF EXISTS idx_dashboards_org_bot;
DROP TABLE IF EXISTS dashboard_filters;
DROP TABLE IF EXISTS dashboard_widgets;
DROP TABLE IF EXISTS dashboard_data_sources;
DROP TABLE IF EXISTS dashboards;

View file

@ -0,0 +1,100 @@
CREATE TABLE dashboards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
owner_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
layout JSONB NOT NULL DEFAULT '{"columns": 12, "row_height": 80, "gap": 16}',
refresh_interval INTEGER,
is_public BOOLEAN NOT NULL DEFAULT FALSE,
is_template BOOLEAN NOT NULL DEFAULT FALSE,
tags TEXT[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE dashboard_widgets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dashboard_id UUID NOT NULL REFERENCES dashboards(id) ON DELETE CASCADE,
widget_type VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
position_x INTEGER NOT NULL DEFAULT 0,
position_y INTEGER NOT NULL DEFAULT 0,
width INTEGER NOT NULL DEFAULT 4,
height INTEGER NOT NULL DEFAULT 3,
config JSONB NOT NULL DEFAULT '{}',
data_query JSONB,
style JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE dashboard_data_sources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
source_type VARCHAR(50) NOT NULL,
connection JSONB NOT NULL DEFAULT '{}',
schema_definition JSONB NOT NULL DEFAULT '{}',
refresh_schedule VARCHAR(100),
last_sync TIMESTAMPTZ,
status VARCHAR(50) NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE dashboard_filters (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dashboard_id UUID NOT NULL REFERENCES dashboards(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
field VARCHAR(255) NOT NULL,
filter_type VARCHAR(50) NOT NULL,
default_value JSONB,
options JSONB NOT NULL DEFAULT '[]',
linked_widgets JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE dashboard_widget_data_sources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
widget_id UUID NOT NULL REFERENCES dashboard_widgets(id) ON DELETE CASCADE,
data_source_id UUID NOT NULL REFERENCES dashboard_data_sources(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(widget_id, data_source_id)
);
CREATE TABLE conversational_queries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
dashboard_id UUID REFERENCES dashboards(id) ON DELETE SET NULL,
user_id UUID NOT NULL,
natural_language TEXT NOT NULL,
generated_query TEXT,
result_widget_config JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_dashboards_org_bot ON dashboards(org_id, bot_id);
CREATE INDEX idx_dashboards_owner ON dashboards(owner_id);
CREATE INDEX idx_dashboards_is_public ON dashboards(is_public) WHERE is_public = TRUE;
CREATE INDEX idx_dashboards_is_template ON dashboards(is_template) WHERE is_template = TRUE;
CREATE INDEX idx_dashboards_tags ON dashboards USING GIN(tags);
CREATE INDEX idx_dashboards_created ON dashboards(created_at DESC);
CREATE INDEX idx_dashboard_widgets_dashboard ON dashboard_widgets(dashboard_id);
CREATE INDEX idx_dashboard_widgets_type ON dashboard_widgets(widget_type);
CREATE INDEX idx_dashboard_data_sources_org_bot ON dashboard_data_sources(org_id, bot_id);
CREATE INDEX idx_dashboard_data_sources_type ON dashboard_data_sources(source_type);
CREATE INDEX idx_dashboard_data_sources_status ON dashboard_data_sources(status);
CREATE INDEX idx_dashboard_filters_dashboard ON dashboard_filters(dashboard_id);
CREATE INDEX idx_conversational_queries_org_bot ON conversational_queries(org_id, bot_id);
CREATE INDEX idx_conversational_queries_dashboard ON conversational_queries(dashboard_id) WHERE dashboard_id IS NOT NULL;
CREATE INDEX idx_conversational_queries_user ON conversational_queries(user_id);
CREATE INDEX idx_conversational_queries_created ON conversational_queries(created_at DESC);

View file

@ -0,0 +1,84 @@
-- Rollback extended product fields
-- Remove variant indexes
DROP INDEX IF EXISTS idx_product_variants_global_trade_number;
-- Remove variant fields
ALTER TABLE product_variants DROP COLUMN IF EXISTS global_trade_number;
ALTER TABLE product_variants DROP COLUMN IF EXISTS net_weight;
ALTER TABLE product_variants DROP COLUMN IF EXISTS gross_weight;
ALTER TABLE product_variants DROP COLUMN IF EXISTS width;
ALTER TABLE product_variants DROP COLUMN IF EXISTS height;
ALTER TABLE product_variants DROP COLUMN IF EXISTS length;
ALTER TABLE product_variants DROP COLUMN IF EXISTS color;
ALTER TABLE product_variants DROP COLUMN IF EXISTS size;
ALTER TABLE product_variants DROP COLUMN IF EXISTS images;
-- Remove product indexes
DROP INDEX IF EXISTS idx_products_tax_code;
DROP INDEX IF EXISTS idx_products_global_trade_number;
DROP INDEX IF EXISTS idx_products_brand;
DROP INDEX IF EXISTS idx_products_slug;
DROP INDEX IF EXISTS idx_products_expiration;
DROP INDEX IF EXISTS idx_products_external_id;
-- Remove SEO and search
ALTER TABLE products DROP COLUMN IF EXISTS slug;
ALTER TABLE products DROP COLUMN IF EXISTS meta_title;
ALTER TABLE products DROP COLUMN IF EXISTS meta_description;
ALTER TABLE products DROP COLUMN IF EXISTS tags;
-- Remove payment gateway integration
ALTER TABLE products DROP COLUMN IF EXISTS external_id;
ALTER TABLE products DROP COLUMN IF EXISTS external_category_id;
ALTER TABLE products DROP COLUMN IF EXISTS external_metadata;
-- Remove detailed pricing
ALTER TABLE products DROP COLUMN IF EXISTS sale_price;
ALTER TABLE products DROP COLUMN IF EXISTS sale_start;
ALTER TABLE products DROP COLUMN IF EXISTS sale_end;
ALTER TABLE products DROP COLUMN IF EXISTS shipping_cost;
ALTER TABLE products DROP COLUMN IF EXISTS profit_margin;
-- Remove advanced inventory control
ALTER TABLE products DROP COLUMN IF EXISTS warehouse_location;
ALTER TABLE products DROP COLUMN IF EXISTS batch_number;
ALTER TABLE products DROP COLUMN IF EXISTS expiration_date;
ALTER TABLE products DROP COLUMN IF EXISTS manufacture_date;
ALTER TABLE products DROP COLUMN IF EXISTS min_stock;
ALTER TABLE products DROP COLUMN IF EXISTS max_stock;
ALTER TABLE products DROP COLUMN IF EXISTS reorder_point;
-- Remove marketplace and e-commerce fields
ALTER TABLE products DROP COLUMN IF EXISTS brand;
ALTER TABLE products DROP COLUMN IF EXISTS model;
ALTER TABLE products DROP COLUMN IF EXISTS color;
ALTER TABLE products DROP COLUMN IF EXISTS size;
ALTER TABLE products DROP COLUMN IF EXISTS material;
ALTER TABLE products DROP COLUMN IF EXISTS gender;
-- Remove tax rates by type
ALTER TABLE products DROP COLUMN IF EXISTS sales_tax_code;
ALTER TABLE products DROP COLUMN IF EXISTS sales_tax_rate;
ALTER TABLE products DROP COLUMN IF EXISTS excise_tax_code;
ALTER TABLE products DROP COLUMN IF EXISTS excise_tax_rate;
ALTER TABLE products DROP COLUMN IF EXISTS vat_code;
ALTER TABLE products DROP COLUMN IF EXISTS vat_rate;
ALTER TABLE products DROP COLUMN IF EXISTS service_tax_code;
ALTER TABLE products DROP COLUMN IF EXISTS service_tax_rate;
-- Remove detailed dimensions
ALTER TABLE products DROP COLUMN IF EXISTS net_weight;
ALTER TABLE products DROP COLUMN IF EXISTS gross_weight;
ALTER TABLE products DROP COLUMN IF EXISTS width;
ALTER TABLE products DROP COLUMN IF EXISTS height;
ALTER TABLE products DROP COLUMN IF EXISTS length;
ALTER TABLE products DROP COLUMN IF EXISTS package_count;
-- Remove tax and fiscal identification fields
ALTER TABLE products DROP COLUMN IF EXISTS tax_code;
ALTER TABLE products DROP COLUMN IF EXISTS tax_class;
ALTER TABLE products DROP COLUMN IF EXISTS fiscal_code;
ALTER TABLE products DROP COLUMN IF EXISTS origin_code;
ALTER TABLE products DROP COLUMN IF EXISTS global_trade_number;
ALTER TABLE products DROP COLUMN IF EXISTS tax_unit_code;

View file

@ -0,0 +1,83 @@
-- Extended product fields for e-commerce and payment integrations
-- Tax and fiscal identification fields
ALTER TABLE products ADD COLUMN IF NOT EXISTS tax_code VARCHAR(10);
ALTER TABLE products ADD COLUMN IF NOT EXISTS tax_class VARCHAR(50);
ALTER TABLE products ADD COLUMN IF NOT EXISTS fiscal_code VARCHAR(10);
ALTER TABLE products ADD COLUMN IF NOT EXISTS origin_code INTEGER DEFAULT 0;
ALTER TABLE products ADD COLUMN IF NOT EXISTS global_trade_number VARCHAR(14);
ALTER TABLE products ADD COLUMN IF NOT EXISTS tax_unit_code VARCHAR(14);
-- Detailed dimensions (for shipping calculation)
ALTER TABLE products ADD COLUMN IF NOT EXISTS net_weight DECIMAL(10,3);
ALTER TABLE products ADD COLUMN IF NOT EXISTS gross_weight DECIMAL(10,3);
ALTER TABLE products ADD COLUMN IF NOT EXISTS width DECIMAL(10,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS height DECIMAL(10,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS length DECIMAL(10,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS package_count INTEGER DEFAULT 1;
-- Tax rates by type
ALTER TABLE products ADD COLUMN IF NOT EXISTS sales_tax_code VARCHAR(3);
ALTER TABLE products ADD COLUMN IF NOT EXISTS sales_tax_rate DECIMAL(5,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS excise_tax_code VARCHAR(2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS excise_tax_rate DECIMAL(5,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS vat_code VARCHAR(2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS vat_rate DECIMAL(5,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS service_tax_code VARCHAR(2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS service_tax_rate DECIMAL(5,2);
-- Marketplace and e-commerce fields
ALTER TABLE products ADD COLUMN IF NOT EXISTS brand VARCHAR(100);
ALTER TABLE products ADD COLUMN IF NOT EXISTS model VARCHAR(100);
ALTER TABLE products ADD COLUMN IF NOT EXISTS color VARCHAR(50);
ALTER TABLE products ADD COLUMN IF NOT EXISTS size VARCHAR(20);
ALTER TABLE products ADD COLUMN IF NOT EXISTS material VARCHAR(100);
ALTER TABLE products ADD COLUMN IF NOT EXISTS gender VARCHAR(20);
-- Advanced inventory control
ALTER TABLE products ADD COLUMN IF NOT EXISTS warehouse_location VARCHAR(100);
ALTER TABLE products ADD COLUMN IF NOT EXISTS batch_number VARCHAR(50);
ALTER TABLE products ADD COLUMN IF NOT EXISTS expiration_date DATE;
ALTER TABLE products ADD COLUMN IF NOT EXISTS manufacture_date DATE;
ALTER TABLE products ADD COLUMN IF NOT EXISTS min_stock INTEGER DEFAULT 0;
ALTER TABLE products ADD COLUMN IF NOT EXISTS max_stock INTEGER;
ALTER TABLE products ADD COLUMN IF NOT EXISTS reorder_point INTEGER;
-- Detailed pricing
ALTER TABLE products ADD COLUMN IF NOT EXISTS sale_price DECIMAL(15,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS sale_start TIMESTAMPTZ;
ALTER TABLE products ADD COLUMN IF NOT EXISTS sale_end TIMESTAMPTZ;
ALTER TABLE products ADD COLUMN IF NOT EXISTS shipping_cost DECIMAL(15,2);
ALTER TABLE products ADD COLUMN IF NOT EXISTS profit_margin DECIMAL(5,2);
-- Payment gateway integration
ALTER TABLE products ADD COLUMN IF NOT EXISTS external_id VARCHAR(100);
ALTER TABLE products ADD COLUMN IF NOT EXISTS external_category_id VARCHAR(100);
ALTER TABLE products ADD COLUMN IF NOT EXISTS external_metadata JSONB DEFAULT '{}';
-- SEO and search
ALTER TABLE products ADD COLUMN IF NOT EXISTS slug VARCHAR(255);
ALTER TABLE products ADD COLUMN IF NOT EXISTS meta_title VARCHAR(255);
ALTER TABLE products ADD COLUMN IF NOT EXISTS meta_description TEXT;
ALTER TABLE products ADD COLUMN IF NOT EXISTS tags TEXT[];
-- Indexes for new fields
CREATE INDEX IF NOT EXISTS idx_products_tax_code ON products(tax_code);
CREATE INDEX IF NOT EXISTS idx_products_global_trade_number ON products(global_trade_number);
CREATE INDEX IF NOT EXISTS idx_products_brand ON products(brand);
CREATE INDEX IF NOT EXISTS idx_products_slug ON products(slug);
CREATE INDEX IF NOT EXISTS idx_products_expiration ON products(expiration_date);
CREATE INDEX IF NOT EXISTS idx_products_external_id ON products(external_id);
-- Add similar fields to product variants
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS global_trade_number VARCHAR(14);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS net_weight DECIMAL(10,3);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS gross_weight DECIMAL(10,3);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS width DECIMAL(10,2);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS height DECIMAL(10,2);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS length DECIMAL(10,2);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS color VARCHAR(50);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS size VARCHAR(20);
ALTER TABLE product_variants ADD COLUMN IF NOT EXISTS images JSONB DEFAULT '[]';
CREATE INDEX IF NOT EXISTS idx_product_variants_global_trade_number ON product_variants(global_trade_number);

View file

@ -0,0 +1,12 @@
DROP INDEX IF EXISTS idx_consent_history_consent;
DROP INDEX IF EXISTS idx_consent_history_created;
DROP INDEX IF EXISTS idx_cookie_consents_user;
DROP INDEX IF EXISTS idx_cookie_consents_session;
DROP INDEX IF EXISTS idx_cookie_consents_org_bot;
DROP INDEX IF EXISTS idx_legal_documents_org_bot;
DROP INDEX IF EXISTS idx_legal_documents_slug;
DROP INDEX IF EXISTS idx_legal_documents_type;
DROP TABLE IF EXISTS consent_history;
DROP TABLE IF EXISTS cookie_consents;
DROP TABLE IF EXISTS legal_documents;

View file

@ -0,0 +1,140 @@
CREATE TABLE legal_documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
slug VARCHAR(100) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
document_type VARCHAR(50) NOT NULL,
version VARCHAR(50) NOT NULL DEFAULT '1.0.0',
effective_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
requires_acceptance BOOLEAN NOT NULL DEFAULT FALSE,
metadata JSONB NOT NULL DEFAULT '{}',
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(org_id, bot_id, slug, version)
);
CREATE TABLE legal_document_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
document_id UUID NOT NULL REFERENCES legal_documents(id) ON DELETE CASCADE,
version VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
change_summary TEXT,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE cookie_consents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
user_id UUID,
session_id VARCHAR(255),
ip_address VARCHAR(45),
user_agent TEXT,
country_code VARCHAR(2),
consent_necessary BOOLEAN NOT NULL DEFAULT TRUE,
consent_analytics BOOLEAN NOT NULL DEFAULT FALSE,
consent_marketing BOOLEAN NOT NULL DEFAULT FALSE,
consent_preferences BOOLEAN NOT NULL DEFAULT FALSE,
consent_functional BOOLEAN NOT NULL DEFAULT FALSE,
consent_version VARCHAR(50) NOT NULL,
consent_given_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
consent_updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
consent_withdrawn_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE consent_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
consent_id UUID NOT NULL REFERENCES cookie_consents(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL,
previous_consents JSONB NOT NULL DEFAULT '{}',
new_consents JSONB NOT NULL DEFAULT '{}',
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE legal_acceptances (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
user_id UUID NOT NULL,
document_id UUID NOT NULL REFERENCES legal_documents(id) ON DELETE CASCADE,
document_version VARCHAR(50) NOT NULL,
accepted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ip_address VARCHAR(45),
user_agent TEXT,
UNIQUE(user_id, document_id, document_version)
);
CREATE TABLE data_deletion_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
user_id UUID NOT NULL,
request_type VARCHAR(50) NOT NULL DEFAULT 'full',
status VARCHAR(50) NOT NULL DEFAULT 'pending',
reason TEXT,
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
scheduled_for TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
confirmation_token VARCHAR(255) NOT NULL,
confirmed_at TIMESTAMPTZ,
processed_by UUID,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE data_export_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
user_id UUID NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
format VARCHAR(50) NOT NULL DEFAULT 'json',
include_sections JSONB NOT NULL DEFAULT '["all"]',
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
file_url TEXT,
file_size INTEGER,
expires_at TIMESTAMPTZ,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_legal_documents_org_bot ON legal_documents(org_id, bot_id);
CREATE INDEX idx_legal_documents_slug ON legal_documents(org_id, bot_id, slug);
CREATE INDEX idx_legal_documents_type ON legal_documents(document_type);
CREATE INDEX idx_legal_documents_active ON legal_documents(is_active) WHERE is_active = TRUE;
CREATE INDEX idx_legal_document_versions_document ON legal_document_versions(document_id);
CREATE INDEX idx_legal_document_versions_version ON legal_document_versions(document_id, version);
CREATE INDEX idx_cookie_consents_org_bot ON cookie_consents(org_id, bot_id);
CREATE INDEX idx_cookie_consents_user ON cookie_consents(user_id) WHERE user_id IS NOT NULL;
CREATE INDEX idx_cookie_consents_session ON cookie_consents(session_id) WHERE session_id IS NOT NULL;
CREATE INDEX idx_cookie_consents_given ON cookie_consents(consent_given_at DESC);
CREATE INDEX idx_consent_history_consent ON consent_history(consent_id);
CREATE INDEX idx_consent_history_action ON consent_history(action);
CREATE INDEX idx_consent_history_created ON consent_history(created_at DESC);
CREATE INDEX idx_legal_acceptances_org_bot ON legal_acceptances(org_id, bot_id);
CREATE INDEX idx_legal_acceptances_user ON legal_acceptances(user_id);
CREATE INDEX idx_legal_acceptances_document ON legal_acceptances(document_id);
CREATE INDEX idx_data_deletion_requests_org_bot ON data_deletion_requests(org_id, bot_id);
CREATE INDEX idx_data_deletion_requests_user ON data_deletion_requests(user_id);
CREATE INDEX idx_data_deletion_requests_status ON data_deletion_requests(status);
CREATE INDEX idx_data_deletion_requests_token ON data_deletion_requests(confirmation_token);
CREATE INDEX idx_data_export_requests_org_bot ON data_export_requests(org_id, bot_id);
CREATE INDEX idx_data_export_requests_user ON data_export_requests(user_id);
CREATE INDEX idx_data_export_requests_status ON data_export_requests(status);

View file

@ -0,0 +1,18 @@
DROP INDEX IF EXISTS idx_compliance_evidence_check;
DROP INDEX IF EXISTS idx_compliance_evidence_org_bot;
DROP INDEX IF EXISTS idx_audit_log_created;
DROP INDEX IF EXISTS idx_audit_log_resource;
DROP INDEX IF EXISTS idx_audit_log_user;
DROP INDEX IF EXISTS idx_audit_log_org_bot;
DROP INDEX IF EXISTS idx_compliance_issues_status;
DROP INDEX IF EXISTS idx_compliance_issues_severity;
DROP INDEX IF EXISTS idx_compliance_issues_check;
DROP INDEX IF EXISTS idx_compliance_issues_org_bot;
DROP INDEX IF EXISTS idx_compliance_checks_status;
DROP INDEX IF EXISTS idx_compliance_checks_framework;
DROP INDEX IF EXISTS idx_compliance_checks_org_bot;
DROP TABLE IF EXISTS compliance_evidence;
DROP TABLE IF EXISTS audit_log;
DROP TABLE IF EXISTS compliance_issues;
DROP TABLE IF EXISTS compliance_checks;

View file

@ -0,0 +1,182 @@
CREATE TABLE compliance_checks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
framework VARCHAR(50) NOT NULL,
control_id VARCHAR(100) NOT NULL,
control_name VARCHAR(500) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
score NUMERIC(5,2) NOT NULL DEFAULT 0,
checked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
checked_by UUID,
evidence JSONB NOT NULL DEFAULT '[]',
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE compliance_issues (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
check_id UUID REFERENCES compliance_checks(id) ON DELETE CASCADE,
severity VARCHAR(50) NOT NULL DEFAULT 'medium',
title VARCHAR(500) NOT NULL,
description TEXT NOT NULL,
remediation TEXT,
due_date TIMESTAMPTZ,
assigned_to UUID,
status VARCHAR(50) NOT NULL DEFAULT 'open',
resolved_at TIMESTAMPTZ,
resolved_by UUID,
resolution_notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE compliance_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
event_type VARCHAR(100) NOT NULL,
user_id UUID,
resource_type VARCHAR(100) NOT NULL,
resource_id VARCHAR(255) NOT NULL,
action VARCHAR(100) NOT NULL,
result VARCHAR(50) NOT NULL DEFAULT 'success',
ip_address VARCHAR(45),
user_agent TEXT,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE compliance_evidence (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
check_id UUID REFERENCES compliance_checks(id) ON DELETE CASCADE,
issue_id UUID REFERENCES compliance_issues(id) ON DELETE CASCADE,
evidence_type VARCHAR(100) NOT NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
file_url TEXT,
file_name VARCHAR(255),
file_size INTEGER,
mime_type VARCHAR(100),
metadata JSONB NOT NULL DEFAULT '{}',
collected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
collected_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE compliance_risk_assessments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
title VARCHAR(500) NOT NULL,
assessor_id UUID NOT NULL,
methodology VARCHAR(100) NOT NULL DEFAULT 'qualitative',
overall_risk_score NUMERIC(5,2) NOT NULL DEFAULT 0,
status VARCHAR(50) NOT NULL DEFAULT 'draft',
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
next_review_date DATE,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE compliance_risks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
assessment_id UUID NOT NULL REFERENCES compliance_risk_assessments(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
description TEXT,
category VARCHAR(100) NOT NULL DEFAULT 'operational',
likelihood_score INTEGER NOT NULL DEFAULT 1 CHECK (likelihood_score BETWEEN 1 AND 5),
impact_score INTEGER NOT NULL DEFAULT 1 CHECK (impact_score BETWEEN 1 AND 5),
risk_score INTEGER NOT NULL DEFAULT 1,
risk_level VARCHAR(50) NOT NULL DEFAULT 'low',
current_controls JSONB NOT NULL DEFAULT '[]',
treatment_strategy VARCHAR(50) NOT NULL DEFAULT 'mitigate',
status VARCHAR(50) NOT NULL DEFAULT 'open',
owner_id UUID,
due_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE compliance_training_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
user_id UUID NOT NULL,
training_type VARCHAR(100) NOT NULL,
training_name VARCHAR(500) NOT NULL,
provider VARCHAR(255),
score INTEGER,
passed BOOLEAN NOT NULL DEFAULT FALSE,
completion_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ,
certificate_url TEXT,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE compliance_access_reviews (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
user_id UUID NOT NULL,
reviewer_id UUID NOT NULL,
review_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
permissions_reviewed JSONB NOT NULL DEFAULT '[]',
anomalies JSONB NOT NULL DEFAULT '[]',
recommendations JSONB NOT NULL DEFAULT '[]',
status VARCHAR(50) NOT NULL DEFAULT 'pending',
approved_at TIMESTAMPTZ,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_compliance_checks_org_bot ON compliance_checks(org_id, bot_id);
CREATE INDEX idx_compliance_checks_framework ON compliance_checks(framework);
CREATE INDEX idx_compliance_checks_status ON compliance_checks(status);
CREATE INDEX idx_compliance_checks_checked_at ON compliance_checks(checked_at DESC);
CREATE INDEX idx_compliance_issues_org_bot ON compliance_issues(org_id, bot_id);
CREATE INDEX idx_compliance_issues_check ON compliance_issues(check_id);
CREATE INDEX idx_compliance_issues_severity ON compliance_issues(severity);
CREATE INDEX idx_compliance_issues_status ON compliance_issues(status);
CREATE INDEX idx_compliance_issues_assigned ON compliance_issues(assigned_to) WHERE assigned_to IS NOT NULL;
CREATE INDEX idx_compliance_issues_due ON compliance_issues(due_date) WHERE due_date IS NOT NULL;
CREATE INDEX idx_compliance_audit_log_org_bot ON compliance_audit_log(org_id, bot_id);
CREATE INDEX idx_compliance_audit_log_event ON compliance_audit_log(event_type);
CREATE INDEX idx_compliance_audit_log_user ON compliance_audit_log(user_id) WHERE user_id IS NOT NULL;
CREATE INDEX idx_compliance_audit_log_resource ON compliance_audit_log(resource_type, resource_id);
CREATE INDEX idx_compliance_audit_log_created ON compliance_audit_log(created_at DESC);
CREATE INDEX idx_compliance_evidence_org_bot ON compliance_evidence(org_id, bot_id);
CREATE INDEX idx_compliance_evidence_check ON compliance_evidence(check_id) WHERE check_id IS NOT NULL;
CREATE INDEX idx_compliance_evidence_issue ON compliance_evidence(issue_id) WHERE issue_id IS NOT NULL;
CREATE INDEX idx_compliance_evidence_type ON compliance_evidence(evidence_type);
CREATE INDEX idx_compliance_risk_assessments_org_bot ON compliance_risk_assessments(org_id, bot_id);
CREATE INDEX idx_compliance_risk_assessments_status ON compliance_risk_assessments(status);
CREATE INDEX idx_compliance_risk_assessments_assessor ON compliance_risk_assessments(assessor_id);
CREATE INDEX idx_compliance_risks_assessment ON compliance_risks(assessment_id);
CREATE INDEX idx_compliance_risks_category ON compliance_risks(category);
CREATE INDEX idx_compliance_risks_level ON compliance_risks(risk_level);
CREATE INDEX idx_compliance_risks_status ON compliance_risks(status);
CREATE INDEX idx_compliance_training_org_bot ON compliance_training_records(org_id, bot_id);
CREATE INDEX idx_compliance_training_user ON compliance_training_records(user_id);
CREATE INDEX idx_compliance_training_type ON compliance_training_records(training_type);
CREATE INDEX idx_compliance_training_valid ON compliance_training_records(valid_until) WHERE valid_until IS NOT NULL;
CREATE INDEX idx_compliance_access_reviews_org_bot ON compliance_access_reviews(org_id, bot_id);
CREATE INDEX idx_compliance_access_reviews_user ON compliance_access_reviews(user_id);
CREATE INDEX idx_compliance_access_reviews_reviewer ON compliance_access_reviews(reviewer_id);
CREATE INDEX idx_compliance_access_reviews_status ON compliance_access_reviews(status);

View file

@ -0,0 +1,11 @@
-- Drop Grace Period Status table
DROP TABLE IF EXISTS billing_grace_periods;
-- Drop Billing Notification Preferences table
DROP TABLE IF EXISTS billing_notification_preferences;
-- Drop Billing Alert History table
DROP TABLE IF EXISTS billing_alert_history;
-- Drop Billing Usage Alerts table
DROP TABLE IF EXISTS billing_usage_alerts;

View file

@ -0,0 +1,95 @@
-- Billing Usage Alerts table
CREATE TABLE IF NOT EXISTS billing_usage_alerts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
metric VARCHAR(50) NOT NULL,
severity VARCHAR(20) NOT NULL,
current_usage BIGINT NOT NULL,
usage_limit BIGINT NOT NULL,
percentage DECIMAL(5,2) NOT NULL,
threshold DECIMAL(5,2) NOT NULL,
message TEXT NOT NULL,
acknowledged_at TIMESTAMPTZ,
acknowledged_by UUID,
notification_sent BOOLEAN NOT NULL DEFAULT FALSE,
notification_channels JSONB NOT NULL DEFAULT '[]'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_billing_usage_alerts_org_id ON billing_usage_alerts(org_id);
CREATE INDEX idx_billing_usage_alerts_bot_id ON billing_usage_alerts(bot_id);
CREATE INDEX idx_billing_usage_alerts_severity ON billing_usage_alerts(severity);
CREATE INDEX idx_billing_usage_alerts_created_at ON billing_usage_alerts(created_at);
CREATE INDEX idx_billing_usage_alerts_acknowledged ON billing_usage_alerts(acknowledged_at);
-- Billing Alert History table
CREATE TABLE IF NOT EXISTS billing_alert_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
alert_id UUID NOT NULL,
metric VARCHAR(50) NOT NULL,
severity VARCHAR(20) NOT NULL,
current_usage BIGINT NOT NULL,
usage_limit BIGINT NOT NULL,
percentage DECIMAL(5,2) NOT NULL,
message TEXT NOT NULL,
acknowledged_at TIMESTAMPTZ,
acknowledged_by UUID,
resolved_at TIMESTAMPTZ,
resolution_type VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_billing_alert_history_org_id ON billing_alert_history(org_id);
CREATE INDEX idx_billing_alert_history_created_at ON billing_alert_history(created_at);
-- Billing Notification Preferences table
CREATE TABLE IF NOT EXISTS billing_notification_preferences (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL UNIQUE,
bot_id UUID NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
channels JSONB NOT NULL DEFAULT '["email", "in_app"]'::jsonb,
email_recipients JSONB NOT NULL DEFAULT '[]'::jsonb,
webhook_url TEXT,
webhook_secret TEXT,
slack_webhook_url TEXT,
teams_webhook_url TEXT,
sms_numbers JSONB NOT NULL DEFAULT '[]'::jsonb,
min_severity VARCHAR(20) NOT NULL DEFAULT 'warning',
quiet_hours_start INTEGER,
quiet_hours_end INTEGER,
quiet_hours_timezone VARCHAR(50),
quiet_hours_days JSONB DEFAULT '[]'::jsonb,
metric_overrides JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_billing_notification_preferences_org_id ON billing_notification_preferences(org_id);
-- Grace Period Status table
CREATE TABLE IF NOT EXISTS billing_grace_periods (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
metric VARCHAR(50) NOT NULL,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
overage_at_start DECIMAL(10,2) NOT NULL,
current_overage DECIMAL(10,2) NOT NULL,
max_allowed_overage DECIMAL(10,2) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
ended_at TIMESTAMPTZ,
end_reason VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(org_id, metric, is_active)
);
CREATE INDEX idx_billing_grace_periods_org_id ON billing_grace_periods(org_id);
CREATE INDEX idx_billing_grace_periods_active ON billing_grace_periods(is_active);
CREATE INDEX idx_billing_grace_periods_expires ON billing_grace_periods(expires_at);

View file

@ -0,0 +1,26 @@
-- Drop Scheduled Meetings table
DROP TABLE IF EXISTS scheduled_meetings;
-- Drop Meeting Chat Messages table
DROP TABLE IF EXISTS meeting_chat_messages;
-- Drop Whiteboard Export History table
DROP TABLE IF EXISTS whiteboard_exports;
-- Drop Whiteboard Elements table
DROP TABLE IF EXISTS whiteboard_elements;
-- Drop Meeting Whiteboards table
DROP TABLE IF EXISTS meeting_whiteboards;
-- Drop Meeting Transcriptions table
DROP TABLE IF EXISTS meeting_transcriptions;
-- Drop Meeting Recordings table
DROP TABLE IF EXISTS meeting_recordings;
-- Drop Meeting Participants table
DROP TABLE IF EXISTS meeting_participants;
-- Drop Meeting Rooms table
DROP TABLE IF EXISTS meeting_rooms;

View file

@ -0,0 +1,200 @@
-- Meeting Rooms table
CREATE TABLE IF NOT EXISTS meeting_rooms (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
room_code VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT,
created_by UUID NOT NULL,
max_participants INTEGER NOT NULL DEFAULT 100,
is_recording BOOLEAN NOT NULL DEFAULT FALSE,
is_transcribing BOOLEAN NOT NULL DEFAULT FALSE,
status VARCHAR(20) NOT NULL DEFAULT 'waiting',
settings JSONB NOT NULL DEFAULT '{}'::jsonb,
started_at TIMESTAMPTZ,
ended_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_meeting_rooms_org_id ON meeting_rooms(org_id);
CREATE INDEX idx_meeting_rooms_bot_id ON meeting_rooms(bot_id);
CREATE INDEX idx_meeting_rooms_room_code ON meeting_rooms(room_code);
CREATE INDEX idx_meeting_rooms_status ON meeting_rooms(status);
CREATE INDEX idx_meeting_rooms_created_by ON meeting_rooms(created_by);
CREATE INDEX idx_meeting_rooms_created_at ON meeting_rooms(created_at);
-- Meeting Participants table
CREATE TABLE IF NOT EXISTS meeting_participants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
room_id UUID NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
user_id UUID,
participant_name VARCHAR(255) NOT NULL,
email VARCHAR(255),
role VARCHAR(20) NOT NULL DEFAULT 'participant',
is_bot BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
has_video BOOLEAN NOT NULL DEFAULT FALSE,
has_audio BOOLEAN NOT NULL DEFAULT FALSE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
left_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_meeting_participants_room_id ON meeting_participants(room_id);
CREATE INDEX idx_meeting_participants_user_id ON meeting_participants(user_id);
CREATE INDEX idx_meeting_participants_active ON meeting_participants(is_active) WHERE is_active = TRUE;
-- Meeting Recordings table
CREATE TABLE IF NOT EXISTS meeting_recordings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
room_id UUID NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
recording_type VARCHAR(20) NOT NULL DEFAULT 'video',
file_url TEXT,
file_size BIGINT,
duration_seconds INTEGER,
status VARCHAR(20) NOT NULL DEFAULT 'recording',
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
stopped_at TIMESTAMPTZ,
processed_at TIMESTAMPTZ,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_meeting_recordings_room_id ON meeting_recordings(room_id);
CREATE INDEX idx_meeting_recordings_org_id ON meeting_recordings(org_id);
CREATE INDEX idx_meeting_recordings_status ON meeting_recordings(status);
-- Meeting Transcriptions table
CREATE TABLE IF NOT EXISTS meeting_transcriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
room_id UUID NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
recording_id UUID REFERENCES meeting_recordings(id) ON DELETE SET NULL,
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
participant_id UUID REFERENCES meeting_participants(id) ON DELETE SET NULL,
speaker_name VARCHAR(255),
content TEXT NOT NULL,
start_time DECIMAL(10,3) NOT NULL,
end_time DECIMAL(10,3) NOT NULL,
confidence DECIMAL(5,4),
language VARCHAR(10) DEFAULT 'en',
is_final BOOLEAN NOT NULL DEFAULT TRUE,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_meeting_transcriptions_room_id ON meeting_transcriptions(room_id);
CREATE INDEX idx_meeting_transcriptions_recording_id ON meeting_transcriptions(recording_id);
CREATE INDEX idx_meeting_transcriptions_participant_id ON meeting_transcriptions(participant_id);
CREATE INDEX idx_meeting_transcriptions_created_at ON meeting_transcriptions(created_at);
-- Meeting Whiteboards table
CREATE TABLE IF NOT EXISTS meeting_whiteboards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
room_id UUID REFERENCES meeting_rooms(id) ON DELETE SET NULL,
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
background_color VARCHAR(20) DEFAULT '#ffffff',
grid_enabled BOOLEAN NOT NULL DEFAULT TRUE,
grid_size INTEGER DEFAULT 20,
elements JSONB NOT NULL DEFAULT '[]'::jsonb,
version INTEGER NOT NULL DEFAULT 1,
created_by UUID NOT NULL,
last_modified_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_meeting_whiteboards_room_id ON meeting_whiteboards(room_id);
CREATE INDEX idx_meeting_whiteboards_org_id ON meeting_whiteboards(org_id);
CREATE INDEX idx_meeting_whiteboards_created_by ON meeting_whiteboards(created_by);
-- Whiteboard Elements table (for granular element storage)
CREATE TABLE IF NOT EXISTS whiteboard_elements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
whiteboard_id UUID NOT NULL REFERENCES meeting_whiteboards(id) ON DELETE CASCADE,
element_type VARCHAR(50) NOT NULL,
position_x DECIMAL(10,2) NOT NULL,
position_y DECIMAL(10,2) NOT NULL,
width DECIMAL(10,2),
height DECIMAL(10,2),
rotation DECIMAL(5,2) DEFAULT 0,
z_index INTEGER NOT NULL DEFAULT 0,
properties JSONB NOT NULL DEFAULT '{}'::jsonb,
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_whiteboard_elements_whiteboard_id ON whiteboard_elements(whiteboard_id);
CREATE INDEX idx_whiteboard_elements_type ON whiteboard_elements(element_type);
-- Whiteboard Export History table
CREATE TABLE IF NOT EXISTS whiteboard_exports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
whiteboard_id UUID NOT NULL REFERENCES meeting_whiteboards(id) ON DELETE CASCADE,
org_id UUID NOT NULL,
export_format VARCHAR(20) NOT NULL,
file_url TEXT,
file_size BIGINT,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
error_message TEXT,
requested_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
CREATE INDEX idx_whiteboard_exports_whiteboard_id ON whiteboard_exports(whiteboard_id);
CREATE INDEX idx_whiteboard_exports_org_id ON whiteboard_exports(org_id);
CREATE INDEX idx_whiteboard_exports_status ON whiteboard_exports(status);
-- Meeting Chat Messages table
CREATE TABLE IF NOT EXISTS meeting_chat_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
room_id UUID NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
participant_id UUID REFERENCES meeting_participants(id) ON DELETE SET NULL,
sender_name VARCHAR(255) NOT NULL,
message_type VARCHAR(20) NOT NULL DEFAULT 'text',
content TEXT NOT NULL,
reply_to_id UUID REFERENCES meeting_chat_messages(id) ON DELETE SET NULL,
is_system_message BOOLEAN NOT NULL DEFAULT FALSE,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_meeting_chat_messages_room_id ON meeting_chat_messages(room_id);
CREATE INDEX idx_meeting_chat_messages_participant_id ON meeting_chat_messages(participant_id);
CREATE INDEX idx_meeting_chat_messages_created_at ON meeting_chat_messages(created_at);
-- Scheduled Meetings table
CREATE TABLE IF NOT EXISTS scheduled_meetings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
room_id UUID REFERENCES meeting_rooms(id) ON DELETE SET NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
organizer_id UUID NOT NULL,
scheduled_start TIMESTAMPTZ NOT NULL,
scheduled_end TIMESTAMPTZ NOT NULL,
timezone VARCHAR(50) NOT NULL DEFAULT 'UTC',
recurrence_rule TEXT,
attendees JSONB NOT NULL DEFAULT '[]'::jsonb,
settings JSONB NOT NULL DEFAULT '{}'::jsonb,
status VARCHAR(20) NOT NULL DEFAULT 'scheduled',
reminder_sent BOOLEAN NOT NULL DEFAULT FALSE,
calendar_event_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_scheduled_meetings_org_id ON scheduled_meetings(org_id);
CREATE INDEX idx_scheduled_meetings_organizer_id ON scheduled_meetings(organizer_id);
CREATE INDEX idx_scheduled_meetings_scheduled_start ON scheduled_meetings(scheduled_start);
CREATE INDEX idx_scheduled_meetings_status ON scheduled_meetings(status);

View file

@ -0,0 +1,5 @@
-- Down migration: Remove organization invitations table
DROP TRIGGER IF EXISTS trigger_org_invitation_updated_at ON organization_invitations;
DROP FUNCTION IF EXISTS update_org_invitation_updated_at();
DROP TABLE IF EXISTS organization_invitations;

View file

@ -0,0 +1,57 @@
-- Organization Invitations Table
-- Manages user invitations to organizations
CREATE TABLE IF NOT EXISTS organization_invitations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'member',
status VARCHAR(20) NOT NULL DEFAULT 'pending',
message TEXT,
invited_by UUID NOT NULL REFERENCES users(id) ON DELETE SET NULL,
token VARCHAR(255) UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
accepted_at TIMESTAMPTZ,
accepted_by UUID REFERENCES users(id) ON DELETE SET NULL,
-- Constraint to prevent duplicate pending invitations
CONSTRAINT unique_pending_invitation UNIQUE (org_id, email)
);
-- Index for looking up invitations by organization
CREATE INDEX IF NOT EXISTS idx_org_invitations_org_id ON organization_invitations(org_id);
-- Index for looking up invitations by email
CREATE INDEX IF NOT EXISTS idx_org_invitations_email ON organization_invitations(email);
-- Index for looking up pending invitations
CREATE INDEX IF NOT EXISTS idx_org_invitations_status ON organization_invitations(status) WHERE status = 'pending';
-- Index for token lookups (for invitation acceptance)
CREATE INDEX IF NOT EXISTS idx_org_invitations_token ON organization_invitations(token) WHERE token IS NOT NULL;
-- Index for cleanup of expired invitations
CREATE INDEX IF NOT EXISTS idx_org_invitations_expires ON organization_invitations(expires_at) WHERE status = 'pending';
-- Add updated_at trigger
CREATE OR REPLACE FUNCTION update_org_invitation_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_org_invitation_updated_at ON organization_invitations;
CREATE TRIGGER trigger_org_invitation_updated_at
BEFORE UPDATE ON organization_invitations
FOR EACH ROW
EXECUTE FUNCTION update_org_invitation_updated_at();
-- Comments
COMMENT ON TABLE organization_invitations IS 'Stores pending and historical organization invitations';
COMMENT ON COLUMN organization_invitations.status IS 'pending, accepted, cancelled, expired';
COMMENT ON COLUMN organization_invitations.token IS 'Secure token for invitation acceptance via email link';
COMMENT ON COLUMN organization_invitations.role IS 'Role to assign upon acceptance: member, admin, owner, etc.';

View file

@ -0,0 +1,3 @@
-- Remove manifest_json column from auto_tasks table
DROP INDEX IF EXISTS idx_auto_tasks_manifest_json;
ALTER TABLE auto_tasks DROP COLUMN IF EXISTS manifest_json;

View file

@ -0,0 +1,5 @@
-- Add manifest_json column to store the full task manifest for historical viewing
ALTER TABLE auto_tasks ADD COLUMN IF NOT EXISTS manifest_json JSONB;
-- Add an index for faster lookups when manifest exists
CREATE INDEX IF NOT EXISTS idx_auto_tasks_manifest_json ON auto_tasks USING gin (manifest_json) WHERE manifest_json IS NOT NULL;

View file

@ -0,0 +1,25 @@
-- Drop RBAC tables in reverse order of creation
DROP INDEX IF EXISTS idx_rbac_group_roles_role;
DROP INDEX IF EXISTS idx_rbac_group_roles_group;
DROP INDEX IF EXISTS idx_rbac_user_groups_group;
DROP INDEX IF EXISTS idx_rbac_user_groups_user;
DROP INDEX IF EXISTS idx_rbac_user_roles_expires;
DROP INDEX IF EXISTS idx_rbac_user_roles_role;
DROP INDEX IF EXISTS idx_rbac_user_roles_user;
DROP INDEX IF EXISTS idx_rbac_role_permissions_permission;
DROP INDEX IF EXISTS idx_rbac_role_permissions_role;
DROP INDEX IF EXISTS idx_rbac_permissions_resource;
DROP INDEX IF EXISTS idx_rbac_groups_is_active;
DROP INDEX IF EXISTS idx_rbac_groups_parent;
DROP INDEX IF EXISTS idx_rbac_groups_name;
DROP INDEX IF EXISTS idx_rbac_roles_is_active;
DROP INDEX IF EXISTS idx_rbac_roles_name;
DROP TABLE IF EXISTS rbac_group_roles;
DROP TABLE IF EXISTS rbac_user_groups;
DROP TABLE IF EXISTS rbac_user_roles;
DROP TABLE IF EXISTS rbac_role_permissions;
DROP TABLE IF EXISTS rbac_permissions;
DROP TABLE IF EXISTS rbac_groups;
DROP TABLE IF EXISTS rbac_roles;

View file

@ -0,0 +1,705 @@
-- RBAC (Role-Based Access Control) Tables for Enterprise Suite
-- Designed as a free alternative to Office 365 / Google Workspace
-- Comprehensive permissions for all suite applications
-- Roles table: defines available roles in the system
CREATE TABLE IF NOT EXISTS rbac_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
display_name VARCHAR(255) NOT NULL,
description TEXT,
is_system BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Groups table: organizes users into logical groups (like AD Groups / Google Groups)
CREATE TABLE IF NOT EXISTS rbac_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
display_name VARCHAR(255) NOT NULL,
description TEXT,
parent_group_id UUID REFERENCES rbac_groups(id) ON DELETE SET NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Permissions table: defines granular permissions for all suite apps
CREATE TABLE IF NOT EXISTS rbac_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
display_name VARCHAR(255) NOT NULL,
description TEXT,
resource_type VARCHAR(100) NOT NULL,
action VARCHAR(50) NOT NULL,
category VARCHAR(100) NOT NULL DEFAULT 'general',
is_system BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(resource_type, action)
);
-- Role-Permission junction table
CREATE TABLE IF NOT EXISTS rbac_role_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,
permission_id UUID NOT NULL REFERENCES rbac_permissions(id) ON DELETE CASCADE,
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(role_id, permission_id)
);
-- User-Role junction table (direct role assignment)
CREATE TABLE IF NOT EXISTS rbac_user_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ,
UNIQUE(user_id, role_id)
);
-- User-Group junction table
CREATE TABLE IF NOT EXISTS rbac_user_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
group_id UUID NOT NULL REFERENCES rbac_groups(id) ON DELETE CASCADE,
added_by UUID REFERENCES users(id) ON DELETE SET NULL,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, group_id)
);
-- Group-Role junction table (role inheritance through groups)
CREATE TABLE IF NOT EXISTS rbac_group_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES rbac_groups(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(group_id, role_id)
);
-- Performance indexes
CREATE INDEX IF NOT EXISTS idx_rbac_roles_name ON rbac_roles(name);
CREATE INDEX IF NOT EXISTS idx_rbac_roles_is_active ON rbac_roles(is_active);
CREATE INDEX IF NOT EXISTS idx_rbac_groups_name ON rbac_groups(name);
CREATE INDEX IF NOT EXISTS idx_rbac_groups_parent ON rbac_groups(parent_group_id);
CREATE INDEX IF NOT EXISTS idx_rbac_groups_is_active ON rbac_groups(is_active);
CREATE INDEX IF NOT EXISTS idx_rbac_permissions_resource ON rbac_permissions(resource_type);
CREATE INDEX IF NOT EXISTS idx_rbac_permissions_category ON rbac_permissions(category);
CREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_role ON rbac_role_permissions(role_id);
CREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_permission ON rbac_role_permissions(permission_id);
CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_user ON rbac_user_roles(user_id);
CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_role ON rbac_user_roles(role_id);
CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_expires ON rbac_user_roles(expires_at);
CREATE INDEX IF NOT EXISTS idx_rbac_user_groups_user ON rbac_user_groups(user_id);
CREATE INDEX IF NOT EXISTS idx_rbac_user_groups_group ON rbac_user_groups(group_id);
CREATE INDEX IF NOT EXISTS idx_rbac_group_roles_group ON rbac_group_roles(group_id);
CREATE INDEX IF NOT EXISTS idx_rbac_group_roles_role ON rbac_group_roles(role_id);
-- ============================================================================
-- DEFAULT SYSTEM ROLES (Similar to Office 365 / Google Workspace roles)
-- ============================================================================
INSERT INTO rbac_roles (name, display_name, description, is_system) VALUES
-- Global Admin Roles
('global_admin', 'Global Administrator', 'Full control over all organization settings, users, and resources. Similar to Office 365 Global Admin.', TRUE),
('billing_admin', 'Billing Administrator', 'Manages subscriptions, billing, and payment methods.', TRUE),
('compliance_admin', 'Compliance Administrator', 'Manages compliance features, audit logs, and data governance.', TRUE),
('security_admin', 'Security Administrator', 'Manages security settings, threat protection, and access policies.', TRUE),
-- User Management Roles
('user_admin', 'User Administrator', 'Creates and manages users, resets passwords, manages groups.', TRUE),
('groups_admin', 'Groups Administrator', 'Creates and manages all groups in the organization.', TRUE),
('helpdesk_admin', 'Helpdesk Administrator', 'Resets passwords and manages support tickets for non-admin users.', TRUE),
('password_admin', 'Password Administrator', 'Can reset passwords for non-privileged users.', TRUE),
-- Service-Specific Admin Roles
('exchange_admin', 'Mail Administrator', 'Full control over mail settings, mailboxes, and mail flow rules.', TRUE),
('sharepoint_admin', 'Drive Administrator', 'Manages file storage, sharing settings, and site collections.', TRUE),
('teams_admin', 'Meet & Chat Administrator', 'Manages video meetings, chat settings, and collaboration features.', TRUE),
-- Content Roles
('knowledge_admin', 'Knowledge Administrator', 'Manages knowledge bases, document libraries, and search settings.', TRUE),
('reports_reader', 'Reports Reader', 'Can view usage reports, analytics, and dashboards.', TRUE),
-- Standard User Roles
('power_user', 'Power User', 'Advanced user with additional permissions for automation and integrations.', TRUE),
('standard_user', 'Standard User', 'Default role for regular employees with access to productivity apps.', TRUE),
('guest_user', 'Guest User', 'Limited external access for collaboration with partners.', TRUE),
('viewer', 'Viewer', 'Read-only access across applications.', TRUE)
ON CONFLICT (name) DO NOTHING;
-- ============================================================================
-- COMPREHENSIVE PERMISSIONS BY APPLICATION
-- ============================================================================
-- === ADMIN & SYSTEM PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
-- Organization Management
('org.read', 'View Organization', 'View organization settings and information', 'organization', 'read', 'admin', TRUE),
('org.write', 'Manage Organization', 'Modify organization settings', 'organization', 'write', 'admin', TRUE),
('org.delete', 'Delete Organization', 'Delete organization data', 'organization', 'delete', 'admin', TRUE),
('org.billing', 'Manage Billing', 'Access billing and subscription management', 'organization', 'billing', 'admin', TRUE),
-- User Management (like Azure AD / Google Admin)
('users.read', 'View Users', 'View user profiles and directory', 'users', 'read', 'admin', TRUE),
('users.create', 'Create Users', 'Create new user accounts', 'users', 'create', 'admin', TRUE),
('users.write', 'Edit Users', 'Modify user profiles and settings', 'users', 'write', 'admin', TRUE),
('users.delete', 'Delete Users', 'Delete user accounts', 'users', 'delete', 'admin', TRUE),
('users.password_reset', 'Reset Passwords', 'Reset user passwords', 'users', 'password_reset', 'admin', TRUE),
('users.mfa_manage', 'Manage MFA', 'Enable/disable multi-factor authentication', 'users', 'mfa_manage', 'admin', TRUE),
('users.impersonate', 'Impersonate Users', 'Sign in as another user for troubleshooting', 'users', 'impersonate', 'admin', TRUE),
('users.export', 'Export Users', 'Export user data and directory', 'users', 'export', 'admin', TRUE),
('users.import', 'Import Users', 'Bulk import users from CSV/LDAP', 'users', 'import', 'admin', TRUE),
-- Group Management
('groups.read', 'View Groups', 'View groups and memberships', 'groups', 'read', 'admin', TRUE),
('groups.create', 'Create Groups', 'Create new groups', 'groups', 'create', 'admin', TRUE),
('groups.write', 'Edit Groups', 'Modify group settings and membership', 'groups', 'write', 'admin', TRUE),
('groups.delete', 'Delete Groups', 'Delete groups', 'groups', 'delete', 'admin', TRUE),
('groups.manage_members', 'Manage Members', 'Add/remove group members', 'groups', 'manage_members', 'admin', TRUE),
('groups.manage_owners', 'Manage Owners', 'Assign group owners', 'groups', 'manage_owners', 'admin', TRUE),
-- Role & Permission Management
('roles.read', 'View Roles', 'View role definitions', 'roles', 'read', 'admin', TRUE),
('roles.create', 'Create Roles', 'Create custom roles', 'roles', 'create', 'admin', TRUE),
('roles.write', 'Edit Roles', 'Modify role permissions', 'roles', 'write', 'admin', TRUE),
('roles.delete', 'Delete Roles', 'Delete custom roles', 'roles', 'delete', 'admin', TRUE),
('roles.assign', 'Assign Roles', 'Assign roles to users and groups', 'roles', 'assign', 'admin', TRUE),
-- DNS & Domain Management
('dns.read', 'View DNS', 'View DNS records and domain settings', 'dns', 'read', 'admin', TRUE),
('dns.write', 'Manage DNS', 'Add/modify DNS records', 'dns', 'write', 'admin', TRUE),
('domains.verify', 'Verify Domains', 'Verify domain ownership', 'domains', 'verify', 'admin', TRUE),
-- Audit & Compliance
('audit.read', 'View Audit Logs', 'Access audit and activity logs', 'audit', 'read', 'compliance', TRUE),
('audit.export', 'Export Audit Logs', 'Export audit data for compliance', 'audit', 'export', 'compliance', TRUE),
('compliance.read', 'View Compliance', 'View compliance dashboard and reports', 'compliance', 'read', 'compliance', TRUE),
('compliance.write', 'Manage Compliance', 'Configure compliance policies', 'compliance', 'write', 'compliance', TRUE),
('dlp.read', 'View DLP Policies', 'View data loss prevention rules', 'dlp', 'read', 'compliance', TRUE),
('dlp.write', 'Manage DLP', 'Create and modify DLP policies', 'dlp', 'write', 'compliance', TRUE),
('retention.read', 'View Retention', 'View data retention policies', 'retention', 'read', 'compliance', TRUE),
('retention.write', 'Manage Retention', 'Configure retention policies', 'retention', 'write', 'compliance', TRUE),
('ediscovery.access', 'eDiscovery Access', 'Access eDiscovery tools and holds', 'ediscovery', 'access', 'compliance', TRUE),
-- Security
('security.read', 'View Security', 'View security dashboard and alerts', 'security', 'read', 'security', TRUE),
('security.write', 'Manage Security', 'Configure security settings', 'security', 'write', 'security', TRUE),
('threats.read', 'View Threats', 'View threat detection and incidents', 'threats', 'read', 'security', TRUE),
('threats.respond', 'Respond to Threats', 'Take action on security incidents', 'threats', 'respond', 'security', TRUE),
('secrets.read', 'View Secrets', 'View API keys and secrets', 'secrets', 'read', 'security', TRUE),
('secrets.write', 'Manage Secrets', 'Create and rotate secrets', 'secrets', 'write', 'security', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === MAIL PERMISSIONS (Like Outlook / Gmail) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('mail.read', 'Read Mail', 'Read own mailbox and messages', 'mail', 'read', 'mail', TRUE),
('mail.send', 'Send Mail', 'Send emails', 'mail', 'send', 'mail', TRUE),
('mail.delete', 'Delete Mail', 'Delete emails', 'mail', 'delete', 'mail', TRUE),
('mail.organize', 'Organize Mail', 'Create folders, apply labels, set rules', 'mail', 'organize', 'mail', TRUE),
('mail.delegate', 'Mail Delegation', 'Grant mailbox access to others', 'mail', 'delegate', 'mail', TRUE),
('mail.shared_read', 'Read Shared Mailbox', 'Access shared mailboxes', 'mail', 'shared_read', 'mail', TRUE),
('mail.shared_send', 'Send from Shared', 'Send as shared mailbox', 'mail', 'shared_send', 'mail', TRUE),
('mail.admin', 'Mail Admin', 'Administer mail settings globally', 'mail', 'admin', 'mail', TRUE),
('mail.rules_global', 'Global Mail Rules', 'Create organization-wide mail rules', 'mail', 'rules_global', 'mail', TRUE),
('mail.signatures_global', 'Global Signatures', 'Manage organization email signatures', 'mail', 'signatures_global', 'mail', TRUE),
('mail.distribution_lists', 'Distribution Lists', 'Manage distribution lists', 'mail', 'distribution_lists', 'mail', TRUE),
('mail.encryption', 'Mail Encryption', 'Send encrypted messages', 'mail', 'encryption', 'mail', TRUE),
('mail.archive', 'Mail Archive', 'Access mail archive', 'mail', 'archive', 'mail', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === CALENDAR PERMISSIONS (Like Outlook Calendar / Google Calendar) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('calendar.read', 'View Calendar', 'View own calendar and events', 'calendar', 'read', 'calendar', TRUE),
('calendar.write', 'Manage Calendar', 'Create, edit, delete events', 'calendar', 'write', 'calendar', TRUE),
('calendar.share', 'Share Calendar', 'Share calendar with others', 'calendar', 'share', 'calendar', TRUE),
('calendar.delegate', 'Calendar Delegation', 'Allow others to manage calendar', 'calendar', 'delegate', 'calendar', TRUE),
('calendar.free_busy', 'View Free/Busy', 'View availability of others', 'calendar', 'free_busy', 'calendar', TRUE),
('calendar.rooms', 'Book Rooms', 'Reserve meeting rooms and resources', 'calendar', 'rooms', 'calendar', TRUE),
('calendar.rooms_admin', 'Manage Rooms', 'Administer room resources', 'calendar', 'rooms_admin', 'calendar', TRUE),
('calendar.shared_read', 'Read Shared Calendars', 'View shared team calendars', 'calendar', 'shared_read', 'calendar', TRUE),
('calendar.shared_write', 'Edit Shared Calendars', 'Modify shared team calendars', 'calendar', 'shared_write', 'calendar', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === DRIVE PERMISSIONS (Like OneDrive / SharePoint / Google Drive) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('drive.read', 'View Files', 'View own files and folders', 'drive', 'read', 'drive', TRUE),
('drive.write', 'Upload Files', 'Upload and create files', 'drive', 'write', 'drive', TRUE),
('drive.delete', 'Delete Files', 'Delete own files', 'drive', 'delete', 'drive', TRUE),
('drive.share', 'Share Files', 'Share files with others', 'drive', 'share', 'drive', TRUE),
('drive.share_external', 'External Sharing', 'Share files externally', 'drive', 'share_external', 'drive', TRUE),
('drive.download', 'Download Files', 'Download files locally', 'drive', 'download', 'drive', TRUE),
('drive.sync', 'Sync Files', 'Use desktop sync client', 'drive', 'sync', 'drive', TRUE),
('drive.version_history', 'Version History', 'View and restore file versions', 'drive', 'version_history', 'drive', TRUE),
('drive.shared_read', 'Read Shared Drives', 'Access team shared drives', 'drive', 'shared_read', 'drive', TRUE),
('drive.shared_write', 'Write Shared Drives', 'Modify files in shared drives', 'drive', 'shared_write', 'drive', TRUE),
('drive.shared_admin', 'Manage Shared Drives', 'Administer shared drive settings', 'drive', 'shared_admin', 'drive', TRUE),
('drive.trash', 'Manage Trash', 'View and restore deleted items', 'drive', 'trash', 'drive', TRUE),
('drive.quota', 'View Storage Quota', 'View storage usage', 'drive', 'quota', 'drive', TRUE),
('drive.admin', 'Drive Admin', 'Full administrative access to all drives', 'drive', 'admin', 'drive', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === DOCS PERMISSIONS (Like Word Online / Google Docs) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('docs.read', 'View Documents', 'View documents', 'docs', 'read', 'docs', TRUE),
('docs.write', 'Edit Documents', 'Create and edit documents', 'docs', 'write', 'docs', TRUE),
('docs.comment', 'Comment on Documents', 'Add comments and suggestions', 'docs', 'comment', 'docs', TRUE),
('docs.share', 'Share Documents', 'Share documents with others', 'docs', 'share', 'docs', TRUE),
('docs.export', 'Export Documents', 'Export to PDF, Word, etc.', 'docs', 'export', 'docs', TRUE),
('docs.templates', 'Use Templates', 'Access document templates', 'docs', 'templates', 'docs', TRUE),
('docs.templates_manage', 'Manage Templates', 'Create organization templates', 'docs', 'templates_manage', 'docs', TRUE),
('docs.collaborate', 'Real-time Collaboration', 'Co-author documents in real-time', 'docs', 'collaborate', 'docs', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === SHEET PERMISSIONS (Like Excel Online / Google Sheets) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('sheet.read', 'View Spreadsheets', 'View spreadsheets', 'sheet', 'read', 'sheet', TRUE),
('sheet.write', 'Edit Spreadsheets', 'Create and edit spreadsheets', 'sheet', 'write', 'sheet', TRUE),
('sheet.share', 'Share Spreadsheets', 'Share spreadsheets with others', 'sheet', 'share', 'sheet', TRUE),
('sheet.export', 'Export Spreadsheets', 'Export to Excel, CSV, etc.', 'sheet', 'export', 'sheet', TRUE),
('sheet.import', 'Import Data', 'Import data from external sources', 'sheet', 'import', 'sheet', TRUE),
('sheet.macros', 'Run Macros', 'Execute spreadsheet macros', 'sheet', 'macros', 'sheet', TRUE),
('sheet.connections', 'Data Connections', 'Create database connections', 'sheet', 'connections', 'sheet', TRUE),
('sheet.pivot', 'Pivot Tables', 'Create pivot tables and charts', 'sheet', 'pivot', 'sheet', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === SLIDES PERMISSIONS (Like PowerPoint Online / Google Slides) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('slides.read', 'View Presentations', 'View presentations', 'slides', 'read', 'slides', TRUE),
('slides.write', 'Edit Presentations', 'Create and edit presentations', 'slides', 'write', 'slides', TRUE),
('slides.share', 'Share Presentations', 'Share presentations with others', 'slides', 'share', 'slides', TRUE),
('slides.present', 'Present Live', 'Start live presentations', 'slides', 'present', 'slides', TRUE),
('slides.export', 'Export Presentations', 'Export to PDF, PowerPoint', 'slides', 'export', 'slides', TRUE),
('slides.templates', 'Slide Templates', 'Access presentation templates', 'slides', 'templates', 'slides', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === MEET PERMISSIONS (Like Teams / Zoom / Google Meet) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('meet.join', 'Join Meetings', 'Join video meetings', 'meet', 'join', 'meet', TRUE),
('meet.create', 'Create Meetings', 'Schedule and create meetings', 'meet', 'create', 'meet', TRUE),
('meet.host', 'Host Meetings', 'Full host controls in meetings', 'meet', 'host', 'meet', TRUE),
('meet.record', 'Record Meetings', 'Record meeting sessions', 'meet', 'record', 'meet', TRUE),
('meet.transcript', 'Meeting Transcripts', 'Access meeting transcriptions', 'meet', 'transcript', 'meet', TRUE),
('meet.screen_share', 'Screen Share', 'Share screen in meetings', 'meet', 'screen_share', 'meet', TRUE),
('meet.breakout', 'Breakout Rooms', 'Create and manage breakout rooms', 'meet', 'breakout', 'meet', TRUE),
('meet.webinar', 'Host Webinars', 'Host large webinar events', 'meet', 'webinar', 'meet', TRUE),
('meet.admin', 'Meet Admin', 'Administer meeting settings globally', 'meet', 'admin', 'meet', TRUE),
('meet.external', 'External Meetings', 'Meet with external participants', 'meet', 'external', 'meet', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === CHAT PERMISSIONS (Like Teams Chat / Slack / Google Chat) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('chat.read', 'Read Messages', 'Read chat messages', 'chat', 'read', 'chat', TRUE),
('chat.write', 'Send Messages', 'Send chat messages', 'chat', 'write', 'chat', TRUE),
('chat.delete', 'Delete Messages', 'Delete own messages', 'chat', 'delete', 'chat', TRUE),
('chat.edit', 'Edit Messages', 'Edit sent messages', 'chat', 'edit', 'chat', TRUE),
('chat.files', 'Share Files in Chat', 'Share files in conversations', 'chat', 'files', 'chat', TRUE),
('chat.channels_create', 'Create Channels', 'Create chat channels', 'chat', 'channels_create', 'chat', TRUE),
('chat.channels_manage', 'Manage Channels', 'Manage channel settings', 'chat', 'channels_manage', 'chat', TRUE),
('chat.external', 'External Chat', 'Chat with external users', 'chat', 'external', 'chat', TRUE),
('chat.reactions', 'Reactions', 'Add reactions to messages', 'chat', 'reactions', 'chat', TRUE),
('chat.threads', 'Thread Replies', 'Reply in threads', 'chat', 'threads', 'chat', TRUE),
('chat.mentions', 'Mentions', 'Mention users and groups', 'chat', 'mentions', 'chat', TRUE),
('chat.admin', 'Chat Admin', 'Administer chat settings globally', 'chat', 'admin', 'chat', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === TASKS PERMISSIONS (Like Planner / Asana / Google Tasks) ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('tasks.read', 'View Tasks', 'View own and assigned tasks', 'tasks', 'read', 'tasks', TRUE),
('tasks.write', 'Manage Tasks', 'Create and edit tasks', 'tasks', 'write', 'tasks', TRUE),
('tasks.delete', 'Delete Tasks', 'Delete tasks', 'tasks', 'delete', 'tasks', TRUE),
('tasks.assign', 'Assign Tasks', 'Assign tasks to others', 'tasks', 'assign', 'tasks', TRUE),
('tasks.projects_create', 'Create Projects', 'Create task projects/boards', 'tasks', 'projects_create', 'tasks', TRUE),
('tasks.projects_manage', 'Manage Projects', 'Administer project settings', 'tasks', 'projects_manage', 'tasks', TRUE),
('tasks.time_track', 'Time Tracking', 'Log time against tasks', 'tasks', 'time_track', 'tasks', TRUE),
('tasks.reports', 'Task Reports', 'View task analytics and reports', 'tasks', 'reports', 'tasks', TRUE),
('tasks.automation', 'Task Automation', 'Create task automation rules', 'tasks', 'automation', 'tasks', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === BOT & AI PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('bots.read', 'View Bots', 'View bot configurations', 'bots', 'read', 'ai', TRUE),
('bots.create', 'Create Bots', 'Create new bots', 'bots', 'create', 'ai', TRUE),
('bots.write', 'Edit Bots', 'Modify bot settings', 'bots', 'write', 'ai', TRUE),
('bots.delete', 'Delete Bots', 'Delete bots', 'bots', 'delete', 'ai', TRUE),
('bots.publish', 'Publish Bots', 'Publish bots to channels', 'bots', 'publish', 'ai', TRUE),
('bots.channels', 'Manage Channels', 'Configure bot communication channels', 'bots', 'channels', 'ai', TRUE),
-- AI Assistant / Copilot
('ai.chat', 'AI Chat', 'Use AI chat assistant', 'ai', 'chat', 'ai', TRUE),
('ai.summarize', 'AI Summarize', 'Use AI to summarize content', 'ai', 'summarize', 'ai', TRUE),
('ai.compose', 'AI Compose', 'Use AI to draft content', 'ai', 'compose', 'ai', TRUE),
('ai.translate', 'AI Translate', 'Use AI translation', 'ai', 'translate', 'ai', TRUE),
('ai.analyze', 'AI Analyze', 'Use AI for data analysis', 'ai', 'analyze', 'ai', TRUE),
('ai.advanced', 'Advanced AI', 'Access advanced AI features', 'ai', 'advanced', 'ai', TRUE),
-- Knowledge Base
('kb.read', 'View Knowledge Base', 'Access knowledge base documents', 'kb', 'read', 'ai', TRUE),
('kb.write', 'Edit Knowledge Base', 'Add/edit knowledge base content', 'kb', 'write', 'ai', TRUE),
('kb.admin', 'KB Admin', 'Administer knowledge base settings', 'kb', 'admin', 'ai', TRUE),
-- Conversations / Attendant
('conversations.read', 'View Conversations', 'View bot conversations', 'conversations', 'read', 'ai', TRUE),
('conversations.write', 'Manage Conversations', 'Intervene in conversations', 'conversations', 'write', 'ai', TRUE),
('conversations.transfer', 'Transfer Conversations', 'Transfer to human agent', 'conversations', 'transfer', 'ai', TRUE),
('conversations.history', 'Conversation History', 'Access conversation history', 'conversations', 'history', 'ai', TRUE),
('attendant.access', 'Attendant Access', 'Access human attendant queue', 'attendant', 'access', 'ai', TRUE),
('attendant.respond', 'Attendant Respond', 'Respond to queued conversations', 'attendant', 'respond', 'ai', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === ANALYTICS & REPORTING PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('analytics.read', 'View Analytics', 'View usage analytics and dashboards', 'analytics', 'read', 'analytics', TRUE),
('analytics.export', 'Export Analytics', 'Export analytics data', 'analytics', 'export', 'analytics', TRUE),
('analytics.custom', 'Custom Reports', 'Create custom reports and dashboards', 'analytics', 'custom', 'analytics', TRUE),
('analytics.realtime', 'Real-time Analytics', 'Access real-time analytics', 'analytics', 'realtime', 'analytics', TRUE),
('reports.read', 'View Reports', 'Access standard reports', 'reports', 'read', 'analytics', TRUE),
('reports.schedule', 'Schedule Reports', 'Schedule automated report delivery', 'reports', 'schedule', 'analytics', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === MONITORING & SYSTEM PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('monitoring.read', 'View Monitoring', 'View system health and metrics', 'monitoring', 'read', 'system', TRUE),
('monitoring.alerts', 'Manage Alerts', 'Configure monitoring alerts', 'monitoring', 'alerts', 'system', TRUE),
('logs.read', 'View Logs', 'Access system and application logs', 'logs', 'read', 'system', TRUE),
('logs.export', 'Export Logs', 'Export log data', 'logs', 'export', 'system', TRUE),
('services.read', 'View Services', 'View service status', 'services', 'read', 'system', TRUE),
('services.manage', 'Manage Services', 'Start/stop/restart services', 'services', 'manage', 'system', TRUE),
('resources.read', 'View Resources', 'View resource usage', 'resources', 'read', 'system', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === PAPER & RESEARCH (AI Writing) PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('paper.read', 'View Papers', 'View AI-generated papers and notes', 'paper', 'read', 'paper', TRUE),
('paper.write', 'Create Papers', 'Create and edit AI-assisted documents', 'paper', 'write', 'paper', TRUE),
('paper.publish', 'Publish Papers', 'Publish papers to knowledge base', 'paper', 'publish', 'paper', TRUE),
('research.read', 'View Research', 'Access AI research results', 'research', 'read', 'research', TRUE),
('research.create', 'Create Research', 'Start AI research queries', 'research', 'create', 'research', TRUE),
('research.deep', 'Deep Research', 'Access deep research features', 'research', 'deep', 'research', TRUE),
('quicknote.access', 'Quick Notes', 'Access quick note feature', 'quicknote', 'access', 'paper', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === SOURCES & INTEGRATIONS PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('sources.read', 'View Sources', 'View configured data sources', 'sources', 'read', 'integrations', TRUE),
('sources.create', 'Create Sources', 'Add new data sources', 'sources', 'create', 'integrations', TRUE),
('sources.write', 'Edit Sources', 'Modify data source configurations', 'sources', 'write', 'integrations', TRUE),
('sources.delete', 'Delete Sources', 'Remove data sources', 'sources', 'delete', 'integrations', TRUE),
('webhooks.read', 'View Webhooks', 'View webhook configurations', 'webhooks', 'read', 'integrations', TRUE),
('webhooks.write', 'Manage Webhooks', 'Create and edit webhooks', 'webhooks', 'write', 'integrations', TRUE),
('api.access', 'API Access', 'Access REST API endpoints', 'api', 'access', 'integrations', TRUE),
('api.keys', 'API Key Management', 'Create and manage API keys', 'api', 'keys', 'integrations', TRUE),
('integrations.read', 'View Integrations', 'View third-party integrations', 'integrations', 'read', 'integrations', TRUE),
('integrations.write', 'Manage Integrations', 'Configure third-party integrations', 'integrations', 'write', 'integrations', TRUE),
('mcp.access', 'MCP Access', 'Access Model Context Protocol tools', 'mcp', 'access', 'integrations', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === AUTOTASK / AUTOMATION PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('autotask.read', 'View AutoTasks', 'View automated task definitions', 'autotask', 'read', 'automation', TRUE),
('autotask.create', 'Create AutoTasks', 'Create new automated tasks', 'autotask', 'create', 'automation', TRUE),
('autotask.write', 'Edit AutoTasks', 'Modify automated task settings', 'autotask', 'write', 'automation', TRUE),
('autotask.delete', 'Delete AutoTasks', 'Remove automated tasks', 'autotask', 'delete', 'automation', TRUE),
('autotask.execute', 'Execute AutoTasks', 'Run automated tasks manually', 'autotask', 'execute', 'automation', TRUE),
('autotask.schedule', 'Schedule AutoTasks', 'Schedule task automation', 'autotask', 'schedule', 'automation', TRUE),
('workflows.read', 'View Workflows', 'View workflow definitions', 'workflows', 'read', 'automation', TRUE),
('workflows.write', 'Manage Workflows', 'Create and edit workflows', 'workflows', 'write', 'automation', TRUE),
('intents.read', 'View Intents', 'View AI intent definitions', 'intents', 'read', 'automation', TRUE),
('intents.write', 'Manage Intents', 'Create and edit intents', 'intents', 'write', 'automation', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === DESIGNER PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('designer.access', 'Access Designer', 'Open visual designer tool', 'designer', 'access', 'designer', TRUE),
('designer.create', 'Create Designs', 'Create new UI designs', 'designer', 'create', 'designer', TRUE),
('designer.edit', 'Edit Designs', 'Modify existing designs', 'designer', 'edit', 'designer', TRUE),
('designer.publish', 'Publish Designs', 'Publish designs to production', 'designer', 'publish', 'designer', TRUE),
('designer.templates', 'Design Templates', 'Access and create design templates', 'designer', 'templates', 'designer', TRUE)
ON CONFLICT (name) DO NOTHING;
-- === SETTINGS PERMISSIONS ===
INSERT INTO rbac_permissions (name, display_name, description, resource_type, action, category, is_system) VALUES
('settings.personal', 'Personal Settings', 'Manage own user settings', 'settings', 'personal', 'settings', TRUE),
('settings.organization', 'Organization Settings', 'Manage organization settings', 'settings', 'organization', 'settings', TRUE),
('settings.security', 'Security Settings', 'Manage security configuration', 'settings', 'security', 'settings', TRUE),
('settings.notifications', 'Notification Settings', 'Manage notification preferences', 'settings', 'notifications', 'settings', TRUE),
('settings.appearance', 'Appearance Settings', 'Customize appearance and themes', 'settings', 'appearance', 'settings', TRUE),
('settings.language', 'Language Settings', 'Set language and locale', 'settings', 'language', 'settings', TRUE),
('settings.backup', 'Backup Settings', 'Configure backup and export', 'settings', 'backup', 'settings', TRUE)
ON CONFLICT (name) DO NOTHING;
-- ============================================================================
-- DEFAULT GROUPS (Like AD / Google Groups)
-- ============================================================================
INSERT INTO rbac_groups (name, display_name, description) VALUES
('all_users', 'All Users', 'Default group containing all organization users'),
('executives', 'Executives', 'Executive leadership team'),
('managers', 'Managers', 'Department managers and team leads'),
('it_department', 'IT Department', 'Information technology staff'),
('hr_department', 'HR Department', 'Human resources staff'),
('finance_department', 'Finance Department', 'Finance and accounting staff'),
('sales_team', 'Sales Team', 'Sales representatives and account managers'),
('support_team', 'Support Team', 'Customer support and helpdesk staff'),
('marketing_team', 'Marketing Team', 'Marketing and communications staff'),
('developers', 'Developers', 'Software development team'),
('external_contractors', 'External Contractors', 'External consultants and contractors'),
('guests', 'Guests', 'External guest users')
ON CONFLICT (name) DO NOTHING;
-- ============================================================================
-- ROLE-PERMISSION ASSIGNMENTS
-- ============================================================================
-- Global Admin gets ALL permissions
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'global_admin'
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Billing Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'billing_admin' AND p.name IN (
'org.read', 'org.billing', 'users.read', 'reports.read', 'analytics.read'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Compliance Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'compliance_admin' AND p.name IN (
'org.read', 'users.read', 'groups.read', 'audit.read', 'audit.export',
'compliance.read', 'compliance.write', 'dlp.read', 'dlp.write',
'retention.read', 'retention.write', 'ediscovery.access',
'analytics.read', 'reports.read', 'logs.read', 'logs.export'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Security Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'security_admin' AND p.name IN (
'org.read', 'users.read', 'users.mfa_manage', 'groups.read',
'security.read', 'security.write', 'threats.read', 'threats.respond',
'secrets.read', 'secrets.write', 'audit.read', 'logs.read',
'monitoring.read', 'monitoring.alerts'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- User Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'user_admin' AND p.name IN (
'users.read', 'users.create', 'users.write', 'users.delete',
'users.password_reset', 'users.mfa_manage', 'users.export', 'users.import',
'groups.read', 'groups.create', 'groups.write', 'groups.manage_members',
'roles.read', 'roles.assign', 'audit.read'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Groups Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'groups_admin' AND p.name IN (
'users.read', 'groups.read', 'groups.create', 'groups.write', 'groups.delete',
'groups.manage_members', 'groups.manage_owners'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Helpdesk Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'helpdesk_admin' AND p.name IN (
'users.read', 'users.password_reset', 'groups.read',
'attendant.access', 'attendant.respond', 'conversations.read'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Password Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'password_admin' AND p.name IN (
'users.read', 'users.password_reset'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Mail Admin (Exchange Admin)
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'exchange_admin' AND p.name LIKE 'mail.%'
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Drive Admin (SharePoint Admin)
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'sharepoint_admin' AND p.name LIKE 'drive.%'
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Meet & Chat Admin (Teams Admin)
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'teams_admin' AND (p.name LIKE 'meet.%' OR p.name LIKE 'chat.%')
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Knowledge Admin
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'knowledge_admin' AND p.name IN (
'kb.read', 'kb.write', 'kb.admin', 'docs.read', 'docs.write',
'docs.templates_manage', 'paper.read', 'paper.write', 'paper.publish',
'research.read', 'research.create', 'research.deep'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Reports Reader
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'reports_reader' AND p.name IN (
'analytics.read', 'reports.read', 'monitoring.read'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Power User
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'power_user' AND p.name IN (
-- All standard user permissions plus extras
'mail.read', 'mail.send', 'mail.delete', 'mail.organize', 'mail.delegate',
'calendar.read', 'calendar.write', 'calendar.share', 'calendar.delegate', 'calendar.free_busy', 'calendar.rooms',
'drive.read', 'drive.write', 'drive.delete', 'drive.share', 'drive.share_external', 'drive.download', 'drive.sync', 'drive.version_history', 'drive.shared_read', 'drive.shared_write',
'docs.read', 'docs.write', 'docs.comment', 'docs.share', 'docs.export', 'docs.templates', 'docs.collaborate',
'sheet.read', 'sheet.write', 'sheet.share', 'sheet.export', 'sheet.import', 'sheet.macros', 'sheet.connections', 'sheet.pivot',
'slides.read', 'slides.write', 'slides.share', 'slides.present', 'slides.export', 'slides.templates',
'meet.join', 'meet.create', 'meet.host', 'meet.record', 'meet.transcript', 'meet.screen_share', 'meet.breakout', 'meet.external',
'chat.read', 'chat.write', 'chat.delete', 'chat.edit', 'chat.files', 'chat.channels_create', 'chat.reactions', 'chat.threads', 'chat.mentions',
'tasks.read', 'tasks.write', 'tasks.delete', 'tasks.assign', 'tasks.projects_create', 'tasks.time_track', 'tasks.automation',
'ai.chat', 'ai.summarize', 'ai.compose', 'ai.translate', 'ai.analyze', 'ai.advanced',
'kb.read', 'kb.write',
'paper.read', 'paper.write', 'quicknote.access',
'research.read', 'research.create', 'research.deep',
'autotask.read', 'autotask.create', 'autotask.execute',
'sources.read', 'sources.create',
'webhooks.read', 'webhooks.write',
'api.access', 'api.keys',
'designer.access', 'designer.create', 'designer.edit',
'settings.personal', 'settings.notifications', 'settings.appearance', 'settings.language',
'analytics.read'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Standard User (Default for employees)
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'standard_user' AND p.name IN (
-- Mail
'mail.read', 'mail.send', 'mail.delete', 'mail.organize',
-- Calendar
'calendar.read', 'calendar.write', 'calendar.share', 'calendar.free_busy', 'calendar.rooms', 'calendar.shared_read',
-- Drive
'drive.read', 'drive.write', 'drive.delete', 'drive.share', 'drive.download', 'drive.sync', 'drive.version_history', 'drive.shared_read', 'drive.shared_write',
-- Docs
'docs.read', 'docs.write', 'docs.comment', 'docs.share', 'docs.export', 'docs.templates', 'docs.collaborate',
-- Sheet
'sheet.read', 'sheet.write', 'sheet.share', 'sheet.export', 'sheet.import', 'sheet.pivot',
-- Slides
'slides.read', 'slides.write', 'slides.share', 'slides.present', 'slides.export', 'slides.templates',
-- Meet
'meet.join', 'meet.create', 'meet.host', 'meet.screen_share', 'meet.external',
-- Chat
'chat.read', 'chat.write', 'chat.delete', 'chat.edit', 'chat.files', 'chat.reactions', 'chat.threads', 'chat.mentions',
-- Tasks
'tasks.read', 'tasks.write', 'tasks.delete', 'tasks.assign', 'tasks.time_track',
-- AI
'ai.chat', 'ai.summarize', 'ai.compose', 'ai.translate',
-- KB
'kb.read',
-- Paper & Research
'paper.read', 'paper.write', 'quicknote.access',
'research.read', 'research.create',
-- Settings
'settings.personal', 'settings.notifications', 'settings.appearance', 'settings.language',
-- API
'api.access'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Guest User (External collaboration)
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'guest_user' AND p.name IN (
-- Limited mail
'mail.read', 'mail.send',
-- Limited calendar
'calendar.read', 'calendar.free_busy',
-- Limited drive (shared only)
'drive.read', 'drive.download', 'drive.shared_read',
-- Limited docs
'docs.read', 'docs.comment',
-- Limited meet
'meet.join', 'meet.screen_share',
-- Limited chat
'chat.read', 'chat.write', 'chat.reactions',
-- Limited tasks
'tasks.read',
-- Settings
'settings.personal', 'settings.language'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- Viewer (Read-only access)
INSERT INTO rbac_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM rbac_roles r, rbac_permissions p
WHERE r.name = 'viewer' AND p.name IN (
'mail.read', 'calendar.read', 'calendar.free_busy',
'drive.read', 'drive.shared_read', 'docs.read', 'sheet.read', 'slides.read',
'meet.join', 'chat.read', 'tasks.read', 'kb.read',
'analytics.read', 'settings.personal'
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- ============================================================================
-- GROUP-ROLE ASSIGNMENTS (Common patterns)
-- ============================================================================
-- IT Department gets helpdesk + security reader
INSERT INTO rbac_group_roles (group_id, role_id)
SELECT g.id, r.id FROM rbac_groups g, rbac_roles r
WHERE g.name = 'it_department' AND r.name IN ('helpdesk_admin', 'reports_reader')
ON CONFLICT (group_id, role_id) DO NOTHING;
-- Developers get power user role
INSERT INTO rbac_group_roles (group_id, role_id)
SELECT g.id, r.id FROM rbac_groups g, rbac_roles r
WHERE g.name = 'developers' AND r.name = 'power_user'
ON CONFLICT (group_id, role_id) DO NOTHING;
-- All Users get standard user role
INSERT INTO rbac_group_roles (group_id, role_id)
SELECT g.id, r.id FROM rbac_groups g, rbac_roles r
WHERE g.name = 'all_users' AND r.name = 'standard_user'
ON CONFLICT (group_id, role_id) DO NOTHING;
-- External Contractors get guest role
INSERT INTO rbac_group_roles (group_id, role_id)
SELECT g.id, r.id FROM rbac_groups g, rbac_roles r
WHERE g.name = 'external_contractors' AND r.name = 'guest_user'
ON CONFLICT (group_id, role_id) DO NOTHING;
-- Guests get guest role
INSERT INTO rbac_group_roles (group_id, role_id)
SELECT g.id, r.id FROM rbac_groups g, rbac_roles r
WHERE g.name = 'guests' AND r.name = 'guest_user'
ON CONFLICT (group_id, role_id) DO NOTHING;
-- Managers get reports reader
INSERT INTO rbac_group_roles (group_id, role_id)
SELECT g.id, r.id FROM rbac_groups g, rbac_roles r
WHERE g.name = 'managers' AND r.name = 'reports_reader'
ON CONFLICT (group_id, role_id) DO NOTHING;

View file

@ -0,0 +1,23 @@
-- Learn Module Database Migration - Rollback
-- Removes all Learn module tables and data
-- Drop triggers first
DROP TRIGGER IF EXISTS trigger_learn_paths_updated_at ON learn_paths;
DROP TRIGGER IF EXISTS trigger_learn_quizzes_updated_at ON learn_quizzes;
DROP TRIGGER IF EXISTS trigger_learn_lessons_updated_at ON learn_lessons;
DROP TRIGGER IF EXISTS trigger_learn_courses_updated_at ON learn_courses;
-- Drop the trigger function
DROP FUNCTION IF EXISTS update_learn_updated_at();
-- Drop tables in reverse order of creation (respecting foreign key constraints)
DROP TABLE IF EXISTS learn_path_courses CASCADE;
DROP TABLE IF EXISTS learn_paths CASCADE;
DROP TABLE IF EXISTS learn_quiz_attempts CASCADE;
DROP TABLE IF EXISTS learn_certificates CASCADE;
DROP TABLE IF EXISTS learn_course_assignments CASCADE;
DROP TABLE IF EXISTS learn_user_progress CASCADE;
DROP TABLE IF EXISTS learn_quizzes CASCADE;
DROP TABLE IF EXISTS learn_lessons CASCADE;
DROP TABLE IF EXISTS learn_courses CASCADE;
DROP TABLE IF EXISTS learn_categories CASCADE;

View file

@ -0,0 +1,401 @@
-- Learn Module Database Migration
-- Learning Management System (LMS) for General Bots
-- ============================================================================
-- CATEGORIES TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
icon TEXT,
color TEXT,
parent_id UUID REFERENCES learn_categories(id) ON DELETE SET NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_learn_categories_parent ON learn_categories(parent_id);
CREATE INDEX idx_learn_categories_sort ON learn_categories(sort_order);
-- Insert default categories
INSERT INTO learn_categories (name, description, icon, color, sort_order) VALUES
('compliance', 'Treinamentos de Compliance e Conformidade', '📋', '#3b82f6', 1),
('security', 'Segurança da Informação e LGPD', '🔒', '#ef4444', 2),
('onboarding', 'Integração de Novos Colaboradores', '🚀', '#10b981', 3),
('skills', 'Desenvolvimento de Habilidades', '💡', '#f59e0b', 4),
('leadership', 'Liderança e Gestão', '👔', '#8b5cf6', 5),
('technical', 'Treinamentos Técnicos', '⚙️', '#6366f1', 6),
('soft-skills', 'Soft Skills e Comunicação', '💬', '#ec4899', 7),
('wellness', 'Bem-estar e Qualidade de Vida', '🧘', '#14b8a6', 8);
-- ============================================================================
-- COURSES TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id UUID,
title TEXT NOT NULL,
description TEXT,
category TEXT NOT NULL DEFAULT 'skills',
difficulty TEXT NOT NULL DEFAULT 'beginner',
duration_minutes INTEGER NOT NULL DEFAULT 0,
thumbnail_url TEXT,
is_mandatory BOOLEAN NOT NULL DEFAULT FALSE,
due_days INTEGER,
is_published BOOLEAN NOT NULL DEFAULT FALSE,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT valid_difficulty CHECK (difficulty IN ('beginner', 'intermediate', 'advanced'))
);
CREATE INDEX idx_learn_courses_org ON learn_courses(organization_id);
CREATE INDEX idx_learn_courses_category ON learn_courses(category);
CREATE INDEX idx_learn_courses_mandatory ON learn_courses(is_mandatory);
CREATE INDEX idx_learn_courses_published ON learn_courses(is_published);
CREATE INDEX idx_learn_courses_created ON learn_courses(created_at DESC);
-- ============================================================================
-- LESSONS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
title TEXT NOT NULL,
content TEXT,
content_type TEXT NOT NULL DEFAULT 'text',
lesson_order INTEGER NOT NULL DEFAULT 1,
duration_minutes INTEGER NOT NULL DEFAULT 0,
video_url TEXT,
attachments JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT valid_content_type CHECK (content_type IN ('text', 'video', 'pdf', 'slides', 'interactive'))
);
CREATE INDEX idx_learn_lessons_course ON learn_lessons(course_id);
CREATE INDEX idx_learn_lessons_order ON learn_lessons(course_id, lesson_order);
-- ============================================================================
-- QUIZZES TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_quizzes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
lesson_id UUID REFERENCES learn_lessons(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
title TEXT NOT NULL,
passing_score INTEGER NOT NULL DEFAULT 70,
time_limit_minutes INTEGER,
max_attempts INTEGER,
questions JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT valid_passing_score CHECK (passing_score >= 0 AND passing_score <= 100)
);
CREATE INDEX idx_learn_quizzes_course ON learn_quizzes(course_id);
CREATE INDEX idx_learn_quizzes_lesson ON learn_quizzes(lesson_id);
-- ============================================================================
-- USER PROGRESS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_user_progress (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
lesson_id UUID REFERENCES learn_lessons(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'not_started',
quiz_score INTEGER,
quiz_attempts INTEGER NOT NULL DEFAULT 0,
time_spent_minutes INTEGER NOT NULL DEFAULT 0,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
last_accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT valid_status CHECK (status IN ('not_started', 'in_progress', 'completed', 'failed')),
CONSTRAINT valid_quiz_score CHECK (quiz_score IS NULL OR (quiz_score >= 0 AND quiz_score <= 100))
);
CREATE INDEX idx_learn_progress_user ON learn_user_progress(user_id);
CREATE INDEX idx_learn_progress_course ON learn_user_progress(course_id);
CREATE INDEX idx_learn_progress_user_course ON learn_user_progress(user_id, course_id);
CREATE INDEX idx_learn_progress_status ON learn_user_progress(status);
CREATE INDEX idx_learn_progress_last_accessed ON learn_user_progress(last_accessed_at DESC);
-- Unique constraint for course-level progress (where lesson_id is null)
CREATE UNIQUE INDEX idx_learn_progress_user_course_unique
ON learn_user_progress(user_id, course_id)
WHERE lesson_id IS NULL;
-- Unique constraint for lesson-level progress
CREATE UNIQUE INDEX idx_learn_progress_user_lesson_unique
ON learn_user_progress(user_id, lesson_id)
WHERE lesson_id IS NOT NULL;
-- ============================================================================
-- COURSE ASSIGNMENTS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_course_assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
assigned_by UUID,
due_date TIMESTAMPTZ,
is_mandatory BOOLEAN NOT NULL DEFAULT TRUE,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
reminder_sent BOOLEAN NOT NULL DEFAULT FALSE,
reminder_sent_at TIMESTAMPTZ
);
CREATE INDEX idx_learn_assignments_user ON learn_course_assignments(user_id);
CREATE INDEX idx_learn_assignments_course ON learn_course_assignments(course_id);
CREATE INDEX idx_learn_assignments_due ON learn_course_assignments(due_date);
CREATE INDEX idx_learn_assignments_pending ON learn_course_assignments(user_id, completed_at)
WHERE completed_at IS NULL;
CREATE INDEX idx_learn_assignments_overdue ON learn_course_assignments(due_date)
WHERE completed_at IS NULL AND due_date IS NOT NULL;
-- Unique constraint to prevent duplicate assignments
CREATE UNIQUE INDEX idx_learn_assignments_unique ON learn_course_assignments(course_id, user_id);
-- ============================================================================
-- CERTIFICATES TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_certificates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
score INTEGER NOT NULL,
certificate_url TEXT,
verification_code TEXT NOT NULL UNIQUE,
expires_at TIMESTAMPTZ,
CONSTRAINT valid_cert_score CHECK (score >= 0 AND score <= 100)
);
CREATE INDEX idx_learn_certificates_user ON learn_certificates(user_id);
CREATE INDEX idx_learn_certificates_course ON learn_certificates(course_id);
CREATE INDEX idx_learn_certificates_verification ON learn_certificates(verification_code);
CREATE INDEX idx_learn_certificates_issued ON learn_certificates(issued_at DESC);
-- Unique constraint to prevent duplicate certificates
CREATE UNIQUE INDEX idx_learn_certificates_unique ON learn_certificates(user_id, course_id);
-- ============================================================================
-- QUIZ ATTEMPTS TABLE (for detailed tracking)
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_quiz_attempts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
quiz_id UUID NOT NULL REFERENCES learn_quizzes(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
attempt_number INTEGER NOT NULL DEFAULT 1,
score INTEGER NOT NULL,
max_score INTEGER NOT NULL,
passed BOOLEAN NOT NULL,
time_taken_minutes INTEGER,
answers JSONB NOT NULL DEFAULT '{}',
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_learn_quiz_attempts_quiz ON learn_quiz_attempts(quiz_id);
CREATE INDEX idx_learn_quiz_attempts_user ON learn_quiz_attempts(user_id);
CREATE INDEX idx_learn_quiz_attempts_user_quiz ON learn_quiz_attempts(user_id, quiz_id);
-- ============================================================================
-- LEARNING PATHS TABLE (for structured learning journeys)
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_paths (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id UUID,
name TEXT NOT NULL,
description TEXT,
thumbnail_url TEXT,
is_published BOOLEAN NOT NULL DEFAULT FALSE,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_learn_paths_org ON learn_paths(organization_id);
CREATE INDEX idx_learn_paths_published ON learn_paths(is_published);
-- ============================================================================
-- LEARNING PATH COURSES (junction table)
-- ============================================================================
CREATE TABLE IF NOT EXISTS learn_path_courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
path_id UUID NOT NULL REFERENCES learn_paths(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
course_order INTEGER NOT NULL DEFAULT 1,
is_required BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_learn_path_courses_path ON learn_path_courses(path_id);
CREATE INDEX idx_learn_path_courses_course ON learn_path_courses(course_id);
CREATE UNIQUE INDEX idx_learn_path_courses_unique ON learn_path_courses(path_id, course_id);
-- ============================================================================
-- SAMPLE DATA FOR DEMONSTRATION
-- ============================================================================
-- Sample mandatory compliance course
INSERT INTO learn_courses (id, title, description, category, difficulty, duration_minutes, is_mandatory, due_days, is_published) VALUES
('11111111-1111-1111-1111-111111111111',
'LGPD - Lei Geral de Proteção de Dados',
'Treinamento obrigatório sobre a Lei Geral de Proteção de Dados Pessoais. Aprenda os conceitos fundamentais, direitos dos titulares e obrigações da empresa.',
'compliance', 'beginner', 45, TRUE, 30, TRUE),
('22222222-2222-2222-2222-222222222222',
'Código de Conduta e Ética',
'Conheça nosso código de conduta e ética empresarial. Este treinamento é obrigatório para todos os colaboradores.',
'compliance', 'beginner', 30, TRUE, 14, TRUE),
('33333333-3333-3333-3333-333333333333',
'Segurança da Informação',
'Aprenda as melhores práticas de segurança da informação para proteger dados da empresa e de clientes.',
'security', 'intermediate', 60, TRUE, 30, TRUE),
('44444444-4444-4444-4444-444444444444',
'Integração de Novos Colaboradores',
'Bem-vindo à empresa! Este curso apresenta nossa cultura, valores e processos fundamentais.',
'onboarding', 'beginner', 90, FALSE, NULL, TRUE),
('55555555-5555-5555-5555-555555555555',
'Comunicação Efetiva',
'Desenvolva habilidades de comunicação profissional para trabalhar melhor em equipe.',
'soft-skills', 'intermediate', 40, FALSE, NULL, TRUE);
-- Sample lessons for LGPD course
INSERT INTO learn_lessons (course_id, title, content, content_type, lesson_order, duration_minutes) VALUES
('11111111-1111-1111-1111-111111111111', 'Introdução à LGPD',
'<h2>O que é a LGPD?</h2><p>A Lei Geral de Proteção de Dados (Lei nº 13.709/2018) é a legislação brasileira que regula as atividades de tratamento de dados pessoais.</p><h3>Objetivos da LGPD</h3><ul><li>Proteger os direitos fundamentais de liberdade e privacidade</li><li>Estabelecer regras claras sobre tratamento de dados pessoais</li><li>Fomentar o desenvolvimento econômico e tecnológico</li></ul>',
'text', 1, 10),
('11111111-1111-1111-1111-111111111111', 'Conceitos Fundamentais',
'<h2>Principais Conceitos</h2><h3>Dados Pessoais</h3><p>Informação relacionada a pessoa natural identificada ou identificável.</p><h3>Dados Sensíveis</h3><p>Dados sobre origem racial, convicção religiosa, opinião política, filiação sindical, dados de saúde, vida sexual, dados genéticos ou biométricos.</p><h3>Tratamento de Dados</h3><p>Toda operação realizada com dados pessoais: coleta, armazenamento, uso, compartilhamento, exclusão, etc.</p>',
'text', 2, 15),
('11111111-1111-1111-1111-111111111111', 'Direitos dos Titulares',
'<h2>Direitos Garantidos pela LGPD</h2><ul><li><strong>Confirmação e acesso:</strong> saber se seus dados são tratados e ter acesso a eles</li><li><strong>Correção:</strong> corrigir dados incompletos, inexatos ou desatualizados</li><li><strong>Anonimização e exclusão:</strong> solicitar a anonimização ou eliminação de dados desnecessários</li><li><strong>Portabilidade:</strong> transferir seus dados para outro fornecedor</li><li><strong>Revogação do consentimento:</strong> revogar o consentimento a qualquer momento</li></ul>',
'text', 3, 10),
('11111111-1111-1111-1111-111111111111', 'Responsabilidades da Empresa',
'<h2>Nossas Responsabilidades</h2><p>Como colaboradores, temos o dever de:</p><ul><li>Tratar dados pessoais apenas para finalidades legítimas</li><li>Manter a confidencialidade das informações</li><li>Reportar incidentes de segurança</li><li>Seguir os procedimentos internos de proteção de dados</li></ul>',
'text', 4, 10);
-- Sample quiz for LGPD course
INSERT INTO learn_quizzes (course_id, title, passing_score, time_limit_minutes, questions) VALUES
('11111111-1111-1111-1111-111111111111', 'Avaliação - LGPD', 70, 15, '[
{
"id": "q1",
"text": "O que significa LGPD?",
"question_type": "single_choice",
"options": [
{"text": "Lei Geral de Proteção de Dados", "is_correct": true},
{"text": "Lei Governamental de Privacidade Digital", "is_correct": false},
{"text": "Legislação Geral de Proteção Digital", "is_correct": false},
{"text": "Lei de Garantia e Proteção de Dados", "is_correct": false}
],
"correct_answers": [0],
"explanation": "LGPD significa Lei Geral de Proteção de Dados (Lei nº 13.709/2018).",
"points": 10
},
{
"id": "q2",
"text": "Quais são considerados dados sensíveis pela LGPD?",
"question_type": "multiple_choice",
"options": [
{"text": "Dados de saúde", "is_correct": true},
{"text": "Nome e CPF", "is_correct": false},
{"text": "Origem racial ou étnica", "is_correct": true},
{"text": "Endereço de e-mail", "is_correct": false},
{"text": "Convicção religiosa", "is_correct": true}
],
"correct_answers": [0, 2, 4],
"explanation": "Dados sensíveis incluem: origem racial/étnica, convicção religiosa, opinião política, dados de saúde, vida sexual, dados genéticos ou biométricos.",
"points": 20
},
{
"id": "q3",
"text": "O titular dos dados tem direito de solicitar a exclusão de seus dados pessoais.",
"question_type": "true_false",
"options": [
{"text": "Verdadeiro", "is_correct": true},
{"text": "Falso", "is_correct": false}
],
"correct_answers": [0],
"explanation": "Sim, o direito à eliminação de dados é um dos direitos garantidos pela LGPD.",
"points": 10
},
{
"id": "q4",
"text": "O que é tratamento de dados segundo a LGPD?",
"question_type": "single_choice",
"options": [
{"text": "Apenas o armazenamento de dados", "is_correct": false},
{"text": "Toda operação realizada com dados pessoais", "is_correct": true},
{"text": "Somente a coleta de dados", "is_correct": false},
{"text": "A venda de dados pessoais", "is_correct": false}
],
"correct_answers": [1],
"explanation": "Tratamento é toda operação: coleta, produção, recepção, classificação, utilização, acesso, reprodução, transmissão, distribuição, processamento, arquivamento, armazenamento, eliminação, etc.",
"points": 10
},
{
"id": "q5",
"text": "Qual é o prazo para a empresa responder a uma solicitação do titular dos dados?",
"question_type": "single_choice",
"options": [
{"text": "Imediatamente", "is_correct": false},
{"text": "Em até 15 dias", "is_correct": true},
{"text": "Em até 30 dias", "is_correct": false},
{"text": "Em até 90 dias", "is_correct": false}
],
"correct_answers": [1],
"explanation": "A LGPD estabelece que a empresa deve responder às solicitações dos titulares em até 15 dias.",
"points": 10
}
]');
-- Add lessons for other courses
INSERT INTO learn_lessons (course_id, title, content_type, lesson_order, duration_minutes) VALUES
('22222222-2222-2222-2222-222222222222', 'Nossa Missão e Valores', 'text', 1, 10),
('22222222-2222-2222-2222-222222222222', 'Conduta Profissional', 'text', 2, 10),
('22222222-2222-2222-2222-222222222222', 'Canais de Denúncia', 'text', 3, 10),
('33333333-3333-3333-3333-333333333333', 'Ameaças Digitais', 'text', 1, 15),
('33333333-3333-3333-3333-333333333333', 'Senhas Seguras', 'text', 2, 15),
('33333333-3333-3333-3333-333333333333', 'Phishing e Engenharia Social', 'text', 3, 15),
('33333333-3333-3333-3333-333333333333', 'Políticas de Segurança', 'text', 4, 15);
-- ============================================================================
-- TRIGGERS FOR UPDATED_AT
-- ============================================================================
CREATE OR REPLACE FUNCTION update_learn_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_learn_courses_updated_at
BEFORE UPDATE ON learn_courses
FOR EACH ROW
EXECUTE FUNCTION update_learn_updated_at();
CREATE TRIGGER trigger_learn_lessons_updated_at
BEFORE UPDATE ON learn_lessons
FOR EACH ROW
EXECUTE FUNCTION update_learn_updated_at();
CREATE TRIGGER trigger_learn_quizzes_updated_at
BEFORE UPDATE ON learn_quizzes
FOR EACH ROW
EXECUTE FUNCTION update_learn_updated_at();
CREATE TRIGGER trigger_learn_paths_updated_at
BEFORE UPDATE ON learn_paths
FOR EACH ROW
EXECUTE FUNCTION update_learn_updated_at();

View file

@ -0,0 +1,23 @@
DROP INDEX IF EXISTS idx_video_command_history_executed;
DROP INDEX IF EXISTS idx_video_command_history_project;
DROP TABLE IF EXISTS video_command_history;
DROP INDEX IF EXISTS idx_video_exports_status;
DROP INDEX IF EXISTS idx_video_exports_project;
DROP TABLE IF EXISTS video_exports;
DROP INDEX IF EXISTS idx_video_audio_tracks_project;
DROP TABLE IF EXISTS video_audio_tracks;
DROP INDEX IF EXISTS idx_video_layers_track;
DROP INDEX IF EXISTS idx_video_layers_project;
DROP TABLE IF EXISTS video_layers;
DROP INDEX IF EXISTS idx_video_clips_order;
DROP INDEX IF EXISTS idx_video_clips_project;
DROP TABLE IF EXISTS video_clips;
DROP INDEX IF EXISTS idx_video_projects_created_by;
DROP INDEX IF EXISTS idx_video_projects_status;
DROP INDEX IF EXISTS idx_video_projects_organization;
DROP TABLE IF EXISTS video_projects;

View file

@ -0,0 +1,118 @@
-- Video Projects
CREATE TABLE IF NOT EXISTS video_projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id UUID,
created_by UUID,
name TEXT NOT NULL,
description TEXT,
resolution_width INT NOT NULL DEFAULT 1920,
resolution_height INT NOT NULL DEFAULT 1080,
fps INT NOT NULL DEFAULT 30,
total_duration_ms BIGINT NOT NULL DEFAULT 0,
timeline_json JSONB NOT NULL DEFAULT '{"clips": []}',
layers_json JSONB NOT NULL DEFAULT '[]',
audio_tracks_json JSONB NOT NULL DEFAULT '[]',
playhead_ms BIGINT NOT NULL DEFAULT 0,
selection_json JSONB NOT NULL DEFAULT '{"type": "None"}',
zoom_level REAL NOT NULL DEFAULT 1.0,
thumbnail_url TEXT,
status TEXT NOT NULL DEFAULT 'draft',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_video_projects_organization ON video_projects(organization_id);
CREATE INDEX IF NOT EXISTS idx_video_projects_status ON video_projects(status);
CREATE INDEX IF NOT EXISTS idx_video_projects_created_by ON video_projects(created_by);
-- Video Clips
CREATE TABLE IF NOT EXISTS video_clips (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES video_projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
source_url TEXT NOT NULL,
start_ms BIGINT NOT NULL DEFAULT 0,
duration_ms BIGINT NOT NULL DEFAULT 0,
trim_in_ms BIGINT NOT NULL DEFAULT 0,
trim_out_ms BIGINT NOT NULL DEFAULT 0,
volume REAL NOT NULL DEFAULT 1.0,
clip_order INT NOT NULL DEFAULT 0,
transition_in TEXT,
transition_out TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_video_clips_project ON video_clips(project_id);
CREATE INDEX IF NOT EXISTS idx_video_clips_order ON video_clips(project_id, clip_order);
-- Video Layers (Text, Shapes, Images)
CREATE TABLE IF NOT EXISTS video_layers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES video_projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
layer_type TEXT NOT NULL,
track_index INT NOT NULL DEFAULT 0,
start_ms BIGINT NOT NULL DEFAULT 0,
end_ms BIGINT NOT NULL DEFAULT 5000,
x REAL NOT NULL DEFAULT 0.5,
y REAL NOT NULL DEFAULT 0.5,
width REAL NOT NULL DEFAULT 0.5,
height REAL NOT NULL DEFAULT 0.2,
rotation REAL NOT NULL DEFAULT 0.0,
opacity REAL NOT NULL DEFAULT 1.0,
properties_json JSONB NOT NULL DEFAULT '{}',
animation_in TEXT,
animation_out TEXT,
locked BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_video_layers_project ON video_layers(project_id);
CREATE INDEX IF NOT EXISTS idx_video_layers_track ON video_layers(project_id, track_index);
-- Video Audio Tracks
CREATE TABLE IF NOT EXISTS video_audio_tracks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES video_projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
source_url TEXT NOT NULL,
track_type TEXT NOT NULL,
start_ms BIGINT NOT NULL DEFAULT 0,
duration_ms BIGINT NOT NULL DEFAULT 0,
volume REAL NOT NULL DEFAULT 1.0,
fade_in_ms BIGINT NOT NULL DEFAULT 0,
fade_out_ms BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_video_audio_tracks_project ON video_audio_tracks(project_id);
-- Video Exports
CREATE TABLE IF NOT EXISTS video_exports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES video_projects(id) ON DELETE CASCADE,
format TEXT NOT NULL DEFAULT 'mp4',
quality TEXT NOT NULL DEFAULT 'high',
status TEXT NOT NULL DEFAULT 'pending',
progress INT NOT NULL DEFAULT 0,
output_url TEXT,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_video_exports_project ON video_exports(project_id);
CREATE INDEX IF NOT EXISTS idx_video_exports_status ON video_exports(status);
-- Video Command History (for undo/redo)
CREATE TABLE IF NOT EXISTS video_command_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES video_projects(id) ON DELETE CASCADE,
user_id UUID,
command_type TEXT NOT NULL,
command_json JSONB NOT NULL,
executed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_video_command_history_project ON video_command_history(project_id);
CREATE INDEX IF NOT EXISTS idx_video_command_history_executed ON video_command_history(project_id, executed_at DESC);

View file

@ -0,0 +1,7 @@
DROP TABLE IF EXISTS crm_notes;
DROP TABLE IF EXISTS crm_activities;
DROP TABLE IF EXISTS crm_opportunities;
DROP TABLE IF EXISTS crm_leads;
DROP TABLE IF EXISTS crm_pipeline_stages;
DROP TABLE IF EXISTS crm_accounts;
DROP TABLE IF EXISTS crm_contacts;

View file

@ -0,0 +1,231 @@
CREATE TABLE IF NOT EXISTS crm_contacts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
first_name VARCHAR(255),
last_name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(50),
mobile VARCHAR(50),
company VARCHAR(255),
job_title VARCHAR(255),
source VARCHAR(100),
status VARCHAR(50) NOT NULL DEFAULT 'active',
tags TEXT[] DEFAULT '{}',
custom_fields JSONB DEFAULT '{}',
address_line1 VARCHAR(500),
address_line2 VARCHAR(500),
city VARCHAR(255),
state VARCHAR(255),
postal_code VARCHAR(50),
country VARCHAR(100),
notes TEXT,
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_crm_contacts_org ON crm_contacts(org_id);
CREATE INDEX idx_crm_contacts_bot ON crm_contacts(bot_id);
CREATE INDEX idx_crm_contacts_email ON crm_contacts(email);
CREATE INDEX idx_crm_contacts_owner ON crm_contacts(owner_id);
CREATE INDEX idx_crm_contacts_status ON crm_contacts(status);
CREATE TABLE IF NOT EXISTS crm_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
website VARCHAR(500),
industry VARCHAR(100),
employees_count INTEGER,
annual_revenue DECIMAL(15,2),
phone VARCHAR(50),
email VARCHAR(255),
address_line1 VARCHAR(500),
address_line2 VARCHAR(500),
city VARCHAR(255),
state VARCHAR(255),
postal_code VARCHAR(50),
country VARCHAR(100),
description TEXT,
tags TEXT[] DEFAULT '{}',
custom_fields JSONB DEFAULT '{}',
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_crm_accounts_org ON crm_accounts(org_id);
CREATE INDEX idx_crm_accounts_bot ON crm_accounts(bot_id);
CREATE INDEX idx_crm_accounts_name ON crm_accounts(name);
CREATE INDEX idx_crm_accounts_owner ON crm_accounts(owner_id);
CREATE TABLE IF NOT EXISTS crm_pipeline_stages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
stage_order INTEGER NOT NULL,
probability INTEGER NOT NULL DEFAULT 0,
is_won BOOLEAN NOT NULL DEFAULT FALSE,
is_lost BOOLEAN NOT NULL DEFAULT FALSE,
color VARCHAR(7) DEFAULT '#3b82f6',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT crm_pipeline_stages_unique UNIQUE (org_id, bot_id, name)
);
CREATE INDEX idx_crm_pipeline_stages_org ON crm_pipeline_stages(org_id);
CREATE INDEX idx_crm_pipeline_stages_bot ON crm_pipeline_stages(bot_id);
CREATE INDEX idx_crm_pipeline_stages_order ON crm_pipeline_stages(stage_order);
CREATE TABLE IF NOT EXISTS crm_leads (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
contact_id UUID REFERENCES crm_contacts(id) ON DELETE SET NULL,
account_id UUID REFERENCES crm_accounts(id) ON DELETE SET NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
value DECIMAL(15,2),
currency VARCHAR(3) DEFAULT 'USD',
stage_id UUID REFERENCES crm_pipeline_stages(id) ON DELETE SET NULL,
stage VARCHAR(100) NOT NULL DEFAULT 'new',
probability INTEGER NOT NULL DEFAULT 0,
source VARCHAR(100),
expected_close_date DATE,
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
lost_reason VARCHAR(500),
tags TEXT[] DEFAULT '{}',
custom_fields JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
closed_at TIMESTAMPTZ
);
CREATE INDEX idx_crm_leads_org ON crm_leads(org_id);
CREATE INDEX idx_crm_leads_bot ON crm_leads(bot_id);
CREATE INDEX idx_crm_leads_contact ON crm_leads(contact_id);
CREATE INDEX idx_crm_leads_account ON crm_leads(account_id);
CREATE INDEX idx_crm_leads_stage ON crm_leads(stage);
CREATE INDEX idx_crm_leads_owner ON crm_leads(owner_id);
CREATE INDEX idx_crm_leads_expected_close ON crm_leads(expected_close_date);
CREATE TABLE IF NOT EXISTS crm_opportunities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
lead_id UUID REFERENCES crm_leads(id) ON DELETE SET NULL,
account_id UUID REFERENCES crm_accounts(id) ON DELETE SET NULL,
contact_id UUID REFERENCES crm_contacts(id) ON DELETE SET NULL,
name VARCHAR(500) NOT NULL,
description TEXT,
value DECIMAL(15,2),
currency VARCHAR(3) DEFAULT 'USD',
stage_id UUID REFERENCES crm_pipeline_stages(id) ON DELETE SET NULL,
stage VARCHAR(100) NOT NULL DEFAULT 'qualification',
probability INTEGER NOT NULL DEFAULT 0,
source VARCHAR(100),
expected_close_date DATE,
actual_close_date DATE,
won BOOLEAN,
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
tags TEXT[] DEFAULT '{}',
custom_fields JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_crm_opportunities_org ON crm_opportunities(org_id);
CREATE INDEX idx_crm_opportunities_bot ON crm_opportunities(bot_id);
CREATE INDEX idx_crm_opportunities_lead ON crm_opportunities(lead_id);
CREATE INDEX idx_crm_opportunities_account ON crm_opportunities(account_id);
CREATE INDEX idx_crm_opportunities_stage ON crm_opportunities(stage);
CREATE INDEX idx_crm_opportunities_owner ON crm_opportunities(owner_id);
CREATE INDEX idx_crm_opportunities_won ON crm_opportunities(won);
CREATE TABLE IF NOT EXISTS crm_activities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
contact_id UUID REFERENCES crm_contacts(id) ON DELETE CASCADE,
lead_id UUID REFERENCES crm_leads(id) ON DELETE CASCADE,
opportunity_id UUID REFERENCES crm_opportunities(id) ON DELETE CASCADE,
account_id UUID REFERENCES crm_accounts(id) ON DELETE CASCADE,
activity_type VARCHAR(50) NOT NULL,
subject VARCHAR(500),
description TEXT,
due_date TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
outcome VARCHAR(255),
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_crm_activities_org ON crm_activities(org_id);
CREATE INDEX idx_crm_activities_contact ON crm_activities(contact_id);
CREATE INDEX idx_crm_activities_lead ON crm_activities(lead_id);
CREATE INDEX idx_crm_activities_opportunity ON crm_activities(opportunity_id);
CREATE INDEX idx_crm_activities_type ON crm_activities(activity_type);
CREATE INDEX idx_crm_activities_due ON crm_activities(due_date);
CREATE INDEX idx_crm_activities_owner ON crm_activities(owner_id);
CREATE TABLE IF NOT EXISTS crm_notes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
contact_id UUID REFERENCES crm_contacts(id) ON DELETE CASCADE,
lead_id UUID REFERENCES crm_leads(id) ON DELETE CASCADE,
opportunity_id UUID REFERENCES crm_opportunities(id) ON DELETE CASCADE,
account_id UUID REFERENCES crm_accounts(id) ON DELETE CASCADE,
content TEXT NOT NULL,
author_id UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_crm_notes_contact ON crm_notes(contact_id);
CREATE INDEX idx_crm_notes_lead ON crm_notes(lead_id);
CREATE INDEX idx_crm_notes_opportunity ON crm_notes(opportunity_id);
CREATE INDEX idx_crm_notes_account ON crm_notes(account_id);
INSERT INTO crm_pipeline_stages (org_id, bot_id, name, stage_order, probability, is_won, is_lost, color)
SELECT o.org_id, b.id, 'New', 1, 10, FALSE, FALSE, '#94a3b8'
FROM organizations o
CROSS JOIN bots b
LIMIT 1
ON CONFLICT DO NOTHING;
INSERT INTO crm_pipeline_stages (org_id, bot_id, name, stage_order, probability, is_won, is_lost, color)
SELECT o.org_id, b.id, 'Qualified', 2, 25, FALSE, FALSE, '#3b82f6'
FROM organizations o
CROSS JOIN bots b
LIMIT 1
ON CONFLICT DO NOTHING;
INSERT INTO crm_pipeline_stages (org_id, bot_id, name, stage_order, probability, is_won, is_lost, color)
SELECT o.org_id, b.id, 'Proposal', 3, 50, FALSE, FALSE, '#8b5cf6'
FROM organizations o
CROSS JOIN bots b
LIMIT 1
ON CONFLICT DO NOTHING;
INSERT INTO crm_pipeline_stages (org_id, bot_id, name, stage_order, probability, is_won, is_lost, color)
SELECT o.org_id, b.id, 'Negotiation', 4, 75, FALSE, FALSE, '#f59e0b'
FROM organizations o
CROSS JOIN bots b
LIMIT 1
ON CONFLICT DO NOTHING;
INSERT INTO crm_pipeline_stages (org_id, bot_id, name, stage_order, probability, is_won, is_lost, color)
SELECT o.org_id, b.id, 'Won', 5, 100, TRUE, FALSE, '#22c55e'
FROM organizations o
CROSS JOIN bots b
LIMIT 1
ON CONFLICT DO NOTHING;
INSERT INTO crm_pipeline_stages (org_id, bot_id, name, stage_order, probability, is_won, is_lost, color)
SELECT o.org_id, b.id, 'Lost', 6, 0, FALSE, TRUE, '#ef4444'
FROM organizations o
CROSS JOIN bots b
LIMIT 1
ON CONFLICT DO NOTHING;

View file

@ -0,0 +1,30 @@
DROP INDEX IF EXISTS idx_ticket_tags_org_name;
DROP INDEX IF EXISTS idx_ticket_tags_org_bot;
DROP INDEX IF EXISTS idx_ticket_categories_parent;
DROP INDEX IF EXISTS idx_ticket_categories_org_bot;
DROP INDEX IF EXISTS idx_ticket_canned_shortcut;
DROP INDEX IF EXISTS idx_ticket_canned_org_bot;
DROP INDEX IF EXISTS idx_ticket_sla_priority;
DROP INDEX IF EXISTS idx_ticket_sla_org_bot;
DROP INDEX IF EXISTS idx_ticket_comments_created;
DROP INDEX IF EXISTS idx_ticket_comments_ticket;
DROP INDEX IF EXISTS idx_support_tickets_org_number;
DROP INDEX IF EXISTS idx_support_tickets_number;
DROP INDEX IF EXISTS idx_support_tickets_created;
DROP INDEX IF EXISTS idx_support_tickets_requester;
DROP INDEX IF EXISTS idx_support_tickets_assignee;
DROP INDEX IF EXISTS idx_support_tickets_priority;
DROP INDEX IF EXISTS idx_support_tickets_status;
DROP INDEX IF EXISTS idx_support_tickets_org_bot;
DROP TABLE IF EXISTS ticket_tags;
DROP TABLE IF EXISTS ticket_categories;
DROP TABLE IF EXISTS ticket_canned_responses;
DROP TABLE IF EXISTS ticket_sla_policies;
DROP TABLE IF EXISTS ticket_comments;
DROP TABLE IF EXISTS support_tickets;

View file

@ -0,0 +1,113 @@
CREATE TABLE support_tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
ticket_number VARCHAR(50) NOT NULL,
subject VARCHAR(500) NOT NULL,
description TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'open',
priority VARCHAR(50) NOT NULL DEFAULT 'medium',
category VARCHAR(100),
source VARCHAR(50) NOT NULL DEFAULT 'web',
requester_id UUID,
requester_email VARCHAR(255),
requester_name VARCHAR(255),
assignee_id UUID,
team_id UUID,
due_date TIMESTAMPTZ,
first_response_at TIMESTAMPTZ,
resolved_at TIMESTAMPTZ,
closed_at TIMESTAMPTZ,
satisfaction_rating INTEGER,
tags TEXT[] NOT NULL DEFAULT '{}',
custom_fields JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ticket_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_id UUID NOT NULL REFERENCES support_tickets(id) ON DELETE CASCADE,
author_id UUID,
author_name VARCHAR(255),
author_email VARCHAR(255),
content TEXT NOT NULL,
is_internal BOOLEAN NOT NULL DEFAULT FALSE,
attachments JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ticket_sla_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
priority VARCHAR(50) NOT NULL,
first_response_hours INTEGER NOT NULL,
resolution_hours INTEGER NOT NULL,
business_hours_only BOOLEAN NOT NULL DEFAULT TRUE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ticket_canned_responses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
category VARCHAR(100),
shortcut VARCHAR(50),
created_by UUID,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ticket_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
parent_id UUID REFERENCES ticket_categories(id) ON DELETE SET NULL,
color VARCHAR(20),
icon VARCHAR(50),
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ticket_tags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(100) NOT NULL,
color VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_support_tickets_org_bot ON support_tickets(org_id, bot_id);
CREATE INDEX idx_support_tickets_status ON support_tickets(status);
CREATE INDEX idx_support_tickets_priority ON support_tickets(priority);
CREATE INDEX idx_support_tickets_assignee ON support_tickets(assignee_id);
CREATE INDEX idx_support_tickets_requester ON support_tickets(requester_id);
CREATE INDEX idx_support_tickets_created ON support_tickets(created_at DESC);
CREATE INDEX idx_support_tickets_number ON support_tickets(ticket_number);
CREATE UNIQUE INDEX idx_support_tickets_org_number ON support_tickets(org_id, ticket_number);
CREATE INDEX idx_ticket_comments_ticket ON ticket_comments(ticket_id);
CREATE INDEX idx_ticket_comments_created ON ticket_comments(created_at);
CREATE INDEX idx_ticket_sla_org_bot ON ticket_sla_policies(org_id, bot_id);
CREATE INDEX idx_ticket_sla_priority ON ticket_sla_policies(priority);
CREATE INDEX idx_ticket_canned_org_bot ON ticket_canned_responses(org_id, bot_id);
CREATE INDEX idx_ticket_canned_shortcut ON ticket_canned_responses(shortcut);
CREATE INDEX idx_ticket_categories_org_bot ON ticket_categories(org_id, bot_id);
CREATE INDEX idx_ticket_categories_parent ON ticket_categories(parent_id);
CREATE INDEX idx_ticket_tags_org_bot ON ticket_tags(org_id, bot_id);
CREATE UNIQUE INDEX idx_ticket_tags_org_name ON ticket_tags(org_id, bot_id, name);

View file

@ -0,0 +1,36 @@
DROP INDEX IF EXISTS idx_billing_tax_rates_org_bot;
DROP INDEX IF EXISTS idx_billing_recurring_next;
DROP INDEX IF EXISTS idx_billing_recurring_status;
DROP INDEX IF EXISTS idx_billing_recurring_org_bot;
DROP INDEX IF EXISTS idx_billing_quote_items_quote;
DROP INDEX IF EXISTS idx_billing_quotes_number;
DROP INDEX IF EXISTS idx_billing_quotes_valid_until;
DROP INDEX IF EXISTS idx_billing_quotes_customer;
DROP INDEX IF EXISTS idx_billing_quotes_status;
DROP INDEX IF EXISTS idx_billing_quotes_org_bot;
DROP INDEX IF EXISTS idx_billing_payments_number;
DROP INDEX IF EXISTS idx_billing_payments_paid_at;
DROP INDEX IF EXISTS idx_billing_payments_status;
DROP INDEX IF EXISTS idx_billing_payments_invoice;
DROP INDEX IF EXISTS idx_billing_payments_org_bot;
DROP INDEX IF EXISTS idx_billing_invoice_items_invoice;
DROP INDEX IF EXISTS idx_billing_invoices_number;
DROP INDEX IF EXISTS idx_billing_invoices_created;
DROP INDEX IF EXISTS idx_billing_invoices_due_date;
DROP INDEX IF EXISTS idx_billing_invoices_customer;
DROP INDEX IF EXISTS idx_billing_invoices_status;
DROP INDEX IF EXISTS idx_billing_invoices_org_bot;
DROP TABLE IF EXISTS billing_tax_rates;
DROP TABLE IF EXISTS billing_recurring;
DROP TABLE IF EXISTS billing_quote_items;
DROP TABLE IF EXISTS billing_quotes;
DROP TABLE IF EXISTS billing_payments;
DROP TABLE IF EXISTS billing_invoice_items;
DROP TABLE IF EXISTS billing_invoices;

View file

@ -0,0 +1,172 @@
CREATE TABLE billing_invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
invoice_number VARCHAR(50) NOT NULL,
customer_id UUID,
customer_name VARCHAR(255) NOT NULL,
customer_email VARCHAR(255),
customer_address TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'draft',
issue_date DATE NOT NULL,
due_date DATE NOT NULL,
subtotal DECIMAL(15,2) NOT NULL DEFAULT 0,
tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
tax_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
discount_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
total DECIMAL(15,2) NOT NULL DEFAULT 0,
amount_paid DECIMAL(15,2) NOT NULL DEFAULT 0,
amount_due DECIMAL(15,2) NOT NULL DEFAULT 0,
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
notes TEXT,
terms TEXT,
footer TEXT,
paid_at TIMESTAMPTZ,
sent_at TIMESTAMPTZ,
voided_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE billing_invoice_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_id UUID NOT NULL REFERENCES billing_invoices(id) ON DELETE CASCADE,
product_id UUID,
description VARCHAR(500) NOT NULL,
quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
unit_price DECIMAL(15,2) NOT NULL,
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
amount DECIMAL(15,2) NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE billing_payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
invoice_id UUID REFERENCES billing_invoices(id) ON DELETE SET NULL,
payment_number VARCHAR(50) NOT NULL,
amount DECIMAL(15,2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
payment_method VARCHAR(50) NOT NULL DEFAULT 'other',
payment_reference VARCHAR(255),
status VARCHAR(50) NOT NULL DEFAULT 'completed',
payer_name VARCHAR(255),
payer_email VARCHAR(255),
notes TEXT,
paid_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
refunded_at TIMESTAMPTZ,
refund_amount DECIMAL(15,2),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE billing_quotes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
quote_number VARCHAR(50) NOT NULL,
customer_id UUID,
customer_name VARCHAR(255) NOT NULL,
customer_email VARCHAR(255),
customer_address TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'draft',
issue_date DATE NOT NULL,
valid_until DATE NOT NULL,
subtotal DECIMAL(15,2) NOT NULL DEFAULT 0,
tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
tax_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
discount_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
total DECIMAL(15,2) NOT NULL DEFAULT 0,
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
notes TEXT,
terms TEXT,
accepted_at TIMESTAMPTZ,
rejected_at TIMESTAMPTZ,
converted_invoice_id UUID REFERENCES billing_invoices(id) ON DELETE SET NULL,
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE billing_quote_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
quote_id UUID NOT NULL REFERENCES billing_quotes(id) ON DELETE CASCADE,
product_id UUID,
description VARCHAR(500) NOT NULL,
quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
unit_price DECIMAL(15,2) NOT NULL,
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
amount DECIMAL(15,2) NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE billing_recurring (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
customer_id UUID,
customer_name VARCHAR(255) NOT NULL,
customer_email VARCHAR(255),
status VARCHAR(50) NOT NULL DEFAULT 'active',
frequency VARCHAR(50) NOT NULL DEFAULT 'monthly',
interval_count INTEGER NOT NULL DEFAULT 1,
amount DECIMAL(15,2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
description TEXT,
next_invoice_date DATE NOT NULL,
last_invoice_date DATE,
last_invoice_id UUID REFERENCES billing_invoices(id) ON DELETE SET NULL,
start_date DATE NOT NULL,
end_date DATE,
invoices_generated INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE billing_tax_rates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
bot_id UUID NOT NULL,
name VARCHAR(100) NOT NULL,
rate DECIMAL(5,2) NOT NULL,
description TEXT,
region VARCHAR(100),
is_default BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_billing_invoices_org_bot ON billing_invoices(org_id, bot_id);
CREATE INDEX idx_billing_invoices_status ON billing_invoices(status);
CREATE INDEX idx_billing_invoices_customer ON billing_invoices(customer_id);
CREATE INDEX idx_billing_invoices_due_date ON billing_invoices(due_date);
CREATE INDEX idx_billing_invoices_created ON billing_invoices(created_at DESC);
CREATE UNIQUE INDEX idx_billing_invoices_number ON billing_invoices(org_id, invoice_number);
CREATE INDEX idx_billing_invoice_items_invoice ON billing_invoice_items(invoice_id);
CREATE INDEX idx_billing_payments_org_bot ON billing_payments(org_id, bot_id);
CREATE INDEX idx_billing_payments_invoice ON billing_payments(invoice_id);
CREATE INDEX idx_billing_payments_status ON billing_payments(status);
CREATE INDEX idx_billing_payments_paid_at ON billing_payments(paid_at DESC);
CREATE UNIQUE INDEX idx_billing_payments_number ON billing_payments(org_id, payment_number);
CREATE INDEX idx_billing_quotes_org_bot ON billing_quotes(org_id, bot_id);
CREATE INDEX idx_billing_quotes_status ON billing_quotes(status);
CREATE INDEX idx_billing_quotes_customer ON billing_quotes(customer_id);
CREATE INDEX idx_billing_quotes_valid_until ON billing_quotes(valid_until);
CREATE UNIQUE INDEX idx_billing_quotes_number ON billing_quotes(org_id, quote_number);
CREATE INDEX idx_billing_quote_items_quote ON billing_quote_items(quote_id);
CREATE INDEX idx_billing_recurring_org_bot ON billing_recurring(org_id, bot_id);
CREATE INDEX idx_billing_recurring_status ON billing_recurring(status);
CREATE INDEX idx_billing_recurring_next ON billing_recurring(next_invoice_date);
CREATE INDEX idx_billing_tax_rates_org_bot ON billing_tax_rates(org_id, bot_id);

View file

@ -0,0 +1,3 @@
DROP TABLE IF EXISTS workflow_step_executions;
DROP TABLE IF EXISTS workflow_executions;
DROP TABLE IF EXISTS workflow_definitions;

View file

@ -0,0 +1,61 @@
-- Workflow definitions (Automation/Tasks)
CREATE TABLE IF NOT EXISTS workflow_definitions (
id UUID PRIMARY KEY,
bot_id UUID NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
steps JSONB NOT NULL DEFAULT '[]',
triggers JSONB NOT NULL DEFAULT '[]',
error_handling JSONB NOT NULL DEFAULT '{}',
enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
UNIQUE(bot_id, name)
);
-- Workflow executions
CREATE TABLE IF NOT EXISTS workflow_executions (
id UUID PRIMARY KEY,
workflow_id UUID NOT NULL REFERENCES workflow_definitions(id) ON DELETE CASCADE,
bot_id UUID NOT NULL,
session_id UUID,
initiated_by UUID,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
current_step INTEGER NOT NULL DEFAULT 0,
input_data JSONB NOT NULL DEFAULT '{}',
output_data JSONB NOT NULL DEFAULT '{}',
step_results JSONB NOT NULL DEFAULT '[]',
error TEXT,
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
completed_at TIMESTAMP WITH TIME ZONE,
metadata JSONB NOT NULL DEFAULT '{}'
);
-- Workflow step executions
CREATE TABLE IF NOT EXISTS workflow_step_executions (
id UUID PRIMARY KEY,
execution_id UUID NOT NULL REFERENCES workflow_executions(id) ON DELETE CASCADE,
step_name VARCHAR(200) NOT NULL,
step_index INTEGER NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
input_data JSONB NOT NULL DEFAULT '{}',
output_data JSONB NOT NULL DEFAULT '{}',
error TEXT,
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
completed_at TIMESTAMP WITH TIME ZONE,
duration_ms BIGINT
);
-- Indexes for workflow tables
CREATE INDEX IF NOT EXISTS idx_workflow_definitions_bot_id ON workflow_definitions(bot_id);
CREATE INDEX IF NOT EXISTS idx_workflow_executions_workflow_id ON workflow_executions(workflow_id);
CREATE INDEX IF NOT EXISTS idx_workflow_executions_bot_id ON workflow_executions(bot_id);
CREATE INDEX IF NOT EXISTS idx_workflow_executions_status ON workflow_executions(status);
CREATE INDEX IF NOT EXISTS idx_workflow_step_executions_execution_id ON workflow_step_executions(execution_id);
DROP TRIGGER IF EXISTS update_workflow_definitions_updated_at ON workflow_definitions;
CREATE TRIGGER update_workflow_definitions_updated_at
BEFORE UPDATE ON workflow_definitions
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

View file

@ -0,0 +1,3 @@
DROP TABLE IF EXISTS calendar_shares;
DROP TABLE IF EXISTS calendar_resource_bookings;
DROP TABLE IF EXISTS calendar_resources;

View file

@ -0,0 +1,52 @@
-- Legacy Calendar Tables extracted from consolidated
-- Resource booking (meeting rooms, equipment)
CREATE TABLE IF NOT EXISTS calendar_resources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
resource_type VARCHAR(50) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
location VARCHAR(255),
capacity INTEGER,
amenities_json TEXT DEFAULT '[]',
availability_hours_json TEXT,
booking_rules_json TEXT DEFAULT '{}',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_resource_type CHECK (resource_type IN ('room', 'equipment', 'vehicle', 'other'))
);
CREATE INDEX IF NOT EXISTS idx_calendar_resources_bot ON calendar_resources(bot_id);
CREATE INDEX IF NOT EXISTS idx_calendar_resources_type ON calendar_resources(bot_id, resource_type);
CREATE TABLE IF NOT EXISTS calendar_resource_bookings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
resource_id UUID NOT NULL REFERENCES calendar_resources(id) ON DELETE CASCADE,
event_id UUID,
booked_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ NOT NULL,
notes TEXT,
status VARCHAR(20) DEFAULT 'confirmed',
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_booking_status CHECK (status IN ('pending', 'confirmed', 'cancelled'))
);
CREATE INDEX IF NOT EXISTS idx_resource_bookings_resource ON calendar_resource_bookings(resource_id, start_time, end_time);
CREATE INDEX IF NOT EXISTS idx_resource_bookings_user ON calendar_resource_bookings(booked_by);
-- Calendar sharing (skip - already exists from 6.0.13-01-calendar)
-- CREATE TABLE IF NOT EXISTS calendar_shares (
-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- shared_with_user UUID REFERENCES users(id) ON DELETE CASCADE,
-- shared_with_email VARCHAR(255),
-- permission_level VARCHAR(20) DEFAULT 'view',
-- created_at TIMESTAMPTZ DEFAULT NOW(),
-- CONSTRAINT check_cal_permission CHECK (permission_level IN ('free_busy', 'view', 'edit', 'admin'))
-- );
-- CREATE INDEX IF NOT EXISTS idx_calendar_shares_owner ON calendar_shares(owner_id);
-- CREATE INDEX IF NOT EXISTS idx_calendar_shares_shared ON calendar_shares(shared_with_user);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS designer_dialogs;

View file

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS designer_dialogs (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
bot_id TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
is_active BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_designer_dialogs_bot ON designer_dialogs(bot_id);
CREATE INDEX IF NOT EXISTS idx_designer_dialogs_active ON designer_dialogs(is_active);
CREATE INDEX IF NOT EXISTS idx_designer_dialogs_updated ON designer_dialogs(updated_at DESC);

View file

@ -0,0 +1,10 @@
DROP TABLE IF EXISTS folder_change_events;
DROP TABLE IF EXISTS folder_monitors;
DROP TABLE IF EXISTS document_presence;
DROP TABLE IF EXISTS storage_quotas;
DROP TABLE IF EXISTS file_sync_status;
DROP TABLE IF EXISTS file_trash;
DROP TABLE IF EXISTS file_activities;
DROP TABLE IF EXISTS file_shares;
DROP TABLE IF EXISTS file_comments;
DROP TABLE IF EXISTS file_versions;

View file

@ -0,0 +1,186 @@
-- Legacy Drive Tables extracted from consolidated
-- File version history
CREATE TABLE IF NOT EXISTS file_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_id UUID NOT NULL,
version_number INTEGER NOT NULL,
file_path TEXT NOT NULL,
file_size BIGINT NOT NULL,
file_hash VARCHAR(64) NOT NULL,
modified_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
change_summary TEXT,
is_current BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_file_version UNIQUE (file_id, version_number)
);
CREATE INDEX IF NOT EXISTS idx_file_versions_file ON file_versions(file_id);
CREATE INDEX IF NOT EXISTS idx_file_versions_current ON file_versions(file_id) WHERE is_current = true;
-- File comments
CREATE TABLE IF NOT EXISTS file_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_id UUID NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
parent_id UUID REFERENCES file_comments(id) ON DELETE CASCADE,
content TEXT NOT NULL,
anchor_data_json TEXT,
is_resolved BOOLEAN DEFAULT false,
resolved_by UUID REFERENCES users(id) ON DELETE SET NULL,
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_file_comments_file ON file_comments(file_id);
CREATE INDEX IF NOT EXISTS idx_file_comments_unresolved ON file_comments(file_id) WHERE is_resolved = false;
-- File sharing permissions
CREATE TABLE IF NOT EXISTS file_shares (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_id UUID NOT NULL,
shared_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
shared_with_user UUID REFERENCES users(id) ON DELETE CASCADE,
shared_with_email VARCHAR(255),
shared_with_group UUID,
permission_level VARCHAR(20) NOT NULL DEFAULT 'view',
can_reshare BOOLEAN DEFAULT false,
password_hash VARCHAR(255),
expires_at TIMESTAMPTZ,
link_token VARCHAR(64) UNIQUE,
access_count INTEGER DEFAULT 0,
last_accessed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_share_permission CHECK (permission_level IN ('view', 'comment', 'edit', 'admin'))
);
CREATE INDEX IF NOT EXISTS idx_file_shares_file ON file_shares(file_id);
CREATE INDEX IF NOT EXISTS idx_file_shares_user ON file_shares(shared_with_user);
CREATE INDEX IF NOT EXISTS idx_file_shares_token ON file_shares(link_token) WHERE link_token IS NOT NULL;
-- File activity log
CREATE TABLE IF NOT EXISTS file_activities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_id UUID NOT NULL,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
activity_type VARCHAR(50) NOT NULL,
details_json TEXT DEFAULT '{}',
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_file_activities_file ON file_activities(file_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_file_activities_user ON file_activities(user_id, created_at DESC);
-- Trash bin (soft delete with restore)
CREATE TABLE IF NOT EXISTS file_trash (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
original_file_id UUID NOT NULL,
original_path TEXT NOT NULL,
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
file_metadata_json TEXT NOT NULL,
deleted_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
deleted_at TIMESTAMPTZ DEFAULT NOW(),
permanent_delete_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_file_trash_owner ON file_trash(owner_id);
CREATE INDEX IF NOT EXISTS idx_file_trash_expiry ON file_trash(permanent_delete_at);
-- Offline sync tracking
CREATE TABLE IF NOT EXISTS file_sync_status (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
device_id VARCHAR(255) NOT NULL,
file_id UUID NOT NULL,
local_path TEXT,
sync_status VARCHAR(20) DEFAULT 'synced',
local_version INTEGER,
remote_version INTEGER,
conflict_data_json TEXT,
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_sync_status CHECK (sync_status IN ('synced', 'pending', 'conflict', 'error')),
CONSTRAINT unique_sync_entry UNIQUE (user_id, device_id, file_id)
);
CREATE INDEX IF NOT EXISTS idx_file_sync_user ON file_sync_status(user_id, device_id);
CREATE INDEX IF NOT EXISTS idx_file_sync_pending ON file_sync_status(user_id) WHERE sync_status = 'pending';
-- Storage quotas
CREATE TABLE IF NOT EXISTS storage_quotas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID REFERENCES bots(id) ON DELETE CASCADE,
quota_bytes BIGINT NOT NULL DEFAULT 5368709120,
used_bytes BIGINT NOT NULL DEFAULT 0,
warning_threshold_percent INTEGER DEFAULT 90,
last_calculated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_user_quota UNIQUE (user_id),
CONSTRAINT unique_bot_quota UNIQUE (bot_id)
);
CREATE INDEX IF NOT EXISTS idx_storage_quotas_user ON storage_quotas(user_id);
-- Document presence (who's viewing/editing)
CREATE TABLE IF NOT EXISTS document_presence (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
document_id UUID NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
cursor_position_json TEXT,
selection_range_json TEXT,
color VARCHAR(7),
is_editing BOOLEAN DEFAULT false,
last_activity TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_doc_user_presence UNIQUE (document_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_document_presence_doc ON document_presence(document_id);
-- Folder monitoring table for ON CHANGE triggers (GDrive, OneDrive, Dropbox)
CREATE TABLE IF NOT EXISTS folder_monitors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
provider VARCHAR(50) NOT NULL, -- 'gdrive', 'onedrive', 'dropbox', 'local'
account_email VARCHAR(500), -- Email from account:// path (e.g., user@gmail.com)
folder_path VARCHAR(2000) NOT NULL,
folder_id VARCHAR(500), -- Provider-specific folder ID
script_path VARCHAR(1000) NOT NULL,
is_active BOOLEAN DEFAULT true,
watch_subfolders BOOLEAN DEFAULT true,
last_check_at TIMESTAMPTZ,
last_change_token VARCHAR(500), -- Provider-specific change token/page token
event_types_json TEXT DEFAULT '["create", "modify", "delete"]',
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
CONSTRAINT unique_bot_folder UNIQUE (bot_id, provider, folder_path)
);
CREATE INDEX IF NOT EXISTS idx_folder_monitors_bot_id ON folder_monitors(bot_id);
CREATE INDEX IF NOT EXISTS idx_folder_monitors_provider ON folder_monitors(provider);
CREATE INDEX IF NOT EXISTS idx_folder_monitors_active ON folder_monitors(is_active) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS idx_folder_monitors_account_email ON folder_monitors(account_email);
-- Folder change events log
CREATE TABLE IF NOT EXISTS folder_change_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
monitor_id UUID NOT NULL REFERENCES folder_monitors(id) ON DELETE CASCADE,
event_type VARCHAR(50) NOT NULL, -- 'create', 'modify', 'delete', 'rename', 'move'
file_path VARCHAR(2000) NOT NULL,
file_id VARCHAR(500),
file_name VARCHAR(500),
file_size BIGINT,
mime_type VARCHAR(255),
old_path VARCHAR(2000), -- For rename/move events
processed BOOLEAN DEFAULT false,
processed_at TIMESTAMPTZ,
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_folder_events_monitor ON folder_change_events(monitor_id);
CREATE INDEX IF NOT EXISTS idx_folder_events_processed ON folder_change_events(processed) WHERE processed = false;
CREATE INDEX IF NOT EXISTS idx_folder_events_created ON folder_change_events(created_at);

View file

@ -0,0 +1,7 @@
DROP TABLE IF EXISTS bot_reflections;
DROP TABLE IF EXISTS episodic_memories;
DROP TABLE IF EXISTS llm_traces;
DROP TABLE IF EXISTS llm_budget;
DROP TABLE IF EXISTS llm_metrics_hourly;
DROP TABLE IF EXISTS llm_metrics;
DROP TABLE IF EXISTS conversation_costs;

View file

@ -0,0 +1,167 @@
-- LLM Feature Tables (Observability, Costs, Episodic Memory)
-- Conversation Cost Tracking
CREATE TABLE IF NOT EXISTS conversation_costs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL,
user_id UUID NOT NULL,
bot_id UUID NOT NULL,
model_used VARCHAR(100),
input_tokens INTEGER NOT NULL DEFAULT 0,
output_tokens INTEGER NOT NULL DEFAULT 0,
cost_usd DECIMAL(10, 6) NOT NULL DEFAULT 0,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_conv_costs_session ON conversation_costs(session_id);
CREATE INDEX IF NOT EXISTS idx_conv_costs_user ON conversation_costs(user_id);
CREATE INDEX IF NOT EXISTS idx_conv_costs_bot ON conversation_costs(bot_id);
CREATE INDEX IF NOT EXISTS idx_conv_costs_time ON conversation_costs(timestamp);
-- LLM Metrics
CREATE TABLE IF NOT EXISTS llm_metrics (
id UUID PRIMARY KEY,
request_id UUID NOT NULL,
session_id UUID NOT NULL,
bot_id UUID NOT NULL,
model VARCHAR(200) NOT NULL,
request_type VARCHAR(50) NOT NULL,
input_tokens BIGINT NOT NULL DEFAULT 0,
output_tokens BIGINT NOT NULL DEFAULT 0,
total_tokens BIGINT NOT NULL DEFAULT 0,
latency_ms BIGINT NOT NULL DEFAULT 0,
ttft_ms BIGINT,
cached BOOLEAN NOT NULL DEFAULT false,
success BOOLEAN NOT NULL DEFAULT true,
error TEXT,
estimated_cost DOUBLE PRECISION NOT NULL DEFAULT 0,
timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX IF NOT EXISTS idx_llm_metrics_bot_id ON llm_metrics(bot_id);
CREATE INDEX IF NOT EXISTS idx_llm_metrics_session_id ON llm_metrics(session_id);
CREATE INDEX IF NOT EXISTS idx_llm_metrics_timestamp ON llm_metrics(timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_llm_metrics_model ON llm_metrics(model);
-- LLM Metrics Hourly
CREATE TABLE IF NOT EXISTS llm_metrics_hourly (
id UUID PRIMARY KEY,
bot_id UUID NOT NULL,
hour TIMESTAMP WITH TIME ZONE NOT NULL,
total_requests BIGINT NOT NULL DEFAULT 0,
successful_requests BIGINT NOT NULL DEFAULT 0,
failed_requests BIGINT NOT NULL DEFAULT 0,
cache_hits BIGINT NOT NULL DEFAULT 0,
cache_misses BIGINT NOT NULL DEFAULT 0,
total_input_tokens BIGINT NOT NULL DEFAULT 0,
total_output_tokens BIGINT NOT NULL DEFAULT 0,
total_tokens BIGINT NOT NULL DEFAULT 0,
total_cost DOUBLE PRECISION NOT NULL DEFAULT 0,
avg_latency_ms DOUBLE PRECISION NOT NULL DEFAULT 0,
p50_latency_ms DOUBLE PRECISION NOT NULL DEFAULT 0,
p95_latency_ms DOUBLE PRECISION NOT NULL DEFAULT 0,
p99_latency_ms DOUBLE PRECISION NOT NULL DEFAULT 0,
max_latency_ms BIGINT NOT NULL DEFAULT 0,
min_latency_ms BIGINT NOT NULL DEFAULT 0,
requests_by_model JSONB NOT NULL DEFAULT '{}',
tokens_by_model JSONB NOT NULL DEFAULT '{}',
cost_by_model JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
UNIQUE(bot_id, hour)
);
CREATE INDEX IF NOT EXISTS idx_llm_metrics_hourly_bot_id ON llm_metrics_hourly(bot_id);
CREATE INDEX IF NOT EXISTS idx_llm_metrics_hourly_hour ON llm_metrics_hourly(hour DESC);
-- LLM Budget
CREATE TABLE IF NOT EXISTS llm_budget (
id UUID PRIMARY KEY,
bot_id UUID NOT NULL UNIQUE,
daily_limit DOUBLE PRECISION NOT NULL DEFAULT 100,
monthly_limit DOUBLE PRECISION NOT NULL DEFAULT 2000,
alert_threshold DOUBLE PRECISION NOT NULL DEFAULT 0.8,
daily_spend DOUBLE PRECISION NOT NULL DEFAULT 0,
monthly_spend DOUBLE PRECISION NOT NULL DEFAULT 0,
daily_reset_date DATE NOT NULL DEFAULT CURRENT_DATE,
monthly_reset_date DATE NOT NULL DEFAULT DATE_TRUNC('month', CURRENT_DATE)::DATE,
daily_alert_sent BOOLEAN NOT NULL DEFAULT false,
monthly_alert_sent BOOLEAN NOT NULL DEFAULT false,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- LLM Traces
CREATE TABLE IF NOT EXISTS llm_traces (
id UUID PRIMARY KEY,
parent_id UUID,
trace_id UUID NOT NULL,
name VARCHAR(200) NOT NULL,
component VARCHAR(100) NOT NULL,
event_type VARCHAR(50) NOT NULL,
duration_ms BIGINT,
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
end_time TIMESTAMP WITH TIME ZONE,
attributes JSONB NOT NULL DEFAULT '{}',
status VARCHAR(50) NOT NULL DEFAULT 'in_progress',
error TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_llm_traces_trace_id ON llm_traces(trace_id);
CREATE INDEX IF NOT EXISTS idx_llm_traces_start_time ON llm_traces(start_time DESC);
CREATE INDEX IF NOT EXISTS idx_llm_traces_component ON llm_traces(component);
-- Episodic Memories
CREATE TABLE IF NOT EXISTS episodic_memories (
id UUID PRIMARY KEY,
bot_id UUID NOT NULL,
user_id UUID NOT NULL,
session_id UUID,
summary TEXT NOT NULL,
key_topics JSONB DEFAULT '[]',
decisions JSONB DEFAULT '[]',
action_items JSONB DEFAULT '[]',
message_count INTEGER NOT NULL DEFAULT 0,
start_timestamp TIMESTAMPTZ NOT NULL,
end_timestamp TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_episodic_bot ON episodic_memories(bot_id);
CREATE INDEX IF NOT EXISTS idx_episodic_user ON episodic_memories(user_id);
CREATE INDEX IF NOT EXISTS idx_episodic_session ON episodic_memories(session_id);
CREATE INDEX IF NOT EXISTS idx_episodic_time ON episodic_memories(bot_id, user_id, created_at);
-- Bot Reflections
CREATE TABLE IF NOT EXISTS bot_reflections (
id UUID PRIMARY KEY,
bot_id UUID NOT NULL,
session_id UUID NOT NULL,
reflection_type TEXT NOT NULL,
score FLOAT NOT NULL DEFAULT 0.0,
insights TEXT NOT NULL DEFAULT '[]',
improvements TEXT NOT NULL DEFAULT '[]',
positive_patterns TEXT NOT NULL DEFAULT '[]',
concerns TEXT NOT NULL DEFAULT '[]',
raw_response TEXT NOT NULL DEFAULT '',
messages_analyzed INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_bot_reflections_bot ON bot_reflections(bot_id);
CREATE INDEX IF NOT EXISTS idx_bot_reflections_session ON bot_reflections(session_id);
CREATE INDEX IF NOT EXISTS idx_bot_reflections_time ON bot_reflections(bot_id, created_at);
-- Triggers for Updated At (e.g. Budget)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS update_llm_budget_updated_at ON llm_budget;
CREATE TRIGGER update_llm_budget_updated_at
BEFORE UPDATE ON llm_budget
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

View file

@ -0,0 +1,20 @@
DROP TABLE IF EXISTS email_received_events;
DROP TABLE IF EXISTS email_monitors;
DROP TABLE IF EXISTS sent_email_tracking;
-- Remove active_email_account_id from user_sessions
ALTER TABLE user_sessions DROP CONSTRAINT IF EXISTS user_sessions_email_account_id_fkey;
ALTER TABLE user_sessions DROP COLUMN IF EXISTS active_email_account_id;
DROP TABLE IF EXISTS email_folders;
DROP TABLE IF EXISTS email_drafts;
DROP TABLE IF EXISTS user_email_accounts;
DROP TABLE IF EXISTS shared_mailbox_members;
DROP TABLE IF EXISTS shared_mailboxes;
DROP TABLE IF EXISTS distribution_lists;
DROP TABLE IF EXISTS email_label_assignments;
DROP TABLE IF EXISTS email_labels;
DROP TABLE IF EXISTS email_rules;
DROP TABLE IF EXISTS email_auto_responders;
DROP TABLE IF EXISTS email_templates;
DROP TABLE IF EXISTS scheduled_emails;
DROP TABLE IF EXISTS email_signatures;
DROP TABLE IF EXISTS global_email_signatures;

View file

@ -0,0 +1,361 @@
-- Legacy Mail Tables extracted from consolidated
-- Global email signature (applied to all emails from this bot)
CREATE TABLE IF NOT EXISTS global_email_signatures (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL DEFAULT 'Default',
content_html TEXT NOT NULL,
content_plain TEXT NOT NULL,
position VARCHAR(20) DEFAULT 'bottom',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_bot_global_signature UNIQUE (bot_id, name),
CONSTRAINT check_signature_position CHECK (position IN ('top', 'bottom'))
);
CREATE INDEX IF NOT EXISTS idx_global_signatures_bot ON global_email_signatures(bot_id) WHERE is_active = true;
-- User email signatures (in addition to global)
CREATE TABLE IF NOT EXISTS email_signatures (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID REFERENCES bots(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL DEFAULT 'Default',
content_html TEXT NOT NULL,
content_plain TEXT NOT NULL,
is_default BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_user_signature_name UNIQUE (user_id, bot_id, name)
);
CREATE INDEX IF NOT EXISTS idx_email_signatures_user ON email_signatures(user_id);
CREATE INDEX IF NOT EXISTS idx_email_signatures_default ON email_signatures(user_id, bot_id) WHERE is_default = true;
-- Scheduled emails (send later)
CREATE TABLE IF NOT EXISTS scheduled_emails (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
to_addresses TEXT NOT NULL,
cc_addresses TEXT,
bcc_addresses TEXT,
subject TEXT NOT NULL,
body_html TEXT NOT NULL,
body_plain TEXT,
attachments_json TEXT DEFAULT '[]',
scheduled_at TIMESTAMPTZ NOT NULL,
sent_at TIMESTAMPTZ,
status VARCHAR(20) DEFAULT 'pending',
retry_count INTEGER DEFAULT 0,
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_scheduled_status CHECK (status IN ('pending', 'sent', 'failed', 'cancelled'))
);
CREATE INDEX IF NOT EXISTS idx_scheduled_emails_pending ON scheduled_emails(scheduled_at) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_scheduled_emails_user ON scheduled_emails(user_id, bot_id);
-- Email templates
CREATE TABLE IF NOT EXISTS email_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
subject_template TEXT NOT NULL,
body_html_template TEXT NOT NULL,
body_plain_template TEXT,
variables_json TEXT DEFAULT '[]',
category VARCHAR(100),
is_shared BOOLEAN DEFAULT false,
usage_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_email_templates_bot ON email_templates(bot_id);
CREATE INDEX IF NOT EXISTS idx_email_templates_category ON email_templates(category);
CREATE INDEX IF NOT EXISTS idx_email_templates_shared ON email_templates(bot_id) WHERE is_shared = true;
-- Auto-responders (Out of Office) - works with Stalwart Sieve
CREATE TABLE IF NOT EXISTS email_auto_responders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
responder_type VARCHAR(50) NOT NULL DEFAULT 'out_of_office',
subject TEXT NOT NULL,
body_html TEXT NOT NULL,
body_plain TEXT,
start_date TIMESTAMPTZ,
end_date TIMESTAMPTZ,
send_to_internal_only BOOLEAN DEFAULT false,
exclude_addresses TEXT,
is_active BOOLEAN DEFAULT false,
stalwart_sieve_id VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_responder_type CHECK (responder_type IN ('out_of_office', 'vacation', 'custom')),
CONSTRAINT unique_user_responder UNIQUE (user_id, bot_id, responder_type)
);
CREATE INDEX IF NOT EXISTS idx_auto_responders_active ON email_auto_responders(user_id, bot_id) WHERE is_active = true;
-- Email rules/filters - synced with Stalwart Sieve
CREATE TABLE IF NOT EXISTS email_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
priority INTEGER DEFAULT 0,
conditions_json TEXT NOT NULL,
actions_json TEXT NOT NULL,
stop_processing BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
stalwart_sieve_id VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_email_rules_user ON email_rules(user_id, bot_id);
CREATE INDEX IF NOT EXISTS idx_email_rules_priority ON email_rules(user_id, bot_id, priority);
-- Email labels/categories
CREATE TABLE IF NOT EXISTS email_labels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
color VARCHAR(7) DEFAULT '#3b82f6',
parent_id UUID REFERENCES email_labels(id) ON DELETE CASCADE,
is_system BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_user_label UNIQUE (user_id, bot_id, name)
);
CREATE INDEX IF NOT EXISTS idx_email_labels_user ON email_labels(user_id, bot_id);
-- Email-label associations
CREATE TABLE IF NOT EXISTS email_label_assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email_message_id VARCHAR(255) NOT NULL,
label_id UUID NOT NULL REFERENCES email_labels(id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_email_label UNIQUE (email_message_id, label_id)
);
CREATE INDEX IF NOT EXISTS idx_label_assignments_email ON email_label_assignments(email_message_id);
CREATE INDEX IF NOT EXISTS idx_label_assignments_label ON email_label_assignments(label_id);
-- Distribution lists
CREATE TABLE IF NOT EXISTS distribution_lists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
email_alias VARCHAR(255),
description TEXT,
members_json TEXT NOT NULL DEFAULT '[]',
is_public BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_distribution_lists_bot ON distribution_lists(bot_id);
CREATE INDEX IF NOT EXISTS idx_distribution_lists_owner ON distribution_lists(owner_id);
-- Shared mailboxes - managed via Stalwart
CREATE TABLE IF NOT EXISTS shared_mailboxes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
email_address VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL,
description TEXT,
settings_json TEXT DEFAULT '{}',
stalwart_account_id VARCHAR(255),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_shared_mailbox_email UNIQUE (bot_id, email_address)
);
CREATE INDEX IF NOT EXISTS idx_shared_mailboxes_bot ON shared_mailboxes(bot_id);
CREATE TABLE IF NOT EXISTS shared_mailbox_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
mailbox_id UUID NOT NULL REFERENCES shared_mailboxes(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
permission_level VARCHAR(20) DEFAULT 'read',
added_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_mailbox_member UNIQUE (mailbox_id, user_id),
CONSTRAINT check_permission CHECK (permission_level IN ('read', 'write', 'admin'))
);
CREATE INDEX IF NOT EXISTS idx_shared_mailbox_members ON shared_mailbox_members(mailbox_id);
CREATE INDEX IF NOT EXISTS idx_shared_mailbox_user ON shared_mailbox_members(user_id);
-- Add user_email_accounts table for storing user email credentials
CREATE TABLE IF NOT EXISTS user_email_accounts (
id uuid DEFAULT gen_random_uuid() NOT NULL,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
email varchar(255) NOT NULL,
display_name varchar(255) NULL,
imap_server varchar(255) NOT NULL,
imap_port int4 DEFAULT 993 NOT NULL,
smtp_server varchar(255) NOT NULL,
smtp_port int4 DEFAULT 587 NOT NULL,
username varchar(255) NOT NULL,
password_encrypted text NOT NULL,
is_primary bool DEFAULT false NOT NULL,
is_active bool DEFAULT true NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT user_email_accounts_pkey PRIMARY KEY (id),
CONSTRAINT user_email_accounts_user_email_key UNIQUE (user_id, email)
);
CREATE INDEX IF NOT EXISTS idx_user_email_accounts_user_id ON user_email_accounts(user_id);
CREATE INDEX IF NOT EXISTS idx_user_email_accounts_active ON user_email_accounts(is_active) WHERE is_active;
-- Add email drafts table
CREATE TABLE IF NOT EXISTS email_drafts (
id uuid DEFAULT gen_random_uuid() NOT NULL,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
account_id uuid NOT NULL REFERENCES user_email_accounts(id) ON DELETE CASCADE,
to_address text NOT NULL,
cc_address text NULL,
bcc_address text NULL,
subject varchar(500) NULL,
body text NULL,
attachments jsonb DEFAULT '[]'::jsonb NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT email_drafts_pkey PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_email_drafts_user_id ON email_drafts(user_id);
CREATE INDEX IF NOT EXISTS idx_email_drafts_account_id ON email_drafts(account_id);
-- Add email folders metadata table (for caching and custom folders)
CREATE TABLE IF NOT EXISTS email_folders (
id uuid DEFAULT gen_random_uuid() NOT NULL,
account_id uuid NOT NULL REFERENCES user_email_accounts(id) ON DELETE CASCADE,
folder_name varchar(255) NOT NULL,
folder_path varchar(500) NOT NULL,
unread_count int4 DEFAULT 0 NOT NULL,
total_count int4 DEFAULT 0 NOT NULL,
last_synced timestamptz NULL,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT email_folders_pkey PRIMARY KEY (id),
CONSTRAINT email_folders_account_path_key UNIQUE (account_id, folder_path)
);
CREATE INDEX IF NOT EXISTS idx_email_folders_account_id ON email_folders(account_id);
-- Add sessions table enhancement for storing current email account
-- Check if column exists, if not add it (idempotent)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'user_sessions' AND column_name = 'active_email_account_id'
) THEN
ALTER TABLE user_sessions ADD COLUMN active_email_account_id uuid NULL;
ALTER TABLE user_sessions ADD CONSTRAINT user_sessions_email_account_id_fkey
FOREIGN KEY (active_email_account_id) REFERENCES user_email_accounts(id) ON DELETE SET NULL;
END IF;
END $$;
-- Email Read Tracking Table
CREATE TABLE IF NOT EXISTS sent_email_tracking (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tracking_id UUID NOT NULL UNIQUE,
bot_id UUID NOT NULL,
account_id UUID NOT NULL,
from_email VARCHAR(255) NOT NULL,
to_email VARCHAR(255) NOT NULL,
cc TEXT,
bcc TEXT,
subject TEXT NOT NULL,
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
is_read BOOLEAN NOT NULL DEFAULT FALSE,
read_at TIMESTAMPTZ,
read_count INTEGER NOT NULL DEFAULT 0,
first_read_ip VARCHAR(45),
last_read_ip VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Indexes for efficient queries
CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_tracking_id ON sent_email_tracking(tracking_id);
CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_bot_id ON sent_email_tracking(bot_id);
CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_account_id ON sent_email_tracking(account_id);
CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_to_email ON sent_email_tracking(to_email);
CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_sent_at ON sent_email_tracking(sent_at DESC);
CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_is_read ON sent_email_tracking(is_read);
CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_read_status ON sent_email_tracking(bot_id, is_read, sent_at DESC);
-- Trigger to auto-update updated_at for tracking
CREATE OR REPLACE FUNCTION update_sent_email_tracking_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_update_sent_email_tracking_updated_at ON sent_email_tracking;
CREATE TRIGGER trigger_update_sent_email_tracking_updated_at
BEFORE UPDATE ON sent_email_tracking
FOR EACH ROW
EXECUTE FUNCTION update_sent_email_tracking_updated_at();
-- Add comment for documentation
COMMENT ON TABLE sent_email_tracking IS 'Tracks sent emails for read receipt functionality via tracking pixel';
-- Email monitoring table for ON EMAIL triggers
CREATE TABLE IF NOT EXISTS email_monitors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
email_address VARCHAR(500) NOT NULL,
script_path VARCHAR(1000) NOT NULL,
is_active BOOLEAN DEFAULT true,
last_check_at TIMESTAMPTZ,
last_uid BIGINT DEFAULT 0,
filter_from VARCHAR(500),
filter_subject VARCHAR(500),
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
CONSTRAINT unique_bot_email UNIQUE (bot_id, email_address)
);
CREATE INDEX IF NOT EXISTS idx_email_monitors_bot_id ON email_monitors(bot_id);
CREATE INDEX IF NOT EXISTS idx_email_monitors_email ON email_monitors(email_address);
CREATE INDEX IF NOT EXISTS idx_email_monitors_active ON email_monitors(is_active) WHERE is_active = true;
-- Email received events log
CREATE TABLE IF NOT EXISTS email_received_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
monitor_id UUID NOT NULL REFERENCES email_monitors(id) ON DELETE CASCADE,
message_uid BIGINT NOT NULL,
message_id VARCHAR(500),
from_address VARCHAR(500) NOT NULL,
to_addresses_json TEXT,
subject VARCHAR(1000),
received_at TIMESTAMPTZ,
has_attachments BOOLEAN DEFAULT false,
content_preview TEXT,
processed BOOLEAN DEFAULT false,
processed_at TIMESTAMPTZ,
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_email_events_monitor ON email_received_events(monitor_id);
CREATE INDEX IF NOT EXISTS idx_email_events_received ON email_received_events(received_at DESC);
CREATE INDEX IF NOT EXISTS idx_email_events_processed ON email_received_events(processed) WHERE processed = false;

View file

@ -0,0 +1,8 @@
DROP TABLE IF EXISTS user_virtual_backgrounds;
DROP TABLE IF EXISTS meeting_captions;
DROP TABLE IF EXISTS meeting_waiting_room;
DROP TABLE IF EXISTS meeting_questions;
DROP TABLE IF EXISTS meeting_polls;
DROP TABLE IF EXISTS meeting_breakout_rooms;
-- Note: meeting_recordings table is from 6.0.23 migration, don't drop it
DROP TABLE IF EXISTS meetings;

View file

@ -0,0 +1,122 @@
-- Legacy Meet Tables extracted from consolidated
-- Core meetings table (if not exists from scheduled_meetings)
CREATE TABLE IF NOT EXISTS meetings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scheduled_meeting_id UUID REFERENCES scheduled_meetings(id) ON DELETE SET NULL,
room_id UUID,
title VARCHAR(255) NOT NULL,
status VARCHAR(20) DEFAULT 'active',
started_at TIMESTAMPTZ DEFAULT NOW(),
ended_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_meeting_status CHECK (status IN ('active', 'ended', 'cancelled'))
);
CREATE INDEX IF NOT EXISTS idx_meetings_scheduled ON meetings(scheduled_meeting_id);
CREATE INDEX IF NOT EXISTS idx_meetings_status ON meetings(status);
-- Meeting recordings (legacy table already exists, skip creation)
-- Note: meeting_recordings table already exists from 6.0.23 migration with different schema
-- This migration creates additional meeting-related tables that reference the new meetings table
-- Breakout rooms
CREATE TABLE IF NOT EXISTS meeting_breakout_rooms (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
meeting_id UUID NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
room_number INTEGER NOT NULL,
participants_json TEXT DEFAULT '[]',
duration_minutes INTEGER,
started_at TIMESTAMPTZ,
ended_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_breakout_rooms_meeting ON meeting_breakout_rooms(meeting_id);
-- Meeting polls
CREATE TABLE IF NOT EXISTS meeting_polls (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
meeting_id UUID NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
question TEXT NOT NULL,
poll_type VARCHAR(20) DEFAULT 'single',
options_json TEXT NOT NULL,
is_anonymous BOOLEAN DEFAULT false,
allow_multiple BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT false,
results_json TEXT DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
closed_at TIMESTAMPTZ,
CONSTRAINT check_poll_type CHECK (poll_type IN ('single', 'multiple', 'open'))
);
CREATE INDEX IF NOT EXISTS idx_meeting_polls_meeting ON meeting_polls(meeting_id);
-- Meeting Q&A
CREATE TABLE IF NOT EXISTS meeting_questions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
meeting_id UUID NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
asked_by UUID REFERENCES users(id) ON DELETE SET NULL,
question TEXT NOT NULL,
is_anonymous BOOLEAN DEFAULT false,
upvotes INTEGER DEFAULT 0,
is_answered BOOLEAN DEFAULT false,
answer TEXT,
answered_by UUID REFERENCES users(id) ON DELETE SET NULL,
answered_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_meeting_questions_meeting ON meeting_questions(meeting_id);
CREATE INDEX IF NOT EXISTS idx_meeting_questions_unanswered ON meeting_questions(meeting_id) WHERE is_answered = false;
-- Meeting waiting room
CREATE TABLE IF NOT EXISTS meeting_waiting_room (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
meeting_id UUID NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
guest_name VARCHAR(255),
guest_email VARCHAR(255),
device_info_json TEXT DEFAULT '{}',
status VARCHAR(20) DEFAULT 'waiting',
admitted_by UUID REFERENCES users(id) ON DELETE SET NULL,
admitted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_waiting_status CHECK (status IN ('waiting', 'admitted', 'rejected', 'left'))
);
CREATE INDEX IF NOT EXISTS idx_waiting_room_meeting ON meeting_waiting_room(meeting_id);
CREATE INDEX IF NOT EXISTS idx_waiting_room_status ON meeting_waiting_room(meeting_id, status);
-- Meeting live captions
CREATE TABLE IF NOT EXISTS meeting_captions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
meeting_id UUID NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
speaker_id UUID REFERENCES users(id) ON DELETE SET NULL,
speaker_name VARCHAR(255),
caption_text TEXT NOT NULL,
language VARCHAR(10) DEFAULT 'en',
confidence REAL,
timestamp_ms BIGINT NOT NULL,
duration_ms INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_meeting_captions_meeting ON meeting_captions(meeting_id, timestamp_ms);
-- Virtual backgrounds
CREATE TABLE IF NOT EXISTS user_virtual_backgrounds (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(100),
background_type VARCHAR(20) DEFAULT 'image',
file_path TEXT,
blur_intensity INTEGER,
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT check_bg_type CHECK (background_type IN ('image', 'blur', 'none'))
);
CREATE INDEX IF NOT EXISTS idx_virtual_backgrounds_user ON user_virtual_backgrounds(user_id);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS paper_documents;

Some files were not shown because too many files have changed in this diff Show more