diff --git a/MyCore.Framework/Business/FirebaseClient.cs b/MyCore.Framework/Business/FirebaseClient.cs new file mode 100644 index 0000000..ef4ed84 --- /dev/null +++ b/MyCore.Framework/Business/FirebaseClient.cs @@ -0,0 +1,46 @@ +using FirebaseAdmin; +using FirebaseAdmin.Messaging; +using Google.Apis.Auth.OAuth2; +//using Google.Cloud.Firestore; +//using Google.Cloud.Firestore.V1; +//using Grpc.Auth; +//using Grpc.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MyCore.Framework.Business +{ + public class FirebaseClient + { + //public FirestoreDb _firestore; + public FirebaseClient(string computeCredsJson, string firebaseCredsJson, string project) + { + var _credential = GoogleCredential.FromJson(computeCredsJson); + if (_credential.IsCreateScopedRequired) + { + //_credential = _credential.CreateScoped(FirestoreClient.DefaultScopes); + } + + //Channel channel = new Channel(FirestoreClient.DefaultEndpoint.ToString(), _credential.ToChannelCredentials()); + //FirestoreClient client = FirestoreClient.Create(channel); + //_firestore = FirestoreDb.CreateAsync(project, client).GetAwaiter().GetResult(); + + if (FirebaseApp.DefaultInstance == null) + { + try{ + FirebaseApp.Create(new AppOptions() + { + Credential = GoogleCredential.FromJson(firebaseCredsJson), + ProjectId = project, + //ServiceAccountId = firebaseCredsJson["client_email"] + }); + } catch(Exception e) + { + } + } + } + } +} + diff --git a/MyCore.Framework/Business/NotificationLogic.cs b/MyCore.Framework/Business/NotificationLogic.cs new file mode 100644 index 0000000..21715d0 --- /dev/null +++ b/MyCore.Framework/Business/NotificationLogic.cs @@ -0,0 +1,45 @@ +using FirebaseAdmin.Messaging; +using MyCore.Interfaces.DTO; +using MyCore.Interfaces.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MyCore.Framework.Business +{ + public class NotificationLogic + { + public static async Task PushFCMNotification(NotificationDTO notificationDTO, Home home) + { + try + { + // Push FCM notif to phones + var message = new FirebaseAdmin.Messaging.Message() + { + Topic = home == null ? "main" : home.Id, + Notification = new Notification() + { + Title = notificationDTO.notificationTitle, + Body = home == null ? notificationDTO.notificationTitle : $"{home.Name}, {notificationDTO.notificationMessage}" + }, + Data = new Dictionary() + { + { "labelButton", notificationDTO.notificationLabelButton }, + { "type", notificationDTO.type }, + }, + }; + + // Send a message to the device corresponding to the provided + string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); + // Response is a message ID string. + Console.WriteLine("Successfully sent message: " + response); + return true; + } + catch (Exception e) + { + return false; + } + } + } +} diff --git a/MyCore.Framework/MyCore.Framework.csproj b/MyCore.Framework/MyCore.Framework.csproj index c205ef2..49fbfe5 100644 --- a/MyCore.Framework/MyCore.Framework.csproj +++ b/MyCore.Framework/MyCore.Framework.csproj @@ -5,8 +5,13 @@ + + + + + diff --git a/MyCore.Interfaces/DTO/NotificationDTO.cs b/MyCore.Interfaces/DTO/NotificationDTO.cs new file mode 100644 index 0000000..bc58bb5 --- /dev/null +++ b/MyCore.Interfaces/DTO/NotificationDTO.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MyCore.Interfaces.DTO +{ + public class NotificationDTO + { + public string notificationTitle { get; set; } + public string notificationMessage { get; set; } + public string notificationLabelButton { get; set; } + public string title { get; set; } + public string body { get; set; } + public string type { get; set; } + public bool isPushNotification { get; set; } + public bool isButton { get; set; } + public string buttonLabel { get; set; } + } +} diff --git a/MyCore/Controllers/NotificationController.cs b/MyCore/Controllers/NotificationController.cs new file mode 100644 index 0000000..f790640 --- /dev/null +++ b/MyCore/Controllers/NotificationController.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using FirebaseAdmin; +using FirebaseAdmin.Messaging; +using Google.Apis.Auth.OAuth2; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using MyCore.Framework.Business; +using MyCore.Interfaces.DTO; +using MyCore.Interfaces.Models; +using MyCore.Services; + +namespace MyCore.Service.Controllers +{ + //[ApiController] + [Authorize] + [Route("[controller]")] + public class NotificationController : ControllerBase + { + private readonly ILogger _logger; + private HomeDatabaseService _HomeDatabaseService; + private readonly FirebaseClient _firebaseClient; + + public NotificationController(ILogger logger, HomeDatabaseService homeDatabaseService, FirebaseClient firebaseClient) : base() + { + _logger = logger; + this._HomeDatabaseService = homeDatabaseService; + _firebaseClient = firebaseClient; + } + + /// + /// Create a fcm notification + /// + /// notificationDTO + /// bool result + [HttpPost("all")] + [Consumes("application/json")] + public async Task> CreateSimpleNotification([FromBody] NotificationDTO notificationDTO) + { + try + { + if (notificationDTO.isPushNotification) + { + if (notificationDTO.notificationTitle == null + || notificationDTO.notificationMessage == null) + return new BadRequestResult(); + + await NotificationLogic.PushFCMNotification(notificationDTO, null); + + return Ok(true); + } + + return new BadRequestResult(); + + } + catch (Exception ex) + { + _logger?.LogError(ex, $"Error sending global notification to all users"); + return new BadRequestResult(); + } + } + + /// + /// Create a fcm notification for a specific home + /// + /// homeId + /// notificationDTO + /// bool result + [HttpPost("home/{homeId}")] + [Consumes("application/json")] + public async Task> CreateSimpleNotificationForSpecificUser(string homeId, [FromBody] NotificationDTO notificationDTO) + { + try + { + if (notificationDTO.isPushNotification) + { + if (notificationDTO.notificationTitle == null + || notificationDTO.notificationMessage == null + || homeId == null) + return new BadRequestResult(); + + + Home home = _HomeDatabaseService.GetById(homeId); + await NotificationLogic.PushFCMNotification(notificationDTO, home); + + return Ok(true); + } + + return new BadRequestResult(); + + } + catch (Exception ex) + { + _logger?.LogError(ex, $"Error sending notification to specified home {homeId}"); + return new BadRequestResult(); + } + } + } +} diff --git a/MyCore/MyCore.csproj b/MyCore/MyCore.csproj index 90b0aee..b7197e3 100644 --- a/MyCore/MyCore.csproj +++ b/MyCore/MyCore.csproj @@ -20,6 +20,7 @@ + diff --git a/MyCore/Services/Devices/DeviceService.cs b/MyCore/Services/Devices/DeviceService.cs index 6bbe0d5..ed69713 100644 --- a/MyCore/Services/Devices/DeviceService.cs +++ b/MyCore/Services/Devices/DeviceService.cs @@ -321,9 +321,9 @@ namespace MyCore.Services.Devices deviceDetailDTO.ProviderId = provider.Id; deviceDetailDTO.ProviderName = provider.Name; - deviceDetailDTO.Description = zigbee2MqttDevice.type == "Coordinator" ? "Coordinator" : zigbee2MqttDevice.definition.description; + deviceDetailDTO.Description = zigbee2MqttDevice.type == "Coordinator" ? "Coordinator" : zigbee2MqttDevice.definition?.description; - deviceDetailDTO.Model = zigbee2MqttDevice.type == "Coordinator" ? "Coordinator" : zigbee2MqttDevice.definition.model; // Is the base to understand incoming messages ! + deviceDetailDTO.Model = zigbee2MqttDevice.type == "Coordinator" ? "Coordinator" : zigbee2MqttDevice.definition?.model; // Is the base to understand incoming messages ! deviceDetailDTO.FirmwareVersion = zigbee2MqttDevice.software_build_id; deviceDetailDTO.Battery = zigbee2MqttDevice.power_source == null ? false : zigbee2MqttDevice.power_source.Contains("Battery"); @@ -342,13 +342,16 @@ namespace MyCore.Services.Devices { // EXPOSES ! List supportedOperationsDTO = new List(); - foreach (var supportedOperation in zigbee2MqttDevice.definition?.exposes) + if(zigbee2MqttDevice.definition?.exposes != null) { - supportedOperationsDTO.Add(JsonConvert.SerializeObject(supportedOperation)); + foreach (var supportedOperation in zigbee2MqttDevice.definition?.exposes) + { + supportedOperationsDTO.Add(JsonConvert.SerializeObject(supportedOperation)); + } + deviceDetailDTO.SupportedOperations = supportedOperationsDTO; } - deviceDetailDTO.SupportedOperations = supportedOperationsDTO; - deviceDetailDTO.Type = GetDeviceTypeFromZigbeeModel(zigbee2MqttDevice.definition.model); + deviceDetailDTO.Type = GetDeviceTypeFromZigbeeModel(zigbee2MqttDevice.definition?.model); } else { diff --git a/MyCore/Startup.cs b/MyCore/Startup.cs index db56ac7..7aaa4dc 100644 --- a/MyCore/Startup.cs +++ b/MyCore/Startup.cs @@ -36,6 +36,8 @@ using MyCore.Service.Extensions; using Mqtt.Client.AspNetCore.Services; using Mqtt.Client.AspNetCore.Settings; using MyCore.Services.Devices; +using FirebaseAdmin; +using Google.Apis.Auth.OAuth2; namespace MyCore { @@ -144,7 +146,7 @@ namespace MyCore //services.AddMqttClientOnlineHostedService(); // Comment this line when dev - services.AddMerossClientHostedService(); // Todo client files (a lot are useless) + //services.AddMerossClientHostedService(); // Todo client files (a lot are useless) services.AddScoped(); // To clarify if needed.. ? @@ -167,6 +169,14 @@ namespace MyCore services.AddScoped(); services.AddMqttClientHostedService(); // Todo client files (a lot are useless) + + services.AddScoped(c => { + var projectId = Configuration["GCP:project_id"]; + var gcpCreds = File.ReadAllText("googleConfig.json"); + var firebaseCreds = File.ReadAllText("googleConfig.json"); + var firebaseClient = new FirebaseClient(gcpCreds, firebaseCreds, projectId); + return firebaseClient; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/MyCore/appsettings.json b/MyCore/appsettings.json index a10dbb8..4e4ddc5 100644 --- a/MyCore/appsettings.json +++ b/MyCore/appsettings.json @@ -57,5 +57,18 @@ "Id": "5eb020f043ba8930506acbbb", "UserName": "thomas", "Password": "MyCore,1" + }, + "GCP": { + "type": "service_account", + "project_id": "unov---myhomie", + "private_key_id": "0c6263b0b26860a84e500bb465f06c8bd835f34a", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChMXVCQvCxVKPv\n2roCwHovq5nYmO0u7X/GA5WDZ6kHwTvnERE9+RRigVpSwMvWZZXN7VbOKDp62cTX\nO+4sobx8doqkUgjyrVHT2ZjF7ffl6p5I9dQWRdO8+kvXhb+48JYq2i9p8mzPmDnQ\nRq7LDICwrWARL7osmRwNdj4LC5LkpiJYTyIGTMTOX+miiBOd1hmJezv11mExPr2d\nHc5o2vZ4WXeG95Dlh6dRV0PYznVkyC5/GC/pMYemtjJfKNs8xIXZeBVz06L/0gef\n5NrBy3r5NkELfJd4tVIk1QxrtxUkisSx6F6vR95bn7+Vt76eHHMB5H7Gz6HikjhF\nwafUmPcZAgMBAAECggEARa6mvSVI/a/USrzoSoZAi9kH4c2CQxYidUxZVvv540NS\njPog1QSXHlpC3KFBvQYfM21VaDuVxEPdxxm+RYcJf1iTaKOaAKaSSB254F6WGstI\n78ttis9W0Ev3e0zza5R9pjq54ngNN9TbsUDNETPvcfERJXzXY64wpVUnTwkK0FKc\nxPkMWG1Gjtjyg1Csvy5BvGZwEKW2z4YYOmq3J2flbUHea225fpw8t+8IoBKXyUCR\nQuRardNklC5ZncFe1Wlz+mIsoOCBVXJAF7oH+Ougy3hrPdzUuE9PN8WeswG26jz0\nwxVm5GjwdFUpDgjG3753C/yOPHRJbeuhgAjfiwOAPQKBgQDiEQ9KBLZsc+qWqOPN\nlyCzhz15EJuwAv1VJA27S51jSW7fDHhrSy4EdCgICHNdCSrs1JZzfDT5xQfxIwOF\nOO+HCNFFf3H7oq28N2H7eS1ZhJ8vnzfQpsnEL/YSJvdGH7O36iPi01+OZoEq/38J\n3lctJD9rn8NAkFv2bkKdbL9PVwKBgQC2iWUUEtA66a8Y6D7hLSE57M5jT7rh2w1x\nr16xK+fTDsEkJVb5LcHouqaobRV96AEHHg3X++iuKYJAS1OWDiqpm0986be8+n11\nEtk0igPeuTCVe2mTPbOkztSsGeWI6vGkmn7X4jv1zW/IppQIu//fnnNLE5R7ZbO+\n8uGQe8GXDwKBgBgJKE7+YUb2rCKIom/heaCc6HROx77XEohS5QLc70b3L8qXUNZX\nj/T5RKhV/a4Oo3m6wKRWuGs1/7orttwbSZxZ26Y//qZ6jW5NlSVfpOq3pxUxIIYW\nta0eTFNxwSi7higvxNEBGE/TpjgkIiWydiJekD+pJOFgd+ajooGKZmzRAoGANHXK\nQfmiNLxCYWMl5ytEOFSRgENCz3RYKcsf6MVYi/Tal1vP4i8/YnZ6PT+UhKnv2a7O\nhbtVRPCTCY1ciWyq4DHNp3W4I6zN756qJApiUFli2CMu1W1POtdikc+jgzUam5b3\nS24uk1cmgEE3nEcDo63zLqmC6+/vt7owD4kxuwsCgYEAhb1CwvTfNtlMZ/1eGKwf\n9aFt4zB83glU3nfZKo4AtnQ5o7KtY+jCRRfC5Zasvz6xuEteEcxC/4l00vuwXoe0\nFs24T4c5A5rNZQdYy2PycZV180Q8EBlh3o4grGJBXrbeKoEL6Ah9pB4YqZ40ibPS\ngl3ZvmwEfbBAvHKd8jQnBrc=\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-wy808@unov---myhomie.iam.gserviceaccount.com", + "client_id": "102479361065891042544", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-wy808%40unov---myhomie.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" } } diff --git a/MyCore/googleConfig.json b/MyCore/googleConfig.json new file mode 100644 index 0000000..34b0105 --- /dev/null +++ b/MyCore/googleConfig.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "unov---myhomie", + "private_key_id": "0c6263b0b26860a84e500bb465f06c8bd835f34a", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChMXVCQvCxVKPv\n2roCwHovq5nYmO0u7X/GA5WDZ6kHwTvnERE9+RRigVpSwMvWZZXN7VbOKDp62cTX\nO+4sobx8doqkUgjyrVHT2ZjF7ffl6p5I9dQWRdO8+kvXhb+48JYq2i9p8mzPmDnQ\nRq7LDICwrWARL7osmRwNdj4LC5LkpiJYTyIGTMTOX+miiBOd1hmJezv11mExPr2d\nHc5o2vZ4WXeG95Dlh6dRV0PYznVkyC5/GC/pMYemtjJfKNs8xIXZeBVz06L/0gef\n5NrBy3r5NkELfJd4tVIk1QxrtxUkisSx6F6vR95bn7+Vt76eHHMB5H7Gz6HikjhF\nwafUmPcZAgMBAAECggEARa6mvSVI/a/USrzoSoZAi9kH4c2CQxYidUxZVvv540NS\njPog1QSXHlpC3KFBvQYfM21VaDuVxEPdxxm+RYcJf1iTaKOaAKaSSB254F6WGstI\n78ttis9W0Ev3e0zza5R9pjq54ngNN9TbsUDNETPvcfERJXzXY64wpVUnTwkK0FKc\nxPkMWG1Gjtjyg1Csvy5BvGZwEKW2z4YYOmq3J2flbUHea225fpw8t+8IoBKXyUCR\nQuRardNklC5ZncFe1Wlz+mIsoOCBVXJAF7oH+Ougy3hrPdzUuE9PN8WeswG26jz0\nwxVm5GjwdFUpDgjG3753C/yOPHRJbeuhgAjfiwOAPQKBgQDiEQ9KBLZsc+qWqOPN\nlyCzhz15EJuwAv1VJA27S51jSW7fDHhrSy4EdCgICHNdCSrs1JZzfDT5xQfxIwOF\nOO+HCNFFf3H7oq28N2H7eS1ZhJ8vnzfQpsnEL/YSJvdGH7O36iPi01+OZoEq/38J\n3lctJD9rn8NAkFv2bkKdbL9PVwKBgQC2iWUUEtA66a8Y6D7hLSE57M5jT7rh2w1x\nr16xK+fTDsEkJVb5LcHouqaobRV96AEHHg3X++iuKYJAS1OWDiqpm0986be8+n11\nEtk0igPeuTCVe2mTPbOkztSsGeWI6vGkmn7X4jv1zW/IppQIu//fnnNLE5R7ZbO+\n8uGQe8GXDwKBgBgJKE7+YUb2rCKIom/heaCc6HROx77XEohS5QLc70b3L8qXUNZX\nj/T5RKhV/a4Oo3m6wKRWuGs1/7orttwbSZxZ26Y//qZ6jW5NlSVfpOq3pxUxIIYW\nta0eTFNxwSi7higvxNEBGE/TpjgkIiWydiJekD+pJOFgd+ajooGKZmzRAoGANHXK\nQfmiNLxCYWMl5ytEOFSRgENCz3RYKcsf6MVYi/Tal1vP4i8/YnZ6PT+UhKnv2a7O\nhbtVRPCTCY1ciWyq4DHNp3W4I6zN756qJApiUFli2CMu1W1POtdikc+jgzUam5b3\nS24uk1cmgEE3nEcDo63zLqmC6+/vt7owD4kxuwsCgYEAhb1CwvTfNtlMZ/1eGKwf\n9aFt4zB83glU3nfZKo4AtnQ5o7KtY+jCRRfC5Zasvz6xuEteEcxC/4l00vuwXoe0\nFs24T4c5A5rNZQdYy2PycZV180Q8EBlh3o4grGJBXrbeKoEL6Ah9pB4YqZ40ibPS\ngl3ZvmwEfbBAvHKd8jQnBrc=\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-wy808@unov---myhomie.iam.gserviceaccount.com", + "client_id": "102479361065891042544", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-wy808%40unov---myhomie.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +}