feat: sistema completo de notificacoes por e-mail
Implementa sistema de notificacoes por e-mail com: - Notificacoes de ciclo de vida (abertura, resolucao, atribuicao, status) - Sistema de avaliacao de chamados com estrelas (1-5) - Deep linking via protocolo raven:// para abrir chamados no desktop - Tokens de acesso seguro para visualizacao sem login - Preferencias de notificacao configuraveis por usuario - Templates HTML responsivos com design tokens da plataforma - API completa para preferencias, tokens e avaliacoes Modelos Prisma: - TicketRating: avaliacoes de chamados - TicketAccessToken: tokens de acesso direto - NotificationPreferences: preferencias por usuario Turbopack como bundler padrao (Next.js 16) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
cb6add1a4a
commit
f2c0298285
23 changed files with 4387 additions and 9 deletions
103
apps/desktop/src-tauri/Cargo.lock
generated
103
apps/desktop/src-tauri/Cargo.lock
generated
|
|
@ -75,6 +75,7 @@ dependencies = [
|
|||
"sysinfo",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-process",
|
||||
|
|
@ -545,6 +546,26 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
|
|
@ -653,6 +674,12 @@ version = "0.8.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
|
@ -850,6 +877,15 @@ dependencies = [
|
|||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dpi"
|
||||
version = "0.1.2"
|
||||
|
|
@ -1539,6 +1575,12 @@ version = "0.12.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
|
|
@ -2673,6 +2715,16 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
|
|
@ -3429,6 +3481,16 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
|
|
@ -4217,6 +4279,27 @@ dependencies = [
|
|||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e82759f7c7d51de3cbde51c04b3f2332de52436ed84541182cd8944b04e9e73"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"plist",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry",
|
||||
"windows-result 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-notification"
|
||||
version = "2.3.3"
|
||||
|
|
@ -4523,6 +4606,15 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
|
|
@ -5404,6 +5496,17 @@ dependencies = [
|
|||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ tauri-plugin-store = "2.4.0"
|
|||
tauri-plugin-updater = "2.9.0"
|
||||
tauri-plugin-process = "2.3.0"
|
||||
tauri-plugin-notification = "2"
|
||||
tauri-plugin-deep-link = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sysinfo = { version = "0.31", default-features = false, features = ["multithread", "network", "system", "disk"] }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use agent::{collect_inventory_plain, collect_profile, AgentRuntime, MachineProfi
|
|||
use chat::{ChatRuntime, ChatSession, ChatMessagesResponse, SendMessageResponse};
|
||||
use chrono::Local;
|
||||
use usb_control::{UsbPolicy, UsbPolicyResult};
|
||||
use tauri::{Emitter, Manager, WindowEvent};
|
||||
use tauri::{Emitter, Listener, Manager, WindowEvent};
|
||||
use tauri_plugin_store::Builder as StorePluginBuilder;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
|
@ -348,6 +348,81 @@ fn set_chat_minimized(app: tauri::AppHandle, ticket_id: String, minimized: bool)
|
|||
chat::set_chat_minimized(&app, &ticket_id, minimized)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Handler de Deep Link (raven://)
|
||||
// ============================================================================
|
||||
|
||||
/// Processa URLs do protocolo raven://
|
||||
/// Formatos suportados:
|
||||
/// - raven://ticket/{token} - Abre visualizacao do chamado
|
||||
/// - raven://chat/{ticketId}?token={token} - Abre chat do chamado
|
||||
/// - raven://rate/{token} - Abre avaliacao do chamado
|
||||
fn handle_deep_link(app: &tauri::AppHandle, url: &str) {
|
||||
log_info!("Processando deep link: {url}");
|
||||
|
||||
// Remove o prefixo raven://
|
||||
let path = url.trim_start_matches("raven://");
|
||||
|
||||
// Parse do path
|
||||
let parts: Vec<&str> = path.split('/').collect();
|
||||
|
||||
if parts.is_empty() {
|
||||
log_warn!("Deep link invalido: path vazio");
|
||||
return;
|
||||
}
|
||||
|
||||
match parts[0] {
|
||||
"ticket" => {
|
||||
if parts.len() > 1 {
|
||||
let token = parts[1].split('?').next().unwrap_or(parts[1]);
|
||||
log_info!("Abrindo ticket com token: {token}");
|
||||
|
||||
// Mostra a janela principal
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
|
||||
// Emite evento para o frontend navegar para o ticket
|
||||
let _ = app.emit("raven://deep-link/ticket", serde_json::json!({
|
||||
"token": token
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
"chat" => {
|
||||
if parts.len() > 1 {
|
||||
let ticket_id = parts[1].split('?').next().unwrap_or(parts[1]);
|
||||
log_info!("Abrindo chat do ticket: {ticket_id}");
|
||||
|
||||
// Abre janela de chat
|
||||
if let Err(e) = chat::open_chat_window(app, ticket_id) {
|
||||
log_error!("Falha ao abrir chat: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
"rate" => {
|
||||
if parts.len() > 1 {
|
||||
let token = parts[1].split('?').next().unwrap_or(parts[1]);
|
||||
log_info!("Abrindo avaliacao com token: {token}");
|
||||
|
||||
// Mostra a janela principal
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
|
||||
// Emite evento para o frontend navegar para avaliacao
|
||||
let _ = app.emit("raven://deep-link/rate", serde_json::json!({
|
||||
"token": token
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log_warn!("Deep link desconhecido: {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
|
|
@ -358,6 +433,7 @@ pub fn run() {
|
|||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.on_window_event(|window, event| {
|
||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
|
|
@ -372,6 +448,17 @@ pub fn run() {
|
|||
|
||||
log_info!("Raven iniciando...");
|
||||
|
||||
// Configura handler de deep link (raven://)
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
let handle = app.handle().clone();
|
||||
app.listen("deep-link://new-url", move |event| {
|
||||
let urls = event.payload();
|
||||
log_info!("Deep link recebido: {urls}");
|
||||
handle_deep_link(&handle, urls);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
setup_raven_autostart();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@
|
|||
"dialog": true,
|
||||
"active": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5MTMxRTQwODA1NEFCRjAKUldUd3ExU0FRQjRUR2VqcHBNdXhBMUV3WlM2cFA4dmNnNEhtMUJ2a3VVWVlTQnoxbEo5YUtlUTMK"
|
||||
},
|
||||
"deep-link": {
|
||||
"desktop": {
|
||||
"schemes": ["raven"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue