diff --git a/src/api/src/application/Yoma.Core.Api/Controllers/OpportunityController.cs b/src/api/src/application/Yoma.Core.Api/Controllers/OpportunityController.cs index d269e7075..2a541c7f1 100644 --- a/src/api/src/application/Yoma.Core.Api/Controllers/OpportunityController.cs +++ b/src/api/src/application/Yoma.Core.Api/Controllers/OpportunityController.cs @@ -3,6 +3,7 @@ using Swashbuckle.AspNetCore.Annotations; using System.ComponentModel.DataAnnotations; using System.Net; +using Yoma.Core.Domain.ActionLink.Models; using Yoma.Core.Domain.Core; using Yoma.Core.Domain.Opportunity; using Yoma.Core.Domain.Opportunity.Interfaces; @@ -66,18 +67,18 @@ public IActionResult GetPublishedOrExpiredById([FromRoute] Guid id) return StatusCode((int)HttpStatusCode.OK, result); } - [SwaggerOperation(Summary = "Get sharing details for published or expired opportunity by id (Anonymous)")] - [HttpGet("{id}/sharing")] - [ProducesResponseType(typeof(OpportunityInfo), (int)HttpStatusCode.OK)] + [SwaggerOperation(Summary = "Create sharing link for published or expired opportunity by id (Anonymous)")] + [HttpGet("{id}/link/sharing")] + [ProducesResponseType(typeof(LinkInfo), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [AllowAnonymous] - public async Task GetSharingDetails([FromRoute] Guid id, [FromQuery] bool? includeQRCode) + public async Task CreateSharingLink([FromRoute] Guid id, [FromQuery] bool? includeQRCode) { - _logger.LogInformation("Handling request {requestName}", nameof(GetSharingDetails)); + _logger.LogInformation("Handling request {requestName}", nameof(CreateSharingLink)); - var result = await _opportunityService.GetSharingDetails(id, true, includeQRCode); + var result = await _opportunityService.CreateLinkSharing(id, true, includeQRCode, false); - _logger.LogInformation("Request {requestName} handled", nameof(GetSharingDetails)); + _logger.LogInformation("Request {requestName} handled", nameof(CreateSharingLink)); return StatusCode((int)HttpStatusCode.OK, result); } @@ -211,40 +212,22 @@ public IActionResult ListOpportunitySearchCriteriaZltoReward([FromQuery] List GetSharingDetailsAuthed([FromRoute] Guid id, [FromQuery] bool? includeQRCode) - { - _logger.LogInformation("Handling request {requestName}", nameof(GetSharingDetailsAuthed)); - - //by default, all users possess the user role. Therefore, organizational authorization checks are omitted here, allowing org admins to access information for all opportunities without restriction - var result = await _opportunityService.GetSharingDetails(id, false, includeQRCode); - - _logger.LogInformation("Request {requestName} handled", nameof(GetSharingDetailsAuthed)); - - return StatusCode((int)HttpStatusCode.OK, result); - } - #endregion + #endregion Authenticated User Based Actions #region Administrative Actions [SwaggerOperation(Summary = "Return a list of categories associated with opportunities, optionally filter by organization")] @@ -620,6 +603,20 @@ public IActionResult ExportToCsvOpportunitySearch([FromBody] OpportunitySearchFi return File(bytes, "text/csv", fileName); } + [SwaggerOperation(Summary = "Create an instant-verify link for the specified opportunity")] + [HttpPost("{id}/link/instantVerify")] + [ProducesResponseType(typeof(LinkInfo), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + [Authorize(Roles = $"{Constants.Role_Admin}, {Constants.Role_OrganizationAdmin}")] + public async Task CreateInstantVerifyLink([FromRoute] Guid id, [FromBody] OpportunityRequestLinkInstantVerify request) + { + _logger.LogInformation("Handling request {requestName}", nameof(CreateInstantVerifyLink)); + + var result = await _opportunityService.CreateLinkInstantVerify(id, request, true); + _logger.LogInformation("Request {requestName} handled", nameof(CreateInstantVerifyLink)); + + return StatusCode((int)HttpStatusCode.OK, result); + } #endregion } } diff --git a/src/api/src/application/Yoma.Core.Api/Controllers/OrganizationController.cs b/src/api/src/application/Yoma.Core.Api/Controllers/OrganizationController.cs index d7d785c14..f1c1b2d7d 100644 --- a/src/api/src/application/Yoma.Core.Api/Controllers/OrganizationController.cs +++ b/src/api/src/application/Yoma.Core.Api/Controllers/OrganizationController.cs @@ -43,10 +43,10 @@ public OrganizationController( #region Public Members #region Authenticated User Based Actions - [SwaggerOperation(Summary = "Create a new organization (User, Admin or Organization Admin role required)")] + [SwaggerOperation(Summary = "Create a new organization (Authenticated User)")] [HttpPost()] [ProducesResponseType(typeof(Organization), (int)HttpStatusCode.OK)] - [Authorize(Roles = $"{Constants.Role_User}, {Constants.Role_Admin}, {Constants.Role_OrganizationAdmin}")] + [Authorize(Roles = $"{Constants.Role_User}")] public async Task Create([FromForm] OrganizationRequestCreate request) { _logger.LogInformation("Handling request {requestName}", nameof(Create)); @@ -58,10 +58,10 @@ public async Task Create([FromForm] OrganizationRequestCreate req return StatusCode((int)HttpStatusCode.OK, result); } - [SwaggerOperation(Summary = "Return a list of provider types (User, Admin or Organization Admin role required)")] + [SwaggerOperation(Summary = "Return a list of provider types (Authenticated User)")] [HttpGet("lookup/providerType")] [ProducesResponseType(typeof(List), (int)HttpStatusCode.OK)] - [Authorize(Roles = $"{Constants.Role_User}, {Constants.Role_Admin}, {Constants.Role_OrganizationAdmin}")] + [Authorize(Roles = $"{Constants.Role_User}")] public IActionResult ListProviderTypes() { _logger.LogInformation("Handling request {requestName}", nameof(ListProviderTypes)); @@ -72,7 +72,7 @@ public IActionResult ListProviderTypes() return StatusCode((int)HttpStatusCode.OK, result); } - #endregion + #endregion Authenticated User Based Actions #region Administrative Actions [SwaggerOperation(Summary = "Get the specified organization by id")] diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Enumerations.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Enumerations.cs new file mode 100644 index 000000000..3599e34ff --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Enumerations.cs @@ -0,0 +1,21 @@ +namespace Yoma.Core.Domain.ActionLink +{ + public enum LinkEntityType + { + Opportunity + } + + public enum LinkAction + { + Share, + Verify + } + + public enum LinkStatus + { + Active, + Inactive, + Expired, + LimitReached + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Extensions/LinkExtensions.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Extensions/LinkExtensions.cs new file mode 100644 index 000000000..a5970a8b2 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Extensions/LinkExtensions.cs @@ -0,0 +1,31 @@ +using Yoma.Core.Domain.ActionLink.Models; +using Yoma.Core.Domain.Core.Helpers; + +namespace Yoma.Core.Domain.ActionLink.Extensions +{ + public static class LinkExtensions + { + public static LinkInfo ToLinkInfo(this Link value, bool? includeQRCode) + { + ArgumentNullException.ThrowIfNull(value, nameof(value)); + + return new LinkInfo + { + Id = value.Id, + Name = value.Name, + Description = value.Description, + StatusId = value.StatusId, + Status = value.Status, + URL = value.URL, + ShortURL = value.ShortURL, + QRCodeBase64 = includeQRCode == true ? QRCodeHelper.GenerateQRCodeBase64(value.ShortURL) : null, + UsagesLimit = value.UsagesLimit, + UsagesTotal = value.UsagesTotal, + UsagesAvailable = value.UsagesLimit.HasValue ? value.UsagesLimit - (value.UsagesTotal ?? 0) : null, + DateEnd = value.DateEnd, + DateCreated = value.DateCreated, + DateModified = value.DateModified + }; + } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Interfaces/ILinkService.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Interfaces/ILinkService.cs new file mode 100644 index 000000000..7eebf6a3c --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Interfaces/ILinkService.cs @@ -0,0 +1,11 @@ +using Yoma.Core.Domain.ActionLink.Models; + +namespace Yoma.Core.Domain.ActionLink.Interfaces +{ + public interface ILinkService + { + Task Create(LinkRequestCreate request, bool ensureOrganizationAuthorization); + + Task LogUsage(Guid id); + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Interfaces/ILinkStatusService.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Interfaces/ILinkStatusService.cs new file mode 100644 index 000000000..19f0d1694 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Interfaces/ILinkStatusService.cs @@ -0,0 +1,15 @@ +namespace Yoma.Core.Domain.ActionLink.Interfaces +{ + public interface ILinkStatusService + { + Models.Lookups.LinkStatus GetByName(string name); + + Models.Lookups.LinkStatus? GetByNameOrNull(string name); + + Models.Lookups.LinkStatus GetById(Guid id); + + Models.Lookups.LinkStatus? GetByIdOrNull(Guid id); + + List List(); + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/Link.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/Link.cs new file mode 100644 index 000000000..e671fea5e --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/Link.cs @@ -0,0 +1,39 @@ +namespace Yoma.Core.Domain.ActionLink.Models +{ + public class Link + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public string? Description { get; set; } + + public string EntityType { get; set; } + + public string Action { get; set; } + + public Guid StatusId { get; set; } + + public LinkStatus Status { get; set; } + + public Guid? OpportunityId { get; set; } + + public string URL { get; set; } + + public string ShortURL { get; set; } + + public int? UsagesLimit { get; set; } + + public int? UsagesTotal { get; set; } + + public DateTimeOffset? DateEnd { get; set; } + + public DateTimeOffset DateCreated { get; set; } + + public Guid CreatedByUserId { get; set; } + + public DateTimeOffset DateModified { get; set; } + + public Guid ModifiedByUserId { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkInfo.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkInfo.cs new file mode 100644 index 000000000..0457f9edf --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkInfo.cs @@ -0,0 +1,33 @@ +namespace Yoma.Core.Domain.ActionLink.Models +{ + public class LinkInfo + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public string? Description { get; set; } + + public Guid StatusId { get; set; } + + public LinkStatus Status { get; set; } + + public string URL { get; set; } + + public string ShortURL { get; set; } + + public string? QRCodeBase64 { get; set; } + + public int? UsagesLimit { get; set; } + + public int? UsagesTotal { get; set; } + + public int? UsagesAvailable { get; set; } + + public DateTimeOffset? DateEnd { get; set; } + + public DateTimeOffset DateCreated { get; set; } + + public DateTimeOffset DateModified { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkRequestCreate.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkRequestCreate.cs new file mode 100644 index 000000000..ea86ecdfc --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkRequestCreate.cs @@ -0,0 +1,21 @@ +namespace Yoma.Core.Domain.ActionLink.Models +{ + public class LinkRequestCreate + { + public string Name { get; set; } + + public string? Description { get; set; } + + public LinkEntityType EntityType { get; set; } + + public LinkAction Action { get; set; } + + public Guid EntityId { get; set; } + + public string URL { get; set; } + + public int? UsagesLimit { get; set; } + + public DateTimeOffset? DateEnd { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkUsageLog.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkUsageLog.cs new file mode 100644 index 000000000..225e6ea51 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/LinkUsageLog.cs @@ -0,0 +1,13 @@ +namespace Yoma.Core.Domain.ActionLink.Models +{ + public class LinkUsageLog + { + public Guid Id { get; set; } + + public Guid LinkId { get; set; } + + public Guid UserId { get; set; } + + public DateTimeOffset DateCreated { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/Lookups/LinkStatus.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/Lookups/LinkStatus.cs new file mode 100644 index 000000000..ae5b3bfb0 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Models/Lookups/LinkStatus.cs @@ -0,0 +1,9 @@ +namespace Yoma.Core.Domain.ActionLink.Models.Lookups +{ + public class LinkStatus + { + public Guid Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/LinkService.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/LinkService.cs new file mode 100644 index 000000000..5ae1686ae --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/LinkService.cs @@ -0,0 +1,189 @@ +using FluentValidation; +using Flurl; +using Microsoft.AspNetCore.Http; +using System.Transactions; +using Yoma.Core.Domain.ActionLink.Interfaces; +using Yoma.Core.Domain.ActionLink.Models; +using Yoma.Core.Domain.Core.Exceptions; +using Yoma.Core.Domain.Core.Helpers; +using Yoma.Core.Domain.Core.Interfaces; +using Yoma.Core.Domain.Entity.Interfaces; +using Yoma.Core.Domain.Exceptions; +using Yoma.Core.Domain.ShortLinkProvider.Interfaces; +using Yoma.Core.Domain.ShortLinkProvider.Models; + +namespace Yoma.Core.Domain.ActionLink.Services +{ + public class LinkService : ILinkService + { + #region Class Variables + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IShortLinkProviderClient _shortLinkProviderClient; + private readonly IUserService _userService; + private readonly ILinkStatusService _linkStatusService; + private readonly IRepository _linkRepository; + private readonly IRepository _linkUsageLogRepository; + private readonly IExecutionStrategyService _executionStrategyService; + #endregion + + #region Constructor + public LinkService(IHttpContextAccessor httpContextAccessor, + IShortLinkProviderClientFactory shortLinkProviderClientFactory, + IUserService userService, + ILinkStatusService linkStatusService, + IRepository linkRepository, + IRepository linkUsageLogRepository, + IExecutionStrategyService executionStrategyService) + { + _httpContextAccessor = httpContextAccessor; + _shortLinkProviderClient = shortLinkProviderClientFactory.CreateClient(); + _userService = userService; + _linkStatusService = linkStatusService; + _linkRepository = linkRepository; + _linkUsageLogRepository = linkUsageLogRepository; + _executionStrategyService = executionStrategyService; + } + #endregion + + #region Public Members + public void AssertActive(Guid id) + { + var link = GetById(id); + AssertActive(link); + } + + public async Task Create(LinkRequestCreate request, bool ensureOrganizationAuthorization) + { + ArgumentNullException.ThrowIfNull(request); + + //TODO: validation + + var user = _userService.GetByEmail(HttpContextAccessorHelper.GetUsername(_httpContextAccessor, !ensureOrganizationAuthorization), false, false); + + var item = new Link + { + Id = Guid.NewGuid(), + Name = request.Name, + Description = request.Description, + EntityType = request.EntityType.ToString(), + Action = request.Action.ToString(), + Status = LinkStatus.Active, + StatusId = _linkStatusService.GetByName(LinkStatus.Active.ToString()).Id, + URL = request.URL, + UsagesLimit = request.UsagesLimit, + DateEnd = request.DateEnd, + CreatedByUserId = user.Id, + ModifiedByUserId = user.Id, + }; + + switch (request.EntityType) + { + case LinkEntityType.Opportunity: + item.OpportunityId = request.EntityId; + + switch (request.Action) + { + case LinkAction.Share: + var itemExisting = _linkRepository.Query().SingleOrDefault(o => o.EntityType == item.EntityType && o.Action == item.Action && o.OpportunityId == item.OpportunityId); + if (itemExisting == null) break; + + if (!string.Equals(itemExisting.URL, item.URL)) + throw new DataInconsistencyException($"URL mismatch detected for link with id '{itemExisting.Id}'"); + + //sharing links should always remain active; they cannot be deactivated, have no end date, and are not subject to usage limits + AssertActive(itemExisting); + + return itemExisting; + + case LinkAction.Verify: + item.URL = item.URL.AppendPathSegment(item.Id.ToString()); + break; + + default: + throw new InvalidOperationException($"Invalid action of '{request.Action}' for entity type of '{request.EntityType}'"); + } + break; + + default: + throw new InvalidOperationException($"Invalid entity type of '{request.EntityType}'"); + } + + var responseShortLink = await _shortLinkProviderClient.CreateShortLink(new ShortLinkRequest + { + Type = request.EntityType, + Action = request.Action, + Title = request.Name, + URL = request.URL + }); + + item.ShortURL = responseShortLink.Link; + + item = await _linkRepository.Create(item); + + return item; + } + + public async Task LogUsage(Guid id) + { + var link = GetById(id); + + AssertActive(link); + + //only track unique usages provided authenticated + if (!HttpContextAccessorHelper.UserContextAvailable(_httpContextAccessor)) return link; + + var user = _userService.GetByEmail(HttpContextAccessorHelper.GetUsername(_httpContextAccessor, false), false, false); + + var item = _linkUsageLogRepository.Query().SingleOrDefault(o => o.LinkId == id && o.UserId == user.Id); + if (item != null) return link; //already used by the user + + await _executionStrategyService.ExecuteInExecutionStrategyAsync(async () => + { + using var scope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled); + + item = await _linkUsageLogRepository.Create(new LinkUsageLog + { + LinkId = id, + UserId = user.Id, + }); + + link.UsagesTotal = (link.UsagesTotal ?? 0) + 1; + if (link.UsagesLimit.HasValue && link.UsagesTotal == link.UsagesLimit) link.Status = LinkStatus.LimitReached; + link = await _linkRepository.Update(link); + + scope.Complete(); + }); + + return link; + } + #endregion + + #region Private Members + private static void AssertActive(Link link) + { + switch (link.Status) + { + case LinkStatus.Inactive: + throw new ValidationException("This link is no longer active"); + case LinkStatus.Expired: + throw new ValidationException("This link has expired and can no longer be used"); + case LinkStatus.LimitReached: + throw new ValidationException("This link has reached its usage limit and cannot be used further"); + case LinkStatus.Active: + return; + default: + throw new InvalidOperationException($"Invalid status of '{link.Status}'"); + } + } + + private Link GetById(Guid id) + { + if (id == Guid.Empty) + throw new ArgumentNullException(nameof(id)); + + return _linkRepository.Query().SingleOrDefault(o => o.Id == id) + ?? throw new EntityNotFoundException($"Link with id '{id}' does not exist"); + } + #endregion + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/Lookups/LinkStatusService.cs b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/Lookups/LinkStatusService.cs new file mode 100644 index 000000000..2b9ca3410 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ActionLink/Services/Lookups/LinkStatusService.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Yoma.Core.Domain.ActionLink.Interfaces; +using Yoma.Core.Domain.Core.Interfaces; +using Yoma.Core.Domain.Core.Models; + +namespace Yoma.Core.Domain.ActionLink.Services.Lookups +{ + public class LinkStatusService : ILinkStatusService + { + #region Class Variables + private readonly AppSettings _appSettings; + private readonly IMemoryCache _memoryCache; + private readonly IRepository _linkStatusRepository; + #endregion + + #region Constructor + public LinkStatusService(IOptions appSettings, + IMemoryCache memoryCache, + IRepository linkStatusRepository) + { + _appSettings = appSettings.Value; + _memoryCache = memoryCache; + _linkStatusRepository = linkStatusRepository; + } + #endregion + + #region Public Members + public Models.Lookups.LinkStatus GetByName(string name) + { + var result = GetByNameOrNull(name) ?? throw new ArgumentException($"{nameof(Models.Lookups.LinkStatus)} with name '{name}' does not exists", nameof(name)); + return result; + } + + public Models.Lookups.LinkStatus? GetByNameOrNull(string name) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + name = name.Trim(); + + return List().SingleOrDefault(o => string.Equals(o.Name, name, StringComparison.InvariantCultureIgnoreCase)); + } + + public Models.Lookups.LinkStatus GetById(Guid id) + { + var result = GetByIdOrNull(id) ?? throw new ArgumentException($"{nameof(Models.Lookups.LinkStatus)} with '{id}' does not exists", nameof(id)); + return result; + } + + public Models.Lookups.LinkStatus? GetByIdOrNull(Guid id) + { + if (id == Guid.Empty) + throw new ArgumentNullException(nameof(id)); + + return List().SingleOrDefault(o => o.Id == id); + } + + public List List() + { + if (!_appSettings.CacheEnabledByCacheItemTypesAsEnum.HasFlag(Core.CacheItemType.Lookups)) + return [.. _linkStatusRepository.Query().OrderBy(o => o.Name)]; + + var result = _memoryCache.GetOrCreate(nameof(Models.Lookups.LinkStatus), entry => + { + entry.SlidingExpiration = TimeSpan.FromHours(_appSettings.CacheSlidingExpirationInHours); + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(_appSettings.CacheAbsoluteExpirationRelativeToNowInDays); + return _linkStatusRepository.Query().OrderBy(o => o.Name).ToList(); + }) ?? throw new InvalidOperationException($"Failed to retrieve cached list of '{nameof(Models.Lookups.LinkStatus)}s'"); + return result; + } + #endregion + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/EmailProvider/Enumerations.cs b/src/api/src/domain/Yoma.Core.Domain/EmailProvider/Enumerations.cs index 94ff5a092..a3d2b1bb4 100644 --- a/src/api/src/domain/Yoma.Core.Domain/EmailProvider/Enumerations.cs +++ b/src/api/src/domain/Yoma.Core.Domain/EmailProvider/Enumerations.cs @@ -11,6 +11,7 @@ public enum EmailType Opportunity_Expiration_WithinNextDays, //sent to organization admin Opportunity_Posted_Admin, //sent to admin Opportunity_Verification_Pending, //sent to youth - Opportunity_Verification_Pending_Admin //sent to organization admin + Opportunity_Verification_Pending_Admin, //sent to organization admin + Opportunity_Verification_Instant //sent to youth } } diff --git a/src/api/src/domain/Yoma.Core.Domain/Entity/Enumerations.cs b/src/api/src/domain/Yoma.Core.Domain/Entity/Enumerations.cs index ca362df6e..2a3a8eefe 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Entity/Enumerations.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Entity/Enumerations.cs @@ -2,9 +2,9 @@ namespace Yoma.Core.Domain.Entity { public enum OrganizationStatus { - Inactive, //flagged as declined if inactive for x days + Inactive, //flagged as declined if inactive and not modified for x days Active, - Declined, //flagged as deleted if declined for x days + Declined, //flagged as deleted if declined and not modified for x days Deleted } diff --git a/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequest.cs b/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequest.cs index e74db7f0d..208abb522 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequest.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequest.cs @@ -1,12 +1,9 @@ -using System.ComponentModel.DataAnnotations; - namespace Yoma.Core.Domain.Entity.Models { public class UserRequest : UserRequestBase { public Guid? Id { get; set; } - [Required] public bool EmailConfirmed { get; set; } public DateTimeOffset? DateLastLogin { get; set; } diff --git a/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestBase.cs b/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestBase.cs index 953850b6f..5e4b69315 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestBase.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestBase.cs @@ -1,16 +1,11 @@ -using System.ComponentModel.DataAnnotations; - namespace Yoma.Core.Domain.Entity.Models { public abstract class UserRequestBase { - [Required] public string Email { get; set; } - [Required] public string FirstName { get; set; } - [Required] public string Surname { get; set; } public string? DisplayName { get; set; } diff --git a/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestProfile.cs b/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestProfile.cs index 19aa1fb40..8368cfd30 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestProfile.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Entity/Models/UserRequestProfile.cs @@ -1,10 +1,7 @@ -using System.ComponentModel.DataAnnotations; - namespace Yoma.Core.Domain.Entity.Models { public class UserRequestProfile : UserRequestBase { - [Required] public bool ResetPassword { get; set; } } } diff --git a/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Enumerations.cs b/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Enumerations.cs index 5901c4930..dfee0f068 100644 --- a/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Enumerations.cs +++ b/src/api/src/domain/Yoma.Core.Domain/MyOpportunity/Enumerations.cs @@ -10,7 +10,7 @@ public enum Action public enum VerificationStatus { None, - Pending, //flagged as rejected if pending for x days + Pending, //flagged as rejected if pending and not modified for x days Rejected, Completed } diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Enumerations.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Enumerations.cs index 1817440c9..c544398c9 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Enumerations.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Enumerations.cs @@ -4,8 +4,8 @@ public enum Status { Active, //flagged as expired provided ended (notified) Deleted, - Expired, //flagged as deleted if expired for x days - Inactive, //flagged expired provided ended (notified), or as deleted if inactive for x days + Expired, //flagged as deleted if expired and not modified for x days + Inactive, //flagged as deleted if inactive and not modified for x days } public enum VerificationMethod diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Extensions/OpportunityExtensions.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Extensions/OpportunityExtensions.cs index 4ce5af755..5e8e926a7 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Extensions/OpportunityExtensions.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Extensions/OpportunityExtensions.cs @@ -47,7 +47,7 @@ public static int TimeIntervalToDays(this Models.Opportunity opportunity) return days; } - public static (bool result, string? message) PublishedOrExpired(this Models.Opportunity opportunity) + public static (bool found, string? message) PublishedOrExpired(this Models.Opportunity opportunity) { ArgumentNullException.ThrowIfNull(opportunity, nameof(opportunity)); @@ -81,9 +81,18 @@ public static OpportunitySearchCriteriaItem ToOpportunitySearchCriteria(this Mod public static string YomaInfoURL(this Models.Opportunity value, string appBaseURL) { + ArgumentNullException.ThrowIfNull(value, nameof(value)); + return appBaseURL.AppendPathSegment("opportunities").AppendPathSegment(value.Id).ToString(); } + public static string YomaInstantVerifyURL(this Models.Opportunity value, string appBaseURL) + { + ArgumentNullException.ThrowIfNull(value, nameof(value)); + + return appBaseURL.AppendPathSegment("opportunities/actionLink/verify"); + } + public static OpportunityInfo ToOpportunityInfo(this Models.Opportunity value, string appBaseURL) { ArgumentNullException.ThrowIfNull(value, nameof(value)); @@ -103,7 +112,6 @@ public static OpportunityInfo ToOpportunityInfo(this Models.Opportunity value, s Summary = value.Summary, Instructions = value.Instructions, URL = value.URL, - ShortURL = value.ShortURL, ZltoReward = value.ZltoReward, YomaReward = value.YomaReward, VerificationEnabled = value.VerificationEnabled, diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Interfaces/IOpportunityService.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Interfaces/IOpportunityService.cs index 111dc7f0f..ab069a5a9 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Interfaces/IOpportunityService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Interfaces/IOpportunityService.cs @@ -1,3 +1,4 @@ +using Yoma.Core.Domain.ActionLink.Models; using Yoma.Core.Domain.Entity.Models; using Yoma.Core.Domain.Opportunity.Models; @@ -11,8 +12,6 @@ public interface IOpportunityService Models.Opportunity? GetByTitleOrNull(string title, bool includeChildItems, bool includeComputed); - Task GetSharingDetails(Guid id, bool publishedOrExpiredOnly, bool? includeQRCode); - List Contains(string value, bool includeComputed); OpportunitySearchResultsCriteria SearchCriteriaOpportunities(OpportunitySearchFilterCriteria filter, bool ensureOrganizationAuthorization); @@ -66,5 +65,9 @@ public interface IOpportunityService Task AssignVerificationTypes(Guid id, List verificationTypes, bool ensureOrganizationAuthorization); Task RemoveVerificationTypes(Guid id, List verificationTypes, bool ensureOrganizationAuthorization); + + Task CreateLinkSharing(Guid id, bool publishedOrExpiredOnly, bool? includeQRCode, bool ensureOrganizationAuthorization); + + Task CreateLinkInstantVerify(Guid id, OpportunityRequestLinkInstantVerify request, bool ensureOrganizationAuthorization); } } diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/Opportunity.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/Opportunity.cs index 7054fdc9b..b90f552c6 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/Opportunity.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/Opportunity.cs @@ -41,8 +41,6 @@ public class Opportunity public string? URL { get; set; } - public string? ShortURL { get; set; } - public decimal? ZltoReward { get; set; } public decimal? ZltoRewardPool { get; set; } diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityInfo.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityInfo.cs index ed1a83d16..a9551a82e 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityInfo.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityInfo.cs @@ -30,8 +30,6 @@ public class OpportunityInfo public string? URL { get; set; } - public string? ShortURL { get; set; } - [Name("Zlto Reward")] public decimal? ZltoReward { get; set; } diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestBase.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestBase.cs index 370d458d2..caed94ff5 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestBase.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestBase.cs @@ -1,19 +1,13 @@ -using System.ComponentModel.DataAnnotations; - namespace Yoma.Core.Domain.Opportunity.Models { public abstract class OpportunityRequestBase { - [Required] public string Title { get; set; } - [Required] public string Description { get; set; } - [Required] public Guid TypeId { get; set; } - [Required] public Guid OrganizationId { get; set; } public string? Summary { get; set; } @@ -30,44 +24,34 @@ public abstract class OpportunityRequestBase public decimal? YomaRewardPool { get; set; } - [Required] public bool VerificationEnabled { get; set; } public VerificationMethod? VerificationMethod { get; set; } - [Required] public Guid DifficultyId { get; set; } - [Required] public Guid CommitmentIntervalId { get; set; } - [Required] public short CommitmentIntervalCount { get; set; } public int? ParticipantLimit { get; set; } public List? Keywords { get; set; } - [Required] public DateTimeOffset DateStart { get; set; } public DateTimeOffset? DateEnd { get; set; } - [Required] public bool CredentialIssuanceEnabled { get; set; } public string? SSISchemaName { get; set; } - [Required] public List Categories { get; set; } - [Required] public List Countries { get; set; } - [Required] public List Languages { get; set; } - [Required] public List Skills { get; set; } public List? VerificationTypes { get; set; } diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestCreate.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestCreate.cs index 28a26aeab..e02844ab8 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestCreate.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestCreate.cs @@ -1,10 +1,7 @@ -using System.ComponentModel.DataAnnotations; - namespace Yoma.Core.Domain.Opportunity.Models { public class OpportunityRequestCreate : OpportunityRequestBase { - [Required] public bool PostAsActive { get; set; } } } diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestLinkInstantVerify.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestLinkInstantVerify.cs new file mode 100644 index 000000000..c077f88c2 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestLinkInstantVerify.cs @@ -0,0 +1,17 @@ +namespace Yoma.Core.Domain.Opportunity.Models +{ + public class OpportunityRequestLinkInstantVerify + { + public string? Name { get; set; } + + public string? Description { get; set; } + + public int? UsagesLimit { get; set; } + + public List? DistributionList { get; set; } + + public DateTimeOffset? DateEnd { get; set; } + + public bool? IncludeQRCode { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestUpdate.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestUpdate.cs index a4628bb4d..a8ade408f 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestUpdate.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunityRequestUpdate.cs @@ -1,10 +1,7 @@ -using System.ComponentModel.DataAnnotations; - namespace Yoma.Core.Domain.Opportunity.Models { public class OpportunityRequestUpdate : OpportunityRequestBase { - [Required] public Guid Id { get; set; } } } diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunitySharingResult.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunitySharingResult.cs deleted file mode 100644 index 29eb8b1aa..000000000 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunitySharingResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Yoma.Core.Domain.Opportunity.Models -{ - public class OpportunitySharingResult - { - public string ShortURL { get; set; } - - public string? QRCodeBase64 { get; set; } - } -} diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityService.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityService.cs index 472eefaf4..fc628590d 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityService.cs @@ -1,8 +1,12 @@ using FluentValidation; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Transactions; +using Yoma.Core.Domain.ActionLink.Extensions; +using Yoma.Core.Domain.ActionLink.Interfaces; +using Yoma.Core.Domain.ActionLink.Models; using Yoma.Core.Domain.BlobProvider; using Yoma.Core.Domain.Core; using Yoma.Core.Domain.Core.Exceptions; @@ -25,7 +29,6 @@ using Yoma.Core.Domain.Opportunity.Interfaces.Lookups; using Yoma.Core.Domain.Opportunity.Models; using Yoma.Core.Domain.Opportunity.Validators; -using Yoma.Core.Domain.ShortLinkProvider.Interfaces; namespace Yoma.Core.Domain.Opportunity.Services { @@ -49,10 +52,10 @@ public class OpportunityService : IOpportunityService private readonly ITimeIntervalService _timeIntervalService; private readonly IBlobService _blobService; private readonly IUserService _userService; + private readonly ILinkService _linkService; private readonly IEmailURLFactory _emailURLFactory; private readonly IEmailProviderClient _emailProviderClient; private readonly IIdentityProviderClient _identityProviderClient; - private readonly IShortLinkProviderClient _shortLinkProviderClient; private readonly OpportunityRequestValidatorCreate _opportunityRequestValidatorCreate; private readonly OpportunityRequestValidatorUpdate _opportunityRequestValidatorUpdate; @@ -93,10 +96,10 @@ public OpportunityService(ILogger logger, ITimeIntervalService timeIntervalService, IBlobService blobService, IUserService userService, + ILinkService linkService, IEmailURLFactory emailURLFactory, IEmailProviderClientFactory emailProviderClientFactory, IIdentityProviderClientFactory identityProviderClientFactory, - IShortLinkProviderClientFactory shortLinkProviderClientFactory, OpportunityRequestValidatorCreate opportunityRequestValidatorCreate, OpportunityRequestValidatorUpdate opportunityRequestValidatorUpdate, OpportunitySearchFilterValidator opportunitySearchFilterValidator, @@ -126,10 +129,10 @@ public OpportunityService(ILogger logger, _timeIntervalService = timeIntervalService; _blobService = blobService; _userService = userService; + _linkService = linkService; _emailURLFactory = emailURLFactory; _emailProviderClient = emailProviderClientFactory.CreateClient(); _identityProviderClient = identityProviderClientFactory.CreateClient(); - _shortLinkProviderClient = shortLinkProviderClientFactory.CreateClient(); _opportunityRequestValidatorCreate = opportunityRequestValidatorCreate; _opportunityRequestValidatorUpdate = opportunityRequestValidatorUpdate; @@ -198,46 +201,6 @@ public Models.Opportunity GetById(Guid id, bool includeChildItems, bool includeC return result; } - public async Task GetSharingDetails(Guid id, bool publishedOrExpiredOnly, bool? includeQRCode) - { - if (id == Guid.Empty) - throw new ArgumentNullException(nameof(id)); - - var opportunity = GetById(id, false, false, false); - - if (publishedOrExpiredOnly) - { - var (result, message) = opportunity.PublishedOrExpired(); - - if (!result) - { - ArgumentException.ThrowIfNullOrEmpty(message); - throw new EntityNotFoundException(message); - } - } - - if (string.IsNullOrEmpty(opportunity.ShortURL)) - { - var request = new ShortLinkProvider.Models.ShortLinkRequest - { - Type = ShortLinkProvider.EntityType.Opportunity, - Action = ShortLinkProvider.Action.Sharing, - Title = opportunity.Title, - URL = opportunity.YomaInfoURL(_appSettings.AppBaseURL) - }; - - var response = await _shortLinkProviderClient.CreateShortLink(request); - opportunity.ShortURL = response.Link; - await _opportunityRepository.Update(opportunity); - } - - return new OpportunitySharingResult - { - ShortURL = opportunity.ShortURL, - QRCodeBase64 = includeQRCode == true ? QRCodeHelper.GenerateQRCodeBase64(opportunity.ShortURL) : null - }; - } - public List Contains(string value, bool includeComputed) { if (string.IsNullOrWhiteSpace(value)) @@ -1409,6 +1372,85 @@ await _executionStrategyService.ExecuteInExecutionStrategyAsync(async () => return result; } + + public async Task CreateLinkSharing(Guid id, bool publishedOrExpiredOnly, bool? includeQRCode, bool ensureOrganizationAuthorization) + { + if (id == Guid.Empty) + throw new ArgumentNullException(nameof(id)); + + var opportunity = GetById(id, false, false, ensureOrganizationAuthorization); + + if (publishedOrExpiredOnly) + { + var (found, message) = opportunity.PublishedOrExpired(); + + if (!found) + { + ArgumentException.ThrowIfNullOrEmpty(message); + throw new EntityNotFoundException(message); + } + } + + var request = new LinkRequestCreate + { + Name = opportunity.Title.RemoveSpecialCharacters(), + EntityType = ActionLink.LinkEntityType.Opportunity, + Action = ActionLink.LinkAction.Share, + EntityId = opportunity.Id, + URL = opportunity.YomaInfoURL(_appSettings.AppBaseURL) + }; + + Link? result = null; + await _executionStrategyService.ExecuteInExecutionStrategyAsync(async () => + { + using var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled); + + result = await _linkService.Create(request, ensureOrganizationAuthorization); + result = await _linkService.LogUsage(result.Id); + + scope.Complete(); + }); + + if (result == null) + throw new InvalidOperationException("Failed to create sharing link"); + + return result.ToLinkInfo(includeQRCode); + } + + public async Task CreateLinkInstantVerify(Guid id, OpportunityRequestLinkInstantVerify request, bool ensureOrganizationAuthorization) + { + ArgumentNullException.ThrowIfNull(request, nameof(request)); + + var opportunity = GetById(id, false, false, ensureOrganizationAuthorization); + + //TODO: Validator + + if (opportunity.Status != Status.Active) + throw new ValidationException($"Link cannot be created as the opportunity '{opportunity.Title}' is not active"); + + if (string.IsNullOrEmpty(request.Name)) request.Name = opportunity.Title.RemoveSpecialCharacters(); + + if (request.DistributionList != null) request.DistributionList = request.DistributionList.Distinct().ToList(); + + var requestLink = new LinkRequestCreate + { + Name = request.Name, + Description = request.Description, + EntityType = ActionLink.LinkEntityType.Opportunity, + Action = ActionLink.LinkAction.Verify, + EntityId = opportunity.Id, + URL = opportunity.YomaInstantVerifyURL(_appSettings.AppBaseURL), + UsagesLimit = request.UsagesLimit, + DateEnd = request.DateEnd + }; + + var result = await _linkService.Create(requestLink, ensureOrganizationAuthorization); + + //send emails if needed + + return result.ToLinkInfo(request.IncludeQRCode); + } + #endregion #region Private Members @@ -1847,7 +1889,6 @@ private static void ValidateUpdatable(Models.Opportunity opportunity) if (!Statuses_Updatable.Contains(opportunity.Status)) throw new ValidationException($"{nameof(Models.Opportunity)} can no longer be updated (current status '{opportunity.Status}'). Required state '{string.Join(" / ", Statuses_Updatable)}'"); } - #endregion } } diff --git a/src/api/src/domain/Yoma.Core.Domain/SSI/Services/SSICredentialService.cs b/src/api/src/domain/Yoma.Core.Domain/SSI/Services/SSICredentialService.cs index f25e2d8bc..5f6a03d19 100644 --- a/src/api/src/domain/Yoma.Core.Domain/SSI/Services/SSICredentialService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/SSI/Services/SSICredentialService.cs @@ -59,7 +59,7 @@ public async Task ScheduleIssuance(string schemaName, Guid entityId) break; case SchemaType.YoID: - existingItem = _ssiCredentialIssuanceRepository.Query().SingleOrDefault(o => o.SchemaTypeId == item.SchemaTypeId && o.OrganizationId == entityId); + existingItem = _ssiCredentialIssuanceRepository.Query().SingleOrDefault(o => o.SchemaTypeId == item.SchemaTypeId && o.UserId == entityId); item.UserId = entityId; break; } diff --git a/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Enumerations.cs b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Enumerations.cs deleted file mode 100644 index f81007fe5..000000000 --- a/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Enumerations.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Yoma.Core.Domain.ShortLinkProvider -{ - public enum EntityType - { - Opportunity - } - - public enum Action - { - Sharing, - MagicLink //auto-verify - } -} diff --git a/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkRequest.cs b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkRequest.cs index f4ddd2d1c..55d7ef05f 100644 --- a/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkRequest.cs +++ b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkRequest.cs @@ -1,10 +1,12 @@ +using Yoma.Core.Domain.ActionLink; + namespace Yoma.Core.Domain.ShortLinkProvider.Models { public class ShortLinkRequest { - public EntityType Type { get; set; } + public LinkEntityType Type { get; set; } - public Action Action { get; set; } + public LinkAction Action { get; set; } public string Title { get; set; } diff --git a/src/api/src/domain/Yoma.Core.Domain/Startup.cs b/src/api/src/domain/Yoma.Core.Domain/Startup.cs index 125414b96..2d0deefb4 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Startup.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Startup.cs @@ -2,6 +2,9 @@ using Hangfire; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Yoma.Core.Domain.ActionLink.Interfaces; +using Yoma.Core.Domain.ActionLink.Services; +using Yoma.Core.Domain.ActionLink.Services.Lookups; using Yoma.Core.Domain.Analytics.Interfaces; using Yoma.Core.Domain.Analytics.Services; using Yoma.Core.Domain.Core.Interfaces; @@ -48,6 +51,14 @@ public static void ConfigureServices_DomainServices(this IServiceCollection serv //register all validators in Yoma.Core.Domain assembly services.AddValidatorsFromAssemblyContaining(); + #region ActionLink + #region Lookups + services.AddScoped(); + #endregion Lookups + + services.AddScoped(); + #endregion ActionLink + #region Analytics services.AddScoped(); #endregion Analytics diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/Link.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/Link.cs new file mode 100644 index 000000000..401631ab0 --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/Link.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Yoma.Core.Infrastructure.Database.Core.Entities; +using Yoma.Core.Infrastructure.Database.Entity.Entities; + +namespace Yoma.Core.Infrastructure.Database.ActionLink.Entities +{ + [Table("Link", Schema = "ActionLink")] + [Index(nameof(URL), IsUnique = true)] + [Index(nameof(ShortURL), IsUnique = true)] + [Index(nameof(EntityType), nameof(Action), nameof(StatusId), nameof(OpportunityId), nameof(DateEnd), nameof(DateCreated))] + public class Link : BaseEntity + { + [Required] + [Column(TypeName = "varchar(255)")] //MS SQL: nvarchar(255) + public string Name { get; set; } + + [Column(TypeName = "varchar(500)")] //MS SQL: nvarchar(MAX) + public string? Description { get; set; } + + [Required] + [Column(TypeName = "varchar(25)")] + public string EntityType { get; set; } + + [Required] + [Column(TypeName = "varchar(25)")] + public string Action { get; set; } + + [Required] + [ForeignKey("StatusId")] + public Guid StatusId { get; set; } + public Lookups.LinkStatus Status { get; set; } + + [ForeignKey("OpportunityId")] + public Guid? OpportunityId { get; set; } + public Opportunity.Entities.Opportunity? Opportunity { get; set; } + + [Required] + [Column(TypeName = "varchar(2048)")] + public string URL { get; set; } + + [Required] + [Column(TypeName = "varchar(2048)")] + public string ShortURL { get; set; } + + public int? UsagesLimit { get; set; } + + public int? UsagesTotal { get; set; } + + public DateTimeOffset? DateEnd { get; set; } + + [Required] + public DateTimeOffset DateCreated { get; set; } + + [Required] + [ForeignKey("CreatedByUserId")] + public Guid CreatedByUserId { get; set; } + public User CreatedByUser { get; set; } + + [Required] + public DateTimeOffset DateModified { get; set; } + + [Required] + [ForeignKey("ModifiedByUserId")] + public Guid ModifiedByUserId { get; set; } + public User ModifiedByUser { get; set; } + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/LinkUsageLog.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/LinkUsageLog.cs new file mode 100644 index 000000000..6f277622d --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/LinkUsageLog.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Yoma.Core.Infrastructure.Database.Core.Entities; + +namespace Yoma.Core.Infrastructure.Database.ActionLink.Entities +{ + [Table("UsageLog", Schema = "ActionLink")] + [Index(nameof(LinkId), nameof(UserId), IsUnique = true)] + [Index(nameof(DateCreated))] + public class LinkUsageLog : BaseEntity + { + [ForeignKey("LinkId")] + public Guid LinkId { get; set; } + public Link Link { get; set; } + + [ForeignKey("UserId")] + public Guid UserId { get; set; } + public Entity.Entities.User? User { get; set; } + + [Required] + public DateTimeOffset DateCreated { get; set; } + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/Lookups/LinkStatus.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/Lookups/LinkStatus.cs new file mode 100644 index 000000000..3f3d0e1d4 --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Entities/Lookups/LinkStatus.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Yoma.Core.Infrastructure.Database.Core.Entities; + +namespace Yoma.Core.Infrastructure.Database.ActionLink.Entities.Lookups +{ + [Table("Status", Schema = "ActionLink")] + [Index(nameof(Name), IsUnique = true)] + public class LinkStatus : BaseEntity + { + [Required] + [Column(TypeName = "varchar(20)")] + public string Name { get; set; } + + [Required] + public DateTimeOffset DateCreated { get; set; } + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/LinkRepository.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/LinkRepository.cs new file mode 100644 index 000000000..8928cf284 --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/LinkRepository.cs @@ -0,0 +1,95 @@ +using Yoma.Core.Domain.ActionLink; +using Yoma.Core.Domain.ActionLink.Models; +using Yoma.Core.Domain.Core.Interfaces; +using Yoma.Core.Infrastructure.Database.Context; +using Yoma.Core.Infrastructure.Database.Core.Repositories; + +namespace Yoma.Core.Infrastructure.Database.ActionLink.Repositories +{ + public class LinkRepository : BaseRepository, IRepository + { + #region Constructor + public LinkRepository(ApplicationDbContext context) : base(context) { } + #endregion + + #region Public Members + public IQueryable Query() + { + return _context.Link.Select(entity => new Link + { + Id = entity.Id, + Name = entity.Name, + Description = entity.Description, + EntityType = entity.EntityType, + Action = entity.Action, + StatusId = entity.StatusId, + Status = Enum.Parse(entity.Status.Name, true), + OpportunityId = entity.OpportunityId, + URL = entity.URL, + ShortURL = entity.URL, + UsagesLimit = entity.UsagesLimit, + UsagesTotal = entity.UsagesTotal, + DateEnd = entity.DateEnd, + DateCreated = entity.DateCreated, + CreatedByUserId = entity.CreatedByUserId, + DateModified = entity.DateModified, + ModifiedByUserId = entity.ModifiedByUserId + }); + } + + public async Task Create(Link item) + { + item.DateCreated = DateTimeOffset.UtcNow; + item.DateModified = DateTimeOffset.UtcNow; + + var entity = new Entities.Link + { + Id = item.Id, + Name = item.Name, + Description = item.Description, + EntityType = item.EntityType, + Action = item.Action, + StatusId = item.StatusId, + OpportunityId = item.OpportunityId, + URL = item.URL, + ShortURL = item.ShortURL, + UsagesLimit = item.UsagesLimit, + UsagesTotal = item.UsagesTotal, + DateEnd = item.DateEnd, + DateCreated = item.DateCreated, + CreatedByUserId = item.CreatedByUserId, + DateModified = item.DateModified, + ModifiedByUserId = item.ModifiedByUserId + }; + + _context.Link.Add(entity); + await _context.SaveChangesAsync(); + + item.Id = entity.Id; + return item; + } + + public async Task Update(Link item) + { + var entity = _context.Link.Where(o => o.Id == item.Id).SingleOrDefault() + ?? throw new ArgumentOutOfRangeException(nameof(item), $"{nameof(Entities.Link)} with id '{item.Id}' does not exist"); + + item.DateModified = DateTimeOffset.UtcNow; + + entity.UsagesTotal = item.UsagesTotal; + entity.StatusId = item.StatusId; + entity.DateModified = item.DateModified; + entity.ModifiedByUserId = item.ModifiedByUserId; + + await _context.SaveChangesAsync(); + + return item; + } + + public Task Delete(Link item) + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/LinkUsageLogRepository.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/LinkUsageLogRepository.cs new file mode 100644 index 000000000..ee2a8ea31 --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/LinkUsageLogRepository.cs @@ -0,0 +1,56 @@ +using Yoma.Core.Domain.ActionLink.Models; +using Yoma.Core.Domain.Core.Interfaces; +using Yoma.Core.Infrastructure.Database.Context; +using Yoma.Core.Infrastructure.Database.Core.Repositories; + +namespace Yoma.Core.Infrastructure.Database.ActionLink.Repositories +{ + internal class LinkUsageLogRepository : BaseRepository, IRepository + { + #region Constructor + public LinkUsageLogRepository(ApplicationDbContext context) : base(context) { } + #endregion + + #region Public Members + public IQueryable Query() + { + return _context.LinkUsageLog.Select(entity => new LinkUsageLog + { + Id = entity.Id, + LinkId = entity.LinkId, + UserId = entity.UserId, + DateCreated = entity.DateCreated + }); + } + + public async Task Create(LinkUsageLog item) + { + item.DateCreated = DateTimeOffset.UtcNow; + + var entity = new Entities.LinkUsageLog + { + Id = item.Id, + LinkId = item.LinkId, + UserId = item.UserId, + DateCreated = item.DateCreated + }; + + _context.LinkUsageLog.Add(entity); + await _context.SaveChangesAsync(); + + item.Id = entity.Id; + return item; + } + + public Task Update(LinkUsageLog item) + { + throw new NotImplementedException(); + } + + public Task Delete(LinkUsageLog item) + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/Lookups/LinkStatusRepository.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/Lookups/LinkStatusRepository.cs new file mode 100644 index 000000000..2bebf9a47 --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/ActionLink/Repositories/Lookups/LinkStatusRepository.cs @@ -0,0 +1,41 @@ +using Yoma.Core.Domain.ActionLink.Models.Lookups; +using Yoma.Core.Domain.Core.Interfaces; +using Yoma.Core.Infrastructure.Database.Context; +using Yoma.Core.Infrastructure.Database.Core.Repositories; + +namespace Yoma.Core.Infrastructure.Database.ActionLink.Repositories.Lookups +{ + public class LinkStatusRepository : BaseRepository, IRepository + { + #region Constructor + public LinkStatusRepository(ApplicationDbContext context) : base(context) + { + } + #endregion + + #region Public Members + public IQueryable Query() + { + return _context.LinkStatus.Select(entity => new LinkStatus + { + Id = entity.Id, + Name = entity.Name + }); + } + + public Task Create(LinkStatus item) + { + throw new NotImplementedException(); + } + + public Task Update(LinkStatus item) + { + throw new NotImplementedException(); + } + public Task Delete(LinkStatus item) + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Context/ApplicationDbContext.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Context/ApplicationDbContext.cs index d7deecd39..91baeffa9 100644 --- a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Context/ApplicationDbContext.cs +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Context/ApplicationDbContext.cs @@ -19,6 +19,16 @@ public ApplicationDbContext(DbContextOptions options) : ba #endregion #region Public Members + #region ActionLink + #region Lookups + public DbSet LinkStatus { get; set; } + #endregion Lookups + + public DbSet Link { get; set; } + + public DbSet LinkUsageLog { get; set; } + #endregion ActionLink + #region Core public DbSet BlobObject { get; set; } #endregion Core diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink.Designer.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink.Designer.cs new file mode 100644 index 000000000..f717ba7c3 --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink.Designer.cs @@ -0,0 +1,2318 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Yoma.Core.Infrastructure.Database.Context; + +#nullable disable + +namespace Yoma.Core.Infrastructure.Database.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240425033732_ApplicationDb_ActionLink")] + partial class ApplicationDb_ActionLink + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Link", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("varchar(500)"); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("ModifiedByUserId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.Property("ShortURL") + .IsRequired() + .HasColumnType("varchar(2048)"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("URL") + .IsRequired() + .HasColumnType("varchar(2048)"); + + b.Property("UsagesLimit") + .HasColumnType("integer"); + + b.Property("UsagesTotal") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("ModifiedByUserId"); + + b.HasIndex("OpportunityId"); + + b.HasIndex("ShortURL") + .IsUnique(); + + b.HasIndex("StatusId"); + + b.HasIndex("URL") + .IsUnique(); + + b.HasIndex("EntityType", "Action", "StatusId", "OpportunityId", "DateEnd", "DateCreated"); + + b.ToTable("Link", "ActionLink"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.LinkUsageLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("LinkId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DateCreated"); + + b.HasIndex("UserId"); + + b.HasIndex("LinkId", "UserId") + .IsUnique(); + + b.ToTable("UsageLog", "ActionLink"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Lookups.LinkStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Status", "ActionLink"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("varchar(127)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("FileType") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("StorageType") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("ParentId"); + + b.HasIndex("StorageType", "FileType", "ParentId"); + + b.ToTable("Blob", "Object"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.Lookups.OrganizationProviderType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OrganizationProviderType", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.Lookups.OrganizationStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OrganizationStatus", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Biography") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("varchar(50)"); + + b.Property("CommentApproval") + .HasColumnType("varchar(500)"); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("DateStatusModified") + .HasColumnType("timestamp with time zone"); + + b.Property("LogoId") + .HasColumnType("uuid"); + + b.Property("ModifiedByUserId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("NameHashValue") + .IsRequired() + .HasColumnType("varchar(128)"); + + b.Property("PostalCode") + .HasColumnType("varchar(10)"); + + b.Property("PrimaryContactEmail") + .HasColumnType("varchar(320)"); + + b.Property("PrimaryContactName") + .HasColumnType("varchar(255)"); + + b.Property("PrimaryContactPhone") + .HasColumnType("varchar(50)"); + + b.Property("Province") + .HasColumnType("varchar(255)"); + + b.Property("RegistrationNumber") + .HasColumnType("varchar(255)"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("StreetAddress") + .HasColumnType("varchar(500)"); + + b.Property("Tagline") + .HasColumnType("text"); + + b.Property("TaxNumber") + .HasColumnType("varchar(255)"); + + b.Property("VATIN") + .HasColumnType("varchar(255)"); + + b.Property("WebsiteURL") + .HasColumnType("varchar(2048)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("LogoId"); + + b.HasIndex("ModifiedByUserId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("NameHashValue") + .IsUnique(); + + b.HasIndex("StatusId", "DateStatusModified", "DateCreated", "CreatedByUserId", "DateModified", "ModifiedByUserId"); + + b.ToTable("Organization", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.OrganizationDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OrganizationId", "Type", "DateCreated"); + + b.ToTable("OrganizationDocuments", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.OrganizationProviderType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderTypeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderTypeId"); + + b.HasIndex("OrganizationId", "ProviderTypeId") + .IsUnique(); + + b.ToTable("OrganizationProviderTypes", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.OrganizationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique(); + + b.ToTable("OrganizationUsers", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateLastLogin") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("DateOfBirth") + .HasColumnType("timestamp with time zone"); + + b.Property("DateYoIDOnboarded") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("EducationId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("varchar(320)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("ExternalId") + .HasColumnType("uuid"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.Property("GenderId") + .HasColumnType("uuid"); + + b.Property("PhoneNumber") + .HasColumnType("varchar(50)"); + + b.Property("PhotoId") + .HasColumnType("uuid"); + + b.Property("Surname") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.Property("YoIDOnboarded") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("EducationId"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("GenderId"); + + b.HasIndex("PhotoId"); + + b.HasIndex("FirstName", "Surname", "EmailConfirmed", "PhoneNumber", "ExternalId", "YoIDOnboarded", "DateYoIDOnboarded", "DateCreated", "DateModified"); + + b.ToTable("User", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.UserSkill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SkillId"); + + b.HasIndex("UserId", "SkillId") + .IsUnique(); + + b.ToTable("UserSkills", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.UserSkillOrganization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserSkillId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserSkillId", "OrganizationId") + .IsUnique(); + + b.ToTable("UserSkillOrganizations", "Entity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Lookups.Entities.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CodeAlpha2") + .IsRequired() + .HasColumnType("varchar(2)"); + + b.Property("CodeAlpha3") + .IsRequired() + .HasColumnType("varchar(3)"); + + b.Property("CodeNumeric") + .IsRequired() + .HasColumnType("varchar(3)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.HasKey("Id"); + + b.HasIndex("CodeAlpha2") + .IsUnique(); + + b.HasIndex("CodeAlpha3") + .IsUnique(); + + b.HasIndex("CodeNumeric") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Country", "Lookup"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Lookups.Entities.Education", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Education", "Lookup"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Lookups.Entities.Gender", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Gender", "Lookup"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Lookups.Entities.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CodeAlpha2") + .IsRequired() + .HasColumnType("varchar(2)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.HasKey("Id"); + + b.HasIndex("CodeAlpha2") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Language", "Lookup"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Lookups.Entities.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("varchar(100)"); + + b.Property("InfoURL") + .HasColumnType("varchar(2048)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("ExternalId") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Skill", "Lookup"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Lookups.Entities.TimeInterval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("TimeInterval", "Lookup"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Marketplace.Entities.Lookups.TransactionStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(30)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("TransactionStatus", "Marketplace"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Marketplace.Entities.TransactionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("decimal(8,2)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("ItemCategoryId") + .IsRequired() + .HasColumnType("varchar(50)"); + + b.Property("ItemId") + .IsRequired() + .HasColumnType("varchar(50)"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("TransactionId") + .IsRequired() + .HasColumnType("varchar(50)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("StatusId"); + + b.HasIndex("UserId", "ItemCategoryId", "ItemId", "StatusId", "DateCreated", "DateModified"); + + b.ToTable("TransactionLog", "Marketplace"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.Lookups.MyOpportunityAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MyOpportunityAction", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.Lookups.MyOpportunityVerificationStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MyOpportunityVerificationStatus", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActionId") + .HasColumnType("uuid"); + + b.Property("CommentVerification") + .HasColumnType("varchar(500)"); + + b.Property("DateCompleted") + .HasColumnType("timestamp with time zone"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("DateStart") + .HasColumnType("timestamp with time zone"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerificationStatusId") + .HasColumnType("uuid"); + + b.Property("YomaReward") + .HasColumnType("decimal(8,2)"); + + b.Property("ZltoReward") + .HasColumnType("decimal(8,2)"); + + b.HasKey("Id"); + + b.HasIndex("ActionId"); + + b.HasIndex("OpportunityId"); + + b.HasIndex("UserId", "OpportunityId", "ActionId") + .IsUnique(); + + b.HasIndex("VerificationStatusId", "DateCompleted", "ZltoReward", "YomaReward", "DateCreated", "DateModified"); + + b.ToTable("MyOpportunity", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunityVerification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("GeometryProperties") + .HasColumnType("text"); + + b.Property("MyOpportunityId") + .HasColumnType("uuid"); + + b.Property("VerificationTypeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.HasIndex("VerificationTypeId"); + + b.HasIndex("MyOpportunityId", "VerificationTypeId") + .IsUnique(); + + b.ToTable("MyOpportunityVerifications", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("ImageURL") + .IsRequired() + .HasColumnType("varchar(2048)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpportunityCategory", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityDifficulty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpportunityDifficulty", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpportunityStatus", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpportunityType", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityVerificationType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpportunityVerificationType", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CommitmentIntervalCount") + .HasColumnType("smallint"); + + b.Property("CommitmentIntervalId") + .HasColumnType("uuid"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("CredentialIssuanceEnabled") + .HasColumnType("boolean"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("DateStart") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DifficultyId") + .HasColumnType("uuid"); + + b.Property("Instructions") + .HasColumnType("text"); + + b.Property("Keywords") + .HasColumnType("varchar(500)"); + + b.Property("ModifiedByUserId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ParticipantCount") + .HasColumnType("integer"); + + b.Property("ParticipantLimit") + .HasColumnType("integer"); + + b.Property("SSISchemaName") + .HasColumnType("varchar(255)"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("Summary") + .HasColumnType("varchar(500)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("TypeId") + .HasColumnType("uuid"); + + b.Property("URL") + .HasColumnType("varchar(2048)"); + + b.Property("VerificationEnabled") + .HasColumnType("boolean"); + + b.Property("VerificationMethod") + .HasColumnType("varchar(20)"); + + b.Property("YomaReward") + .HasColumnType("decimal(8,2)"); + + b.Property("YomaRewardCumulative") + .HasColumnType("decimal(12,2)"); + + b.Property("YomaRewardPool") + .HasColumnType("decimal(12,2)"); + + b.Property("ZltoReward") + .HasColumnType("decimal(8,2)"); + + b.Property("ZltoRewardCumulative") + .HasColumnType("decimal(12,2)"); + + b.Property("ZltoRewardPool") + .HasColumnType("decimal(12,2)"); + + b.HasKey("Id"); + + b.HasIndex("CommitmentIntervalId"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("Description") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Description"), "GIN"); + + b.HasIndex("DifficultyId"); + + b.HasIndex("ModifiedByUserId"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("StatusId"); + + b.HasIndex("Title") + .IsUnique(); + + b.HasIndex("TypeId", "OrganizationId", "ZltoReward", "DifficultyId", "CommitmentIntervalId", "CommitmentIntervalCount", "StatusId", "Keywords", "DateStart", "DateEnd", "CredentialIssuanceEnabled", "DateCreated", "CreatedByUserId", "DateModified", "ModifiedByUserId"); + + b.ToTable("Opportunity", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("OpportunityId", "CategoryId") + .IsUnique(); + + b.ToTable("OpportunityCategories", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityCountry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("OpportunityId", "CountryId") + .IsUnique(); + + b.ToTable("OpportunityCountries", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityLanguage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("LanguageId") + .HasColumnType("uuid"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LanguageId"); + + b.HasIndex("OpportunityId", "LanguageId") + .IsUnique(); + + b.ToTable("OpportunityLanguages", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunitySkill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SkillId"); + + b.HasIndex("OpportunityId", "SkillId") + .IsUnique(); + + b.ToTable("OpportunitySkills", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityVerificationType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("varchar(255)"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.Property("VerificationTypeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("VerificationTypeId"); + + b.HasIndex("OpportunityId", "VerificationTypeId") + .IsUnique(); + + b.ToTable("OpportunityVerificationTypes", "Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Reward.Entities.Lookups.RewardTransactionStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(30)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_TransactionStatus_Name1"); + + b.ToTable("TransactionStatus", "Reward"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Reward.Entities.Lookups.WalletCreationStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("WalletCreationStatus", "Reward"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Reward.Entities.RewardTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("decimal(8,2)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorReason") + .HasColumnType("text"); + + b.Property("MyOpportunityId") + .HasColumnType("uuid"); + + b.Property("RetryCount") + .HasColumnType("smallint"); + + b.Property("SourceEntityType") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("TransactionId") + .HasColumnType("varchar(50)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MyOpportunityId"); + + b.HasIndex("StatusId", "DateCreated", "DateModified"); + + b.HasIndex("UserId", "SourceEntityType", "MyOpportunityId") + .IsUnique(); + + b.ToTable("Transaction", "Reward"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Reward.Entities.WalletCreation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Balance") + .HasColumnType("decimal(12,2)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorReason") + .HasColumnType("text"); + + b.Property("RetryCount") + .HasColumnType("smallint"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("WalletId") + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("StatusId", "DateCreated", "DateModified"); + + b.ToTable("WalletCreation", "Reward"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSICredentialIssuanceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("CredentialIssuanceStatus", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("TypeName") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("TypeName") + .IsUnique(); + + b.ToTable("SchemaEntity", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntityProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.Property("Format") + .HasColumnType("varchar(125)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(50)"); + + b.Property("NameDisplay") + .IsRequired() + .HasColumnType("varchar(50)"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("SSISchemaEntityId") + .HasColumnType("uuid"); + + b.Property("SystemType") + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("SSISchemaEntityId", "Name") + .IsUnique(); + + b.ToTable("SchemaEntityProperty", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntityType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("SSISchemaEntityId") + .HasColumnType("uuid"); + + b.Property("SSISchemaTypeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SSISchemaTypeId"); + + b.HasIndex("SSISchemaEntityId", "SSISchemaTypeId") + .IsUnique(); + + b.ToTable("SchemaEntityType", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.Property("SupportMultiple") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("SchemaType", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSITenantCreationStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("TenantCreationStatus", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.SSICredentialIssuance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArtifactType") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("CredentialId") + .HasColumnType("varchar(50)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorReason") + .HasColumnType("text"); + + b.Property("MyOpportunityId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RetryCount") + .HasColumnType("smallint"); + + b.Property("SchemaName") + .IsRequired() + .HasColumnType("varchar(125)"); + + b.Property("SchemaTypeId") + .HasColumnType("uuid"); + + b.Property("SchemaVersion") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MyOpportunityId"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("StatusId"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemaName", "UserId", "OrganizationId", "MyOpportunityId") + .IsUnique(); + + b.HasIndex("SchemaTypeId", "ArtifactType", "SchemaName", "StatusId", "DateCreated", "DateModified"); + + b.ToTable("CredentialIssuance", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.SSITenantCreation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("ErrorReason") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RetryCount") + .HasColumnType("smallint"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("varchar(50)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "UserId", "OrganizationId") + .IsUnique(); + + b.HasIndex("StatusId", "DateCreated", "DateModified"); + + b.ToTable("TenantCreation", "SSI"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Link", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "ModifiedByUser") + .WithMany() + .HasForeignKey("ModifiedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany() + .HasForeignKey("OpportunityId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Lookups.LinkStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("ModifiedByUser"); + + b.Navigation("Opportunity"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.LinkUsageLog", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Link", "Link") + .WithMany() + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", "Parent") + .WithMany() + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Country", "Country") + .WithMany() + .HasForeignKey("CountryId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", "Logo") + .WithMany() + .HasForeignKey("LogoId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "ModifiedByUser") + .WithMany() + .HasForeignKey("ModifiedByUserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Lookups.OrganizationStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Country"); + + b.Navigation("CreatedByUser"); + + b.Navigation("Logo"); + + b.Navigation("ModifiedByUser"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.OrganizationDocument", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", "Organization") + .WithMany("Documents") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.OrganizationProviderType", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", "Organization") + .WithMany("ProviderTypes") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Lookups.OrganizationProviderType", "ProviderType") + .WithMany() + .HasForeignKey("ProviderTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ProviderType"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.OrganizationUser", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", "Organization") + .WithMany("Administrators") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.User", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Country", "Country") + .WithMany() + .HasForeignKey("CountryId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Education", "Education") + .WithMany() + .HasForeignKey("EducationId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Gender", "Gender") + .WithMany() + .HasForeignKey("GenderId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", "Photo") + .WithMany() + .HasForeignKey("PhotoId"); + + b.Navigation("Country"); + + b.Navigation("Education"); + + b.Navigation("Gender"); + + b.Navigation("Photo"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.UserSkill", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Skill", "Skill") + .WithMany() + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany("Skills") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Skill"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.UserSkillOrganization", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.UserSkill", "UserSkill") + .WithMany("Organizations") + .HasForeignKey("UserSkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("UserSkill"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Marketplace.Entities.TransactionLog", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Marketplace.Entities.Lookups.TransactionStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Status"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunity", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.Lookups.MyOpportunityAction", "Action") + .WithMany() + .HasForeignKey("ActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany() + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.Lookups.MyOpportunityVerificationStatus", "VerificationStatus") + .WithMany() + .HasForeignKey("VerificationStatusId"); + + b.Navigation("Action"); + + b.Navigation("Opportunity"); + + b.Navigation("User"); + + b.Navigation("VerificationStatus"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunityVerification", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", "File") + .WithMany() + .HasForeignKey("FileId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunity", "MyOpportunity") + .WithMany("Verifications") + .HasForeignKey("MyOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityVerificationType", "VerificationType") + .WithMany() + .HasForeignKey("VerificationTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("MyOpportunity"); + + b.Navigation("VerificationType"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.TimeInterval", "CommitmentInterval") + .WithMany() + .HasForeignKey("CommitmentIntervalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityDifficulty", "Difficulty") + .WithMany() + .HasForeignKey("DifficultyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "ModifiedByUser") + .WithMany() + .HasForeignKey("ModifiedByUserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CommitmentInterval"); + + b.Navigation("CreatedByUser"); + + b.Navigation("Difficulty"); + + b.Navigation("ModifiedByUser"); + + b.Navigation("Organization"); + + b.Navigation("Status"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityCategory", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany("Categories") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityCountry", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany("Countries") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Country"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityLanguage", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Language", "Language") + .WithMany() + .HasForeignKey("LanguageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany("Languages") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Language"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunitySkill", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany("Skills") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Lookups.Entities.Skill", "Skill") + .WithMany() + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Opportunity"); + + b.Navigation("Skill"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.OpportunityVerificationType", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany("VerificationTypes") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Lookups.OpportunityVerificationType", "VerificationType") + .WithMany() + .HasForeignKey("VerificationTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Opportunity"); + + b.Navigation("VerificationType"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Reward.Entities.RewardTransaction", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunity", "MyOpportunity") + .WithMany() + .HasForeignKey("MyOpportunityId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.Reward.Entities.Lookups.RewardTransactionStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MyOpportunity"); + + b.Navigation("Status"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Reward.Entities.WalletCreation", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Reward.Entities.Lookups.WalletCreationStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Status"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntityProperty", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntity", "SSISchemaEntity") + .WithMany("Properties") + .HasForeignKey("SSISchemaEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SSISchemaEntity"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntityType", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntity", "SSISchemaEntity") + .WithMany("Types") + .HasForeignKey("SSISchemaEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaType", "SSISchemaType") + .WithMany() + .HasForeignKey("SSISchemaTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SSISchemaEntity"); + + b.Navigation("SSISchemaType"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.SSICredentialIssuance", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunity", "MyOpportunity") + .WithMany() + .HasForeignKey("MyOpportunityId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaType", "SchemaType") + .WithMany() + .HasForeignKey("SchemaTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSICredentialIssuanceStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("MyOpportunity"); + + b.Navigation("Organization"); + + b.Navigation("SchemaType"); + + b.Navigation("Status"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.SSITenantCreation", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSITenantCreationStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Status"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.Organization", b => + { + b.Navigation("Administrators"); + + b.Navigation("Documents"); + + b.Navigation("ProviderTypes"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.User", b => + { + b.Navigation("Skills"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Entity.Entities.UserSkill", b => + { + b.Navigation("Organizations"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.MyOpportunity.Entities.MyOpportunity", b => + { + b.Navigation("Verifications"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", b => + { + b.Navigation("Categories"); + + b.Navigation("Countries"); + + b.Navigation("Languages"); + + b.Navigation("Skills"); + + b.Navigation("VerificationTypes"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.SSI.Entities.Lookups.SSISchemaEntity", b => + { + b.Navigation("Properties"); + + b.Navigation("Types"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink.cs new file mode 100644 index 000000000..5711a7eaf --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink.cs @@ -0,0 +1,215 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Yoma.Core.Infrastructure.Database.Migrations +{ + /// + public partial class ApplicationDb_ActionLink : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ShortURL", + schema: "Opportunity", + table: "Opportunity"); + + migrationBuilder.EnsureSchema( + name: "ActionLink"); + + migrationBuilder.CreateTable( + name: "Status", + schema: "ActionLink", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "varchar(20)", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Status", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Link", + schema: "ActionLink", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "varchar(255)", nullable: false), + Description = table.Column(type: "varchar(500)", nullable: true), + EntityType = table.Column(type: "varchar(25)", nullable: false), + Action = table.Column(type: "varchar(25)", nullable: false), + StatusId = table.Column(type: "uuid", nullable: false), + OpportunityId = table.Column(type: "uuid", nullable: true), + URL = table.Column(type: "varchar(2048)", nullable: false), + ShortURL = table.Column(type: "varchar(2048)", nullable: false), + UsagesLimit = table.Column(type: "integer", nullable: true), + UsagesTotal = table.Column(type: "integer", nullable: true), + DateEnd = table.Column(type: "timestamp with time zone", nullable: true), + DateCreated = table.Column(type: "timestamp with time zone", nullable: false), + CreatedByUserId = table.Column(type: "uuid", nullable: false), + DateModified = table.Column(type: "timestamp with time zone", nullable: false), + ModifiedByUserId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => x.Id); + table.ForeignKey( + name: "FK_Link_Opportunity_OpportunityId", + column: x => x.OpportunityId, + principalSchema: "Opportunity", + principalTable: "Opportunity", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Link_Status_StatusId", + column: x => x.StatusId, + principalSchema: "ActionLink", + principalTable: "Status", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_User_CreatedByUserId", + column: x => x.CreatedByUserId, + principalSchema: "Entity", + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_User_ModifiedByUserId", + column: x => x.ModifiedByUserId, + principalSchema: "Entity", + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UsageLog", + schema: "ActionLink", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + LinkId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UsageLog", x => x.Id); + table.ForeignKey( + name: "FK_UsageLog_Link_LinkId", + column: x => x.LinkId, + principalSchema: "ActionLink", + principalTable: "Link", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UsageLog_User_UserId", + column: x => x.UserId, + principalSchema: "Entity", + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Link_CreatedByUserId", + schema: "ActionLink", + table: "Link", + column: "CreatedByUserId"); + + migrationBuilder.CreateIndex( + name: "IX_Link_EntityType_Action_StatusId_OpportunityId_DateEnd_DateC~", + schema: "ActionLink", + table: "Link", + columns: ["EntityType", "Action", "StatusId", "OpportunityId", "DateEnd", "DateCreated"]); + + migrationBuilder.CreateIndex( + name: "IX_Link_ModifiedByUserId", + schema: "ActionLink", + table: "Link", + column: "ModifiedByUserId"); + + migrationBuilder.CreateIndex( + name: "IX_Link_OpportunityId", + schema: "ActionLink", + table: "Link", + column: "OpportunityId"); + + migrationBuilder.CreateIndex( + name: "IX_Link_ShortURL", + schema: "ActionLink", + table: "Link", + column: "ShortURL", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Link_StatusId", + schema: "ActionLink", + table: "Link", + column: "StatusId"); + + migrationBuilder.CreateIndex( + name: "IX_Link_URL", + schema: "ActionLink", + table: "Link", + column: "URL", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Status_Name", + schema: "ActionLink", + table: "Status", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UsageLog_DateCreated", + schema: "ActionLink", + table: "UsageLog", + column: "DateCreated"); + + migrationBuilder.CreateIndex( + name: "IX_UsageLog_LinkId_UserId", + schema: "ActionLink", + table: "UsageLog", + columns: ["LinkId", "UserId"], + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UsageLog_UserId", + schema: "ActionLink", + table: "UsageLog", + column: "UserId"); + + ApplicationDb_ActionLink_Seeding.Seed(migrationBuilder); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UsageLog", + schema: "ActionLink"); + + migrationBuilder.DropTable( + name: "Link", + schema: "ActionLink"); + + migrationBuilder.DropTable( + name: "Status", + schema: "ActionLink"); + + migrationBuilder.AddColumn( + name: "ShortURL", + schema: "Opportunity", + table: "Opportunity", + type: "varchar(2048)", + nullable: true); + } + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink_Seeding.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink_Seeding.cs new file mode 100644 index 000000000..905bd274a --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240425033732_ApplicationDb_ActionLink_Seeding.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Yoma.Core.Infrastructure.Database.Migrations +{ + internal class ApplicationDb_ActionLink_Seeding + { + internal static void Seed(MigrationBuilder migrationBuilder) + { + #region ActionLink + migrationBuilder.InsertData( + table: "Status", + columns: ["Id", "Name", "DateCreated"], + values: new object[,] + { + {"B33EE4F4-F810-4134-9FD8-3EC30EE61BD5","Active",DateTimeOffset.UtcNow} + , + {"EF4D12CD-294D-43FE-9689-39B14D55E837","Inactive",DateTimeOffset.UtcNow} + , + {"4FDA3E52-23DD-49B5-9F35-293B9DC9A3AC","Expired",DateTimeOffset.UtcNow} + , + {"5414F827-013E-4877-8FF1-405B727A0482","LimitReached",DateTimeOffset.UtcNow} + }, + schema: "ActionLink"); + #endregion ActionLink + } + } +} diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/ApplicationDbContextModelSnapshot.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/ApplicationDbContextModelSnapshot.cs index a501333a7..aa2506caf 100644 --- a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,6 +22,131 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Link", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("varchar(500)"); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("varchar(25)"); + + b.Property("ModifiedByUserId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("OpportunityId") + .HasColumnType("uuid"); + + b.Property("ShortURL") + .IsRequired() + .HasColumnType("varchar(2048)"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("URL") + .IsRequired() + .HasColumnType("varchar(2048)"); + + b.Property("UsagesLimit") + .HasColumnType("integer"); + + b.Property("UsagesTotal") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("ModifiedByUserId"); + + b.HasIndex("OpportunityId"); + + b.HasIndex("ShortURL") + .IsUnique(); + + b.HasIndex("StatusId"); + + b.HasIndex("URL") + .IsUnique(); + + b.HasIndex("EntityType", "Action", "StatusId", "OpportunityId", "DateEnd", "DateCreated"); + + b.ToTable("Link", "ActionLink"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.LinkUsageLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("LinkId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DateCreated"); + + b.HasIndex("UserId"); + + b.HasIndex("LinkId", "UserId") + .IsUnique(); + + b.ToTable("UsageLog", "ActionLink"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Lookups.LinkStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Status", "ActionLink"); + }); + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", b => { b.Property("Id") @@ -953,9 +1078,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SSISchemaName") .HasColumnType("varchar(255)"); - b.Property("ShortURL") - .HasColumnType("varchar(2048)"); - b.Property("StatusId") .HasColumnType("uuid"); @@ -1557,6 +1679,58 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("TenantCreation", "SSI"); }); + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Link", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "ModifiedByUser") + .WithMany() + .HasForeignKey("ModifiedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Opportunity.Entities.Opportunity", "Opportunity") + .WithMany() + .HasForeignKey("OpportunityId"); + + b.HasOne("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Lookups.LinkStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("ModifiedByUser"); + + b.Navigation("Opportunity"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.ActionLink.Entities.LinkUsageLog", b => + { + b.HasOne("Yoma.Core.Infrastructure.Database.ActionLink.Entities.Link", "Link") + .WithMany() + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yoma.Core.Infrastructure.Database.Entity.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", b => { b.HasOne("Yoma.Core.Infrastructure.Database.Core.Entities.BlobObject", "Parent") diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Entities/Opportunity.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Entities/Opportunity.cs index 9d28317aa..87b18f253 100644 --- a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Entities/Opportunity.cs +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Entities/Opportunity.cs @@ -45,9 +45,6 @@ public class Opportunity : BaseEntity [Column(TypeName = "varchar(2048)")] public string? URL { get; set; } - [Column(TypeName = "varchar(2048)")] - public string? ShortURL { get; set; } - [Column(TypeName = "decimal(8,2)")] public decimal? ZltoReward { get; set; } diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Repositories/OpportunityRepository.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Repositories/OpportunityRepository.cs index 20352af12..eceac4991 100644 --- a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Repositories/OpportunityRepository.cs +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Opportunity/Repositories/OpportunityRepository.cs @@ -42,7 +42,6 @@ public OpportunityRepository(ApplicationDbContext context) : base(context) { } Summary = entity.Summary, Instructions = entity.Instructions, URL = entity.URL, - ShortURL = entity.ShortURL, ZltoReward = entity.ZltoReward, YomaReward = entity.YomaReward, ZltoRewardPool = entity.ZltoRewardPool, @@ -144,7 +143,6 @@ public OpportunityRepository(ApplicationDbContext context) : base(context) { } Summary = item.Summary, Instructions = item.Instructions, URL = item.URL, - ShortURL = item.ShortURL, ZltoReward = item.ZltoReward, YomaReward = item.YomaReward, ZltoRewardPool = item.ZltoRewardPool, @@ -193,7 +191,6 @@ public OpportunityRepository(ApplicationDbContext context) : base(context) { } Summary = item.Summary, Instructions = item.Instructions, URL = item.URL, - ShortURL = item.ShortURL, ZltoReward = item.ZltoReward, YomaReward = item.YomaReward, ZltoRewardPool = item.ZltoRewardPool, @@ -247,7 +244,6 @@ public OpportunityRepository(ApplicationDbContext context) : base(context) { } entity.Summary = item.Summary; entity.Instructions = item.Instructions; entity.URL = item.URL; - entity.ShortURL = item.ShortURL; entity.ZltoReward = item.ZltoReward; entity.YomaReward = item.YomaReward; entity.ZltoRewardPool = item.ZltoRewardPool; @@ -296,7 +292,6 @@ public OpportunityRepository(ApplicationDbContext context) : base(context) { } entity.Summary = item.Summary; entity.Instructions = item.Instructions; entity.URL = item.URL; - entity.ShortURL = item.ShortURL; entity.ZltoReward = item.ZltoReward; entity.YomaReward = item.YomaReward; entity.ZltoRewardPool = item.ZltoRewardPool; diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Startup.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Startup.cs index c65d4a2be..226dad705 100644 --- a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Startup.cs +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Startup.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Yoma.Core.Domain.ActionLink.Models; +using Yoma.Core.Domain.ActionLink.Models.Lookups; using Yoma.Core.Domain.Core.Interfaces; using Yoma.Core.Domain.Core.Models; using Yoma.Core.Domain.Entity.Models; @@ -10,6 +12,8 @@ using Yoma.Core.Domain.Opportunity.Models; using Yoma.Core.Domain.SSI.Models; using Yoma.Core.Domain.SSI.Models.Lookups; +using Yoma.Core.Infrastructure.Database.ActionLink.Repositories; +using Yoma.Core.Infrastructure.Database.ActionLink.Repositories.Lookups; using Yoma.Core.Infrastructure.Database.Context; using Yoma.Core.Infrastructure.Database.Core.Repositories; using Yoma.Core.Infrastructure.Database.Core.Services; @@ -68,6 +72,14 @@ public static void ConfigureServices_InfrastructureDatabase(this IServiceCollect // HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); // repositories + #region ActionLink + #region Lookups + services.AddScoped, LinkStatusRepository>(); + #endregion Lookups + services.AddScoped, LinkRepository>(); + services.AddScoped, LinkUsageLogRepository>(); + #endregion ActionLink + #region Core services.AddScoped(); services.AddScoped, BlobObjectRepository>();