using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using AyaNova.Models; using AyaNova.Api.ControllerHelpers; using AyaNova.Biz; using System.Linq; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using AyaNova.Util; namespace AyaNova.Api.Controllers { [ApiController] [ApiVersion("8.0")] [Route("api/v{version:apiVersion}/notify")] [Produces("application/json")] [Authorize] public class NotifyController : ControllerBase { private readonly AyContext ct; private readonly ILogger log; private readonly ApiServerState serverState; /// /// ctor /// /// /// /// public NotifyController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) { ct = dbcontext; log = logger; serverState = apiServerState; } /// /// Pre-login route to confirm server is available /// /// [AllowAnonymous] [HttpGet("hello")] public async Task GetPreLoginPing() { bool showSampleLogins = false; if (AyaNova.Core.License.ActiveKey.Status == AyaNova.Core.License.AyaNovaLicenseKey.LicenseStatus.ActiveTrial) showSampleLogins = await AyaNova.Util.DbUtil.DBHasTrialUsersAsync(ct, log); bool suIsDefault = await UserBiz.SuperIsDefaultCredsAsync(ct); //confirm if there are logo's to show as well var logo = await ct.Logo.AsNoTracking().SingleOrDefaultAsync(); bool HasLargeLogo = false; bool HasMediumLogo = false; bool HasSmallLogo = false; if (logo != null) { if (logo.Small != null) HasSmallLogo = true; if (logo.Medium != null) HasMediumLogo = true; if (logo.Large != null) HasLargeLogo = true; } //case 4205 bool PPBuild = true; #if (SUBSCRIPTION_BUILD) PPBuild = false; #endif return Ok(ApiOkResponse.Response( new { eval = showSampleLogins, sudf = suIsDefault, ll = HasLargeLogo, ml = HasMediumLogo, sl = HasSmallLogo, lcr = AyaNova.Core.License.LicenseConsentRequired, pp = PPBuild })); } /// /// Get count of new notifications waiting /// /// [HttpGet("new-count")] public async Task GetNewCount() { var UserId = UserIdFromContext.Id(HttpContext.Items); if (serverState.IsClosed && UserId != 1)//bypass for superuser to fix fundamental problems return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); return Ok(ApiOkResponse.Response(await ct.InAppNotification.CountAsync(z => z.UserId == UserId && z.Fetched == false))); } /// /// Get all in-app notifications /// /// [HttpGet("app-notifications")] public async Task GetAppNotifications() { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); var UserId = UserIdFromContext.Id(HttpContext.Items); var ret = await ct.InAppNotification.AsNoTracking().Where(z => z.UserId == UserId).OrderByDescending(z => z.Created).ToListAsync(); await ct.Database.ExecuteSqlInterpolatedAsync($"update ainappnotification set fetched={true} where userid = {UserId}"); return Ok(ApiOkResponse.Response(ret)); } /// /// Delete app Notification /// /// /// NoContent [HttpDelete("{id}")] public async Task DeleteAppNotification([FromRoute] long id) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); var UserId = UserIdFromContext.Id(HttpContext.Items); var n = await ct.InAppNotification.FirstOrDefaultAsync(z => z.Id == id); if (n == null) return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id")); if (n.UserId != UserId) return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, null, "Can't delete notification for another user")); ct.InAppNotification.Remove(n); await ct.SaveChangesAsync(); return NoContent(); } /// /// Get Notify Event object list from queue /// /// Notify Event objects awaiting delivery [HttpGet("queue")] public async Task GetQueue() { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.OpsNotificationSettings)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } if (!ModelState.IsValid) { return BadRequest(new ApiErrorResponse(ModelState)); } var ret = new List(); var NotifyEvents = await ct.NotifyEvent.AsNoTracking().ToListAsync(); foreach (NotifyEvent ne in NotifyEvents) { var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == ne.UserId).Select(x => new { Active = x.Active, Name = x.Name }).FirstOrDefaultAsync(); var Subscription = await ct.NotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == ne.NotifySubscriptionId); ret.Add(new NotifyEventQueueItem(ne.Id, ne.Created, ne.EventDate, (ne.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice), ne.UserId, UserInfo.Name, ne.EventType, ne.AyaType, ne.Name)); } return Ok(ApiOkResponse.Response(ret)); } public record NotifyEventQueueItem(long Id, DateTime Created, DateTime EventDate, DateTime DeliverAfter, long UserId, string User, NotifyEventType EventType, AyaType AyaType, string Name); /// /// Delete pending notification event /// /// /// NoContent [HttpDelete("notify-event/{id}")] public async Task DeleteNotifyEvent([FromRoute] long id) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); if (!Authorized.HasDeleteRole(HttpContext.Items, AyaType.OpsNotificationSettings)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } var n = await ct.NotifyEvent.FirstOrDefaultAsync(z => z.Id == id); if (n == null) return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id")); ct.NotifyEvent.Remove(n); await ct.SaveChangesAsync(); return NoContent(); } /// /// Send direct message notification to selected users /// /// NoContent on success or error [HttpPost("direct-message")] public async Task SendNotifyDirectMessage([FromBody] NotifyDirectMessage notifyDirectMessage) { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); foreach (long l in notifyDirectMessage.Users) { if (l != 0) await NotifyEventHelper.AddGeneralNotifyEvent( NotifyEventType.GeneralNotification, notifyDirectMessage.Message, UserNameFromContext.Name(HttpContext.Items), null, l ); } return NoContent(); } public class NotifyDirectMessage { public NotifyDirectMessage() { Users = new List(); } [Required] public string Message { get; set; } [Required] public List Users { get; set; } } /// /// Send direct SMTP message notification so single object / address /// Note: adds to queue, not instantly sent /// /// NoContent on success or error [HttpPost("direct-smtp")] public async Task SendNotifySmtpDirectMessage([FromBody] NotifyDirectSMTP notifyDirectSMTP) { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); //validate if (string.IsNullOrWhiteSpace(notifyDirectSMTP.ToAddress)) { //We need to fetch the address from the object type and id //if no id then can skip the rest here if (notifyDirectSMTP.ObjectId == 0) { return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ObjectId", "No address or object id specified, no where to send this")); } //get the address switch (notifyDirectSMTP.AType) { case AyaType.NoType: return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ToAddress", "No address or object type and id specified no where to send this")); case AyaType.Customer: var CustomerInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.EmailAddress, x.Active }).FirstAsync(); if (string.IsNullOrWhiteSpace(CustomerInfo.EmailAddress)) return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"Customer {CustomerInfo.Name} doesn't have an email address no where to send this")); if (CustomerInfo.Active == false) return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"Customer {CustomerInfo.Name} is not active, only active customers can be emailed directly")); notifyDirectSMTP.ToAddress = CustomerInfo.EmailAddress; break; default: return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "AType", "Specified Type not supported for 'on request' smtp")); } } var UserId = UserIdFromContext.Id(HttpContext.Items); IMailer m = AyaNova.Util.ServiceProviderProvider.Mailer; try { if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive) { await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.GeneralNotification, $"Email notifications are set to OFF at server, unable to send 'on request' type SMTP notification subject:{notifyDirectSMTP.Subject}", "Error", null, UserId); log.LogInformation($"** WARNING: SMTP notification is currently set to Active=False; unable to send 'on request' type SMTP notification subject:{notifyDirectSMTP.Subject} **"); } else await m.SendEmailAsync(notifyDirectSMTP.ToAddress, "Test from Notification system", "This is a test to confirm notification system is working", ServerGlobalOpsSettingsCache.Notify); } catch (Exception ex) { await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex); return StatusCode(500, new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, null, ExceptionUtil.ExtractAllExceptionMessages(ex))); } return NoContent(); } public class NotifyDirectSMTP { public NotifyDirectSMTP() { } public long ObjectId { get; set; } = 0; public AyaType AType { get; set; } = AyaType.NoType; public string ToAddress { get; set; } public string Subject { get; set; } public string TextBody { get; set; } public string HTMLBody { get; set; } } //------------ }//eoc }//eons