diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/general-bots.iml b/.idea/general-bots.iml new file mode 100644 index 0000000..d86ff1d --- /dev/null +++ b/.idea/general-bots.iml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4d8f223 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 9fa0e4e..71b1f60 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,82 @@ Licensed under terms specified in workspace configuration. - Global expansion - Enterprise features + + +# Infrastructure Compliance Checklist - ISO 27001, HIPAA, LGPD + +| ✓ | Requirement | Component | Standard | Implementation Steps | +|---|-------------|-----------|-----------|---------------------| +| ⬜ | TLS 1.3 Configuration | Nginx | All | Configure modern SSL parameters and ciphers in `/etc/nginx/conf.d/ssl.conf` | +| ⬜ | Access Logging | Nginx | All | Enable detailed access logs with privacy fields in `/etc/nginx/nginx.conf` | +| ⬜ | Rate Limiting | Nginx | ISO 27001 | Implement rate limiting rules in location blocks | +| ⬜ | WAF Rules | Nginx | HIPAA | Install and configure ModSecurity with OWASP rules | +| ⬜ | Reverse Proxy Security | Nginx | All | Configure security headers (X-Frame-Options, HSTS, CSP) | +| ⬜ | MFA Implementation | Zitadel | All | Enable and enforce MFA for all administrative accounts | +| ⬜ | RBAC Configuration | Zitadel | All | Set up role-based access control with least privilege | +| ⬜ | Password Policy | Zitadel | All | Configure strong password requirements (length, complexity, history) | +| ⬜ | OAuth2/OIDC Setup | Zitadel | ISO 27001 | Configure secure OAuth flows and token policies | +| ⬜ | Audit Logging | Zitadel | All | Enable comprehensive audit logging for user activities | +| ⬜ | Encryption at Rest | Garage (S3) | All | Configure encrypted storage with key management | +| ⬜ | Bucket Policies | Garage (S3) | All | Implement strict bucket access policies | +| ⬜ | Object Versioning | Garage (S3) | HIPAA | Enable versioning for data recovery capability | +| ⬜ | Access Logging | Garage (S3) | All | Enable detailed access logging for object operations | +| ⬜ | Lifecycle Rules | Garage (S3) | LGPD | Configure data retention and deletion policies | +| ⬜ | DKIM/SPF/DMARC | Stalwart | All | Configure email authentication mechanisms | +| ⬜ | Mail Encryption | Stalwart | All | Enable TLS for mail transport | +| ⬜ | Content Filtering | Stalwart | All | Implement content scanning and filtering rules | +| ⬜ | Mail Archiving | Stalwart | HIPAA | Configure compliant email archiving | +| ⬜ | Sieve Filtering | Stalwart | All | Implement security-focused mail filtering rules | +| ⬜ | System Hardening | Ubuntu | All | Apply CIS Ubuntu Linux benchmarks | +| ⬜ | System Updates | Ubuntu | All | Configure unattended-upgrades for security patches | +| ⬜ | Audit Daemon | Ubuntu | All | Configure auditd for system event logging | +| ⬜ | Firewall Rules | Ubuntu | All | Configure UFW with restrictive rules | +| ⬜ | Disk Encryption | Ubuntu | All | Implement LUKS encryption for system disks | +| ⬜ | SELinux/AppArmor | Ubuntu | All | Enable and configure mandatory access control | +| ⬜ | Monitoring Setup | All | All | Install and configure Prometheus + Grafana | +| ⬜ | Log Aggregation | All | All | Implement centralized logging (e.g., ELK Stack) | +| ⬜ | Backup System | All | All | Configure automated backup system with encryption | +| ⬜ | Network Isolation | All | All | Implement proper network segmentation | + + +## Documentation Requirements + +1. **Security Policies** + - Information Security Policy + - Access Control Policy + - Password Policy + - Data Protection Policy + - Incident Response Plan + +2. **Procedures** + - Backup and Recovery Procedures + - Change Management Procedures + - Access Review Procedures + - Security Incident Procedures + - Data Breach Response Procedures + +3. **Technical Documentation** + - Network Architecture Diagrams + - System Configuration Documentation + - Security Controls Documentation + - Encryption Standards Documentation + - Logging and Monitoring Documentation + +4. **Compliance Records** + - Risk Assessment Reports + - Audit Logs + - Training Records + - Incident Reports + - Access Review Records + +## Regular Maintenance Tasks + +- Weekly security updates +- Monthly access reviews +- Quarterly compliance audits +- Annual penetration testing +- Bi-annual disaster recovery testing + --- Built with ❤️ from Brazil, using Rust for maximum performance and reliability. diff --git a/gb-api/src/lib.rs b/gb-api/src/lib.rs index 4ec0064..c2a5ace 100644 --- a/gb-api/src/lib.rs +++ b/gb-api/src/lib.rs @@ -42,8 +42,8 @@ mod tests { kind: "test".to_string(), content: "integration test".to_string(), metadata: serde_json::Value::Object(serde_json::Map::new()), - created_at: chrono::Utc::now(), - shard_key: 0, + created_at: Some(chrono::Utc::now()), + shard_key: Some(0), }; let response = app diff --git a/gb-api/src/router.rs b/gb-api/src/router.rs index c7f9d03..66bc6a5 100644 --- a/gb-api/src/router.rs +++ b/gb-api/src/router.rs @@ -24,16 +24,239 @@ pub fn create_router(message_processor: MessageProcessor) -> Router { let state = Arc::new(ApiState { message_processor: Mutex::new(message_processor), }); - - Router::new() - .route("/health", get(|| async { "OK" })) - .route("/messages", post(send_message)) - .route("/messages/:id", get(get_message)) - .route("/rooms", post(create_room)) - .route("/rooms/:id", get(get_room)) - .route("/rooms/:id/join", post(join_room)) - .route("/ws", get(websocket_handler)) - .with_state(state) + Router::new() + // File & Document Management + .route("/files/upload", post(upload_file)) + .route("/files/download", post(download)) + .route("/files/copy", post(copy_file)) + .route("/files/move", post(move_file)) + .route("/files/delete", post(delete_file)) + .route("/files/getContents", post(get_file_contents)) + .route("/files/save", post(save_file)) + .route("/files/createFolder", post(create_folder)) + .route("/files/shareFolder", post(share_folder)) + .route("/files/dirFolder", post(dir_folder)) + .route("/files/list", post(get_files)) + .route("/files/search", post(search_files)) + .route("/files/recent", post(get_recent_files)) + .route("/files/favorite", post(toggle_favorite)) + .route("/files/versions", post(get_file_versions)) + .route("/files/restore", post(restore_file_version)) + .route("/files/permissions", post(set_file_permissions)) + .route("/files/quota", get(get_storage_quota)) + .route("/files/shared", get(get_shared_files)) + .route("/files/sync/status", get(get_sync_status)) + .route("/files/sync/start", post(start_sync)) + .route("/files/sync/stop", post(stop_sync)) + + // Document Processing + .route("/docs/merge", post(merge_documents)) + .route("/docs/convert", post(convert_document)) + .route("/docs/fill", post(fill_document)) + .route("/docs/export", post(export_document)) + .route("/docs/import", post(import_document)) + + // Groups & Organizations + .route("/groups/create", post(create_group)) + .route("/groups/update", put(update_group)) + .route("/groups/delete", delete(delete_group)) + .route("/groups/list", get(get_groups)) + .route("/groups/search", post(search_groups)) + .route("/groups/members", get(get_group_members)) + .route("/groups/members/add", post(add_group_member)) + .route("/groups/members/remove", post(remove_group_member)) + .route("/groups/permissions", post(set_group_permissions)) + .route("/groups/settings", post(update_group_settings)) + .route("/groups/analytics", get(get_group_analytics)) + .route("/groups/join/request", post(request_group_join)) + .route("/groups/join/approve", post(approve_join_request)) + .route("/groups/join/reject", post(reject_join_request)) + .route("/groups/invites/send", post(send_group_invite)) + .route("/groups/invites/list", get(list_group_invites)) + + // Teams & Projects + .route("/teams/create", post(create_team)) + .route("/teams/update", put(update_team)) + .route("/teams/delete", delete(delete_team)) + .route("/teams/list", get(get_teams)) + .route("/teams/search", post(search_teams)) + .route("/teams/members", get(get_team_members)) + .route("/teams/members/add", post(add_team_member)) + .route("/teams/members/remove", post(remove_team_member)) + .route("/teams/roles", post(set_team_roles)) + .route("/teams/permissions", post(set_team_permissions)) + .route("/teams/settings", post(update_team_settings)) + .route("/teams/analytics", get(get_team_analytics)) + .route("/teams/projects/create", post(create_project)) + .route("/teams/projects/list", get(get_projects)) + .route("/teams/projects/update", put(update_project)) + .route("/teams/projects/delete", delete(delete_project)) + .route("/teams/reports/generate", post(generate_team_report)) + .route("/teams/activity", get(get_team_activity)) + + // Conversations & Real-time Communication + .route("/conversations/create", post(create_conversation)) + .route("/conversations/join", post(join_conversation)) + .route("/conversations/leave", post(leave_conversation)) + .route("/conversations/members", get(get_conversation_members)) + .route("/conversations/messages", get(get_messages)) + .route("/conversations/messages/send", post(send_message)) + .route("/conversations/messages/edit", put(edit_message)) + .route("/conversations/messages/delete", delete(delete_message)) + .route("/conversations/messages/react", post(react_to_message)) + .route("/conversations/messages/pin", post(pin_message)) + .route("/conversations/messages/search", post(search_messages)) + .route("/conversations/calls/start", post(start_call)) + .route("/conversations/calls/join", post(join_call)) + .route("/conversations/calls/leave", post(leave_call)) + .route("/conversations/calls/mute", post(mute_participant)) + .route("/conversations/calls/unmute", post(unmute_participant)) + .route("/conversations/screen/share", post(share_screen)) + .route("/conversations/screen/stop", post(stop_screen_share)) + .route("/conversations/recording/start", post(start_recording)) + .route("/conversations/recording/stop", post(stop_recording)) + .route("/conversations/whiteboard/create", post(create_whiteboard)) + .route("/conversations/whiteboard/collaborate", post(collaborate_whiteboard)) + + // Communication Services + .route("/comm/email/send", post(send_email)) + .route("/comm/email/template", post(send_template_email)) + .route("/comm/email/schedule", post(schedule_email)) + .route("/comm/email/cancel", post(cancel_scheduled_email)) + .route("/comm/sms/send", post(send_sms)) + .route("/comm/sms/bulk", post(send_bulk_sms)) + .route("/comm/notifications/send", post(send_notification)) + .route("/comm/notifications/preferences", post(set_notification_preferences)) + .route("/comm/broadcast/send", post(send_broadcast)) + .route("/comm/contacts/import", post(import_contacts)) + .route("/comm/contacts/export", post(export_contacts)) + .route("/comm/contacts/sync", post(sync_contacts)) + .route("/comm/contacts/groups", post(manage_contact_groups)) + + // User Management & Authentication + .route("/users/create", post(create_user)) + .route("/users/update", put(update_user)) + .route("/users/delete", delete(delete_user)) + .route("/users/list", get(get_users)) + .route("/users/search", post(search_users)) + .route("/users/profile", get(get_user_profile)) + .route("/users/profile/update", put(update_profile)) + .route("/users/settings", post(update_user_settings)) + .route("/users/permissions", post(set_user_permissions)) + .route("/users/roles", post(manage_user_roles)) + .route("/users/status", post(update_user_status)) + .route("/users/presence", get(get_user_presence)) + .route("/users/activity", get(get_user_activity)) + .route("/users/security/2fa/enable", post(enable_2fa)) + .route("/users/security/2fa/disable", post(disable_2fa)) + .route("/users/security/devices", get(get_registered_devices)) + .route("/users/security/sessions", get(get_active_sessions)) + .route("/users/notifications/settings", post(update_notification_settings)) + + // Calendar & Task Management + .route("/calendar/events/create", post(create_event)) + .route("/calendar/events/update", put(update_event)) + .route("/calendar/events/delete", delete(delete_event)) + .route("/calendar/events/list", get(get_calendar_events)) + .route("/calendar/events/search", post(search_events)) + .route("/calendar/availability/check", post(check_availability)) + .route("/calendar/schedule/meeting", post(schedule_meeting)) + .route("/calendar/reminders/set", post(set_reminder)) + .route("/tasks/create", post(create_task)) + .route("/tasks/update", put(update_task)) + .route("/tasks/delete", delete(delete_task)) + .route("/tasks/list", get(get_tasks)) + .route("/tasks/assign", post(assign_task)) + .route("/tasks/status/update", put(update_task_status)) + .route("/tasks/priority/set", post(set_task_priority)) + .route("/tasks/dependencies/set", post(set_task_dependencies)) + + // Storage & Data Management + .route("/storage/save", post(save_to_storage)) + .route("/storage/batch", post(save_batch_to_storage)) + .route("/storage/json", post(save_json_to_storage)) + .route("/storage/delete", delete(delete_from_storage)) + .route("/storage/quota/check", get(check_storage_quota)) + .route("/storage/cleanup", post(cleanup_storage)) + .route("/storage/backup/create", post(create_backup)) + .route("/storage/backup/restore", post(restore_backup)) + .route("/storage/archive", post(archive_data)) + .route("/storage/metrics", get(get_storage_metrics)) + + // Automation & Workflows + .route("/automation/workflow/create", post(create_workflow)) + .route("/automation/workflow/update", put(update_workflow)) + .route("/automation/workflow/delete", delete(delete_workflow)) + .route("/automation/workflow/execute", post(execute_workflow)) + .route("/automation/workflow/status", get(get_workflow_status)) + .route("/automation/triggers/create", post(create_trigger)) + .route("/automation/triggers/list", get(list_triggers)) + .route("/automation/schedule/create", post(create_schedule)) + .route("/automation/schedule/update", put(update_schedule)) + .route("/automation/actions/create", post(create_action)) + .route("/automation/actions/execute", post(execute_action)) + .route("/automation/rules/create", post(create_rule)) + .route("/automation/rules/evaluate", post(evaluate_rules)) + + // Analytics & Reporting + .route("/analytics/dashboard", get(get_dashboard_data)) + .route("/analytics/reports/generate", post(generate_report)) + .route("/analytics/reports/schedule", post(schedule_report)) + .route("/analytics/metrics/collect", post(collect_metrics)) + .route("/analytics/insights/generate", post(generate_insights)) + .route("/analytics/trends/analyze", post(analyze_trends)) + .route("/analytics/export", post(export_analytics)) + + // System & Administration + .route("/admin/system/status", get(get_system_status)) + .route("/admin/system/metrics", get(get_system_metrics)) + .route("/admin/logs/view", get(view_logs)) + .route("/admin/logs/export", post(export_logs)) + .route("/admin/config/update", post(update_config)) + .route("/admin/maintenance/schedule", post(schedule_maintenance)) + .route("/admin/backup/create", post(create_system_backup)) + .route("/admin/backup/restore", post(restore_system_backup)) + .route("/admin/users/manage", post(manage_system_users)) + .route("/admin/roles/manage", post(manage_system_roles)) + .route("/admin/quotas/manage", post(manage_quotas)) + .route("/admin/licenses/manage", post(manage_licenses)) + + // Integration & External Services + .route("/integrations/list", get(list_integrations)) + .route("/integrations/install", post(install_integration)) + .route("/integrations/configure", post(configure_integration)) + .route("/integrations/uninstall", post(uninstall_integration)) + .route("/integrations/status", get(get_integration_status)) + .route("/integrations/sync", post(sync_integration_data)) + .route("/integrations/webhook/create", post(create_webhook)) + .route("/integrations/webhook/manage", post(manage_webhooks)) + + // AI & Machine Learning + .route("/ai/analyze/text", post(analyze_text)) + .route("/ai/analyze/image", post(analyze_image)) + .route("/ai/generate/text", post(generate_text)) + .route("/ai/generate/image", post(generate_image)) + .route("/ai/translate", post(translate_content)) + .route("/ai/summarize", post(summarize_content)) + .route("/ai/recommend", post(get_recommendations)) + .route("/ai/train/model", post(train_custom_model)) + .route("/ai/predict", post(make_prediction)) + + // Security & Compliance + .route("/security/audit/logs", get(get_audit_logs)) + .route("/security/compliance/check", post(check_compliance)) + .route("/security/threats/scan", post(scan_for_threats)) + .route("/security/access/review", post(review_access)) + .route("/security/encryption/manage", post(manage_encryption)) + .route("/security/certificates/manage", post(manage_certificates)) + + // Health & Monitoring + .route("/health", get(health_check)) + .route("/health/detailed", get(detailed_health_check)) + .route("/monitoring/status", get(get_monitoring_status)) + .route("/monitoring/alerts", get(get_active_alerts)) + .route("/monitoring/metrics", get(get_monitoring_metrics)) + .with_state(state) } async fn handle_ws_connection( diff --git a/gb-messaging/src/lib.rs b/gb-messaging/src/lib.rs index 09a7b7a..1d7b57c 100644 --- a/gb-messaging/src/lib.rs +++ b/gb-messaging/src/lib.rs @@ -1,12 +1,10 @@ mod kafka; -mod rabbitmq; mod redis_pubsub; mod websocket; mod processor; pub mod models; pub use kafka::Kafka; -pub use rabbitmq::RabbitMQ; pub use redis_pubsub::RedisPubSub; pub use websocket::WebSocketClient; pub use processor::MessageProcessor; @@ -38,10 +36,6 @@ mod tests { let redis_client = Client::open("redis://localhost") .expect("Failed to create Redis client"); let redis = RedisPubSub::new(Arc::new(redis_client)); - let rabbitmq = RabbitMQ::new("amqp://localhost:5672") - .await - .unwrap(); - let mut websocket = WebSocketClient::connect("ws://localhost:8080") .await .unwrap(); @@ -59,10 +53,6 @@ mod tests { .await .unwrap(); - rabbitmq.publish("", "test.key", &test_message) - .await - .unwrap(); - websocket.send_message(&serde_json::to_string(&test_message).unwrap()) .await .unwrap(); @@ -83,8 +73,8 @@ mod tests { kind: "test".to_string(), content: "test content".to_string(), metadata: serde_json::Value::Object(serde_json::Map::new()), - created_at: chrono::Utc::now(), - shard_key: 0, + created_at: Some(chrono::Utc::now()), + shard_key: Some(0), }; let envelope = MessageEnvelope { diff --git a/gb-messaging/src/processor.rs b/gb-messaging/src/processor.rs index 84546ac..2e887cb 100644 --- a/gb-messaging/src/processor.rs +++ b/gb-messaging/src/processor.rs @@ -7,7 +7,7 @@ use tracing::instrument; use crate::MessageEnvelope; use tokio::sync::broadcast; // Add this import use std::sync::Arc; -use tracing::{error, info}; // Add error and info macros here +use tracing::{error}; // Add error and info macros here pub struct MessageProcessor { @@ -109,8 +109,8 @@ mod tests { kind: "test".to_string(), content: "test content".to_string(), metadata: serde_json::Value::Object(serde_json::Map::new()), - created_at: chrono::Utc::now(), - shard_key: 0, + created_at: Some(chrono::Utc::now()), + shard_key: Some(0), } } diff --git a/gb-messaging/src/rabbitmq.rs b/gb-messaging/src/rabbitmq.rs deleted file mode 100644 index a76c11f..0000000 --- a/gb-messaging/src/rabbitmq.rs +++ /dev/null @@ -1,193 +0,0 @@ -use gb_core::{Result, Error}; -use lapin::{ - options::*, - types::FieldTable, - Connection, ConnectionProperties, - Channel, - BasicProperties, -}; -use serde::{de::DeserializeOwned, Serialize}; -use std::sync::Arc; -use tokio::sync::Mutex; -use tracing::{instrument, error}; -use futures::StreamExt; - -pub struct RabbitMQ { - connection: Arc, - channel: Arc>, -} - -impl Clone for RabbitMQ { - fn clone(&self) -> Self { - Self { - connection: self.connection.clone(), - channel: self.channel.clone(), - } - } -} - -impl RabbitMQ { - pub async fn new(url: &str) -> Result { - let connection = Connection::connect( - url, - ConnectionProperties::default(), - ) - .await - .map_err(|e| Error::internal(format!("RabbitMQ connection error: {}", e)))?; - - let channel = connection.create_channel() - .await - .map_err(|e| Error::internal(format!("RabbitMQ channel error: {}", e)))?; - - Ok(Self { - connection: Arc::new(connection), - channel: Arc::new(Mutex::new(channel)), - }) - } - - #[instrument(skip(self, message))] - pub async fn publish( - &self, - exchange: &str, - routing_key: &str, - message: &T, - ) -> Result<()> { - let payload = serde_json::to_string(message) - .map_err(|e| Error::internal(format!("Serialization error: {}", e)))?; - - let channel = self.channel.lock().await; - - channel.basic_publish( - exchange, - routing_key, - BasicPublishOptions::default(), - payload.as_bytes(), - BasicProperties::default(), - ) - .await - .map_err(|e| Error::internal(format!("RabbitMQ publish error: {}", e)))?; - - Ok(()) - } - - #[instrument(skip(self, handler))] - pub async fn subscribe( - &self, - queue: &str, - handler: F, - ) -> Result<()> - where - T: DeserializeOwned, - F: Fn(T) -> Fut, - Fut: std::future::Future>, - { - let channel = self.channel.lock().await; - - channel.queue_declare( - queue, - QueueDeclareOptions::default(), - FieldTable::default(), - ) - .await - .map_err(|e| Error::internal(format!("RabbitMQ queue declare error: {}", e)))?; - - let mut consumer = channel.basic_consume( - queue, - "consumer", - BasicConsumeOptions::default(), - FieldTable::default(), - ) - .await - .map_err(|e| Error::internal(format!("RabbitMQ consume error: {}", e)))?; - - while let Some(delivery) = consumer.next().await { - match delivery { - Ok(delivery) => { - if let Ok(payload) = String::from_utf8(delivery.data.clone()) { - match serde_json::from_str::(&payload) { - Ok(value) => { - if let Err(e) = handler(value).await { - error!("Handler error: {}", e); - } - } - Err(e) => error!("Deserialization error: {}", e), - } - } - delivery.ack(BasicAckOptions::default()) - .await - .map_err(|e| error!("Ack error: {}", e)).ok(); - } - Err(e) => error!("Consumer error: {}", e), - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use rstest::*; - use serde::{Deserialize, Serialize}; - use uuid::Uuid; - use std::time::Duration; - - #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] - struct TestMessage { - id: Uuid, - content: String, - } - - #[fixture] - async fn rabbitmq() -> RabbitMQ { - RabbitMQ::new("amqp://localhost:5672") - .await - .unwrap() - } - - #[fixture] - fn test_message() -> TestMessage { - TestMessage { - id: Uuid::new_v4(), - content: "test message".to_string(), - } - } - - #[rstest] - #[tokio::test] - async fn test_publish_subscribe( - #[future] rabbitmq: RabbitMQ, - test_message: TestMessage, - ) { - let queue = "test_queue"; - let routing_key = "test_routing_key"; - - let rabbitmq = rabbitmq.await; - let rabbitmq_clone = rabbitmq.clone(); - let test_message_clone = test_message.clone(); - - let handle = tokio::spawn(async move { - let test_message_ref = test_message_clone.clone(); - let handler = move |msg: TestMessage| { - let expected_msg = test_message_ref.clone(); - async move { - assert_eq!(msg, expected_msg); - Ok(()) - } - }; - - rabbitmq_clone.subscribe(queue, handler).await.unwrap(); - }); - - tokio::time::sleep(Duration::from_millis(100)).await; - - rabbitmq.publish("", routing_key, &test_message) - .await - .unwrap(); - - tokio::time::sleep(Duration::from_secs(1)).await; - - handle.abort(); - } -} \ No newline at end of file diff --git a/lib.rs b/lib.rs new file mode 100644 index 0000000..e69de29