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 17289b9e9..d269e7075 100644 --- a/src/api/src/application/Yoma.Core.Api/Controllers/OpportunityController.cs +++ b/src/api/src/application/Yoma.Core.Api/Controllers/OpportunityController.cs @@ -66,6 +66,22 @@ 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)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + [AllowAnonymous] + public async Task GetSharingDetails([FromRoute] Guid id, [FromQuery] bool? includeQRCode) + { + _logger.LogInformation("Handling request {requestName}", nameof(GetSharingDetails)); + + var result = await _opportunityService.GetSharingDetails(id, true, includeQRCode); + + _logger.LogInformation("Request {requestName} handled", nameof(GetSharingDetails)); + + return StatusCode((int)HttpStatusCode.OK, result); + } + [SwaggerOperation(Summary = "Search for published opportunities based on the supplied filter (Anonymous)", Description = "Results are always associated with an active organization. By default published opportunities are included, thus active opportunities, irrespective of whether they started (includes both NotStarted and Active states). This default behavior is overridable")] [HttpPost("search")] @@ -204,13 +220,30 @@ public IActionResult GetInfoById([FromRoute] Guid id) { _logger.LogInformation("Handling request {requestName}", nameof(GetInfoById)); - //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. + //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 = _opportunityInfoService.GetById(id, false); _logger.LogInformation("Request {requestName} handled", nameof(GetInfoById)); return StatusCode((int)HttpStatusCode.OK, result); } + + [SwaggerOperation(Summary = "Get sharing details for the specified opportunity by id (User, Admin or Organization Admin role required)")] + [HttpGet("{id}/auth/sharing")] + [ProducesResponseType(typeof(OpportunityInfo), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + [Authorize(Roles = $"{Constants.Role_User}, {Constants.Role_Admin}, {Constants.Role_OrganizationAdmin}")] + public async Task 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 #region Administrative Actions diff --git a/src/api/src/application/Yoma.Core.Api/appsettings.json b/src/api/src/application/Yoma.Core.Api/appsettings.json index ea6331531..f28841cda 100644 --- a/src/api/src/application/Yoma.Core.Api/appsettings.json +++ b/src/api/src/application/Yoma.Core.Api/appsettings.json @@ -50,6 +50,7 @@ "SentryEnabledEnvironments": "Development, Staging, Production", "HttpsRedirectionEnabledEnvironments": null, "LaborMarketProviderAsSourceEnabledEnvironments": "Production", + "ShortLinkProviderAsSourceEnabledEnvironments": "Development, Staging, Production", "DatabaseRetryPolicy": { "MaxRetryCount": 6, "MaxRetryDelayInSeconds": 30 @@ -234,10 +235,9 @@ }, "Bitly": { + "BaseUrl": "https://api-ssl.bitly.com", "GroupId": "{groupid}", "ApiKey": "{apikey}", - "ShortLinkType": "CustomDomainAndBackHalf", - "DomainCustom": "go.yoma.world", - "Tags": [ "Yoma", "Opportunity", "Shortlink" ] + "CustomDomain": "go.yoma.world" } } diff --git a/src/api/src/domain/Yoma.Core.Domain/Core/Extensions/FlurlExtensions.cs b/src/api/src/domain/Yoma.Core.Domain/Core/Extensions/FlurlExtensions.cs index 8599db011..31150c25b 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Core/Extensions/FlurlExtensions.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Core/Extensions/FlurlExtensions.cs @@ -40,7 +40,7 @@ public static IFlurlRequest WithAuthHeaders(this Url url, Dictionary /// /// - public static async Task EnsureSuccessStatusCodeAsync(this Task response) + public static async Task EnsureSuccessStatusCodeAsync(this Task response, List? AdditionalSuccessStatusCodes = null) { IFlurlResponse resp; @@ -66,7 +66,9 @@ public static async Task EnsureSuccessStatusCodeAsync(this Task< var statusCode = (HttpStatusCode)resp.StatusCode; var message = await resp.ResponseMessage.Content.ReadAsStringAsync(); - if (statusCode == HttpStatusCode.OK) return resp; + var successStatusCodes = AdditionalSuccessStatusCodes ?? []; + if (!successStatusCodes.Contains(HttpStatusCode.OK)) successStatusCodes.Add(HttpStatusCode.OK); + if (successStatusCodes.Contains(statusCode)) return resp; throw new HttpClientException(statusCode, message); } diff --git a/src/api/src/domain/Yoma.Core.Domain/Core/Helpers/QRCodeHelper.cs b/src/api/src/domain/Yoma.Core.Domain/Core/Helpers/QRCodeHelper.cs new file mode 100644 index 000000000..199b66ff7 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/Core/Helpers/QRCodeHelper.cs @@ -0,0 +1,19 @@ +using QRCoder; + +namespace Yoma.Core.Domain.Core.Helpers +{ + public static class QRCodeHelper + { + public static string GenerateQRCodeBase64(string url, QRCodeGenerator.ECCLevel ecc = QRCodeGenerator.ECCLevel.Q, int pixelsPerModule = 20) + { + using var qrGenerator = new QRCodeGenerator(); + var qrCodeData = qrGenerator.CreateQrCode(url, ecc); + + using var qrCode = new PngByteQRCode(qrCodeData); + var qrCodeAsBytes = qrCode.GetGraphic(pixelsPerModule); + + var base64String = Convert.ToBase64String(qrCodeAsBytes); + return $"data:image/png;base64,{base64String}"; + } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/Core/Models/AppSettings.cs b/src/api/src/domain/Yoma.Core.Domain/Core/Models/AppSettings.cs index cc3174df7..b7c324ff0 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Core/Models/AppSettings.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Core/Models/AppSettings.cs @@ -87,6 +87,10 @@ public CacheItemType CacheEnabledByCacheItemTypesAsEnum public Environment LaborMarketProviderAsSourceEnabledEnvironmentsAsEnum => ParseEnvironmentInput(LaborMarketProviderAsSourceEnabledEnvironments); + public string ShortLinkProviderAsSourceEnabledEnvironments { get; set; } + + public Environment ShortLinkProviderAsSourceEnabledEnvironmentsAsEnum => ParseEnvironmentInput(ShortLinkProviderAsSourceEnabledEnvironments); + public AppSettingsDatabaseRetryPolicy DatabaseRetryPolicy { get; set; } public bool? RedisSSLCertificateValidationBypass { get; set; } diff --git a/src/api/src/domain/Yoma.Core.Domain/Entity/Services/OrganizationBackgroundService.cs b/src/api/src/domain/Yoma.Core.Domain/Entity/Services/OrganizationBackgroundService.cs index 09431e642..35565429c 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Entity/Services/OrganizationBackgroundService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Entity/Services/OrganizationBackgroundService.cs @@ -81,7 +81,7 @@ public async Task ProcessDeclination() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); _logger.LogInformation("Processing organization declination"); @@ -179,7 +179,7 @@ public async Task ProcessDeletion() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); _logger.LogInformation("Processing organization deletion"); @@ -240,7 +240,7 @@ public async Task SeedLogoAndDocuments() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); if (!_appSettings.TestDataSeedingEnvironmentsAsEnum.HasFlag(_environmentProvider.Environment)) { diff --git a/src/api/src/domain/Yoma.Core.Domain/Entity/Services/UserBackgroundService.cs b/src/api/src/domain/Yoma.Core.Domain/Entity/Services/UserBackgroundService.cs index e27991499..711642375 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Entity/Services/UserBackgroundService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Entity/Services/UserBackgroundService.cs @@ -59,7 +59,7 @@ public async Task SeedPhotos() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); if (!_appSettings.TestDataSeedingEnvironmentsAsEnum.HasFlag(_environmentProvider.Environment)) { diff --git a/src/api/src/domain/Yoma.Core.Domain/Lookups/Services/SkillService.cs b/src/api/src/domain/Yoma.Core.Domain/Lookups/Services/SkillService.cs index abcb43ec2..a29f26d1a 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Lookups/Services/SkillService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Lookups/Services/SkillService.cs @@ -122,7 +122,7 @@ public async Task SeedSkills(bool onStartupInitialSeeding) using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); try { 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 5ba4baa8e..4ce5af755 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,6 +47,20 @@ public static int TimeIntervalToDays(this Models.Opportunity opportunity) return days; } + public static (bool result, string? message) PublishedOrExpired(this Models.Opportunity opportunity) + { + ArgumentNullException.ThrowIfNull(opportunity, nameof(opportunity)); + + if (opportunity.OrganizationStatus != Entity.OrganizationStatus.Active) + return (false, $"Opportunity with id '{opportunity.Id}' belongs to an inactive organization"); + + var statuses = new List() { Status.Active, Status.Expired }; //ignore DateStart, includes both not started and started + if (!statuses.Contains(opportunity.Status)) + return (false, $"Opportunity with id '{opportunity.Id}' has an invalid status. Expected status(es): '{string.Join(", ", statuses)}'"); + + return (true, null); + } + public static void SetPublished(this Models.Opportunity opportunity) { ArgumentNullException.ThrowIfNull(opportunity, nameof(opportunity)); @@ -65,6 +79,11 @@ public static OpportunitySearchCriteriaItem ToOpportunitySearchCriteria(this Mod }; } + public static string YomaInfoURL(this Models.Opportunity value, string appBaseURL) + { + return appBaseURL.AppendPathSegment("opportunities").AppendPathSegment(value.Id).ToString(); + } + public static OpportunityInfo ToOpportunityInfo(this Models.Opportunity value, string appBaseURL) { ArgumentNullException.ThrowIfNull(value, nameof(value)); @@ -84,6 +103,7 @@ 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, @@ -101,7 +121,7 @@ public static OpportunityInfo ToOpportunityInfo(this Models.Opportunity value, s DateStart = value.DateStart, DateEnd = value.DateEnd, Published = value.Published, - YomaInfoURL = appBaseURL.AppendPathSegment("opportunities").AppendPathSegment(value.Id).ToString(), + YomaInfoURL = value.YomaInfoURL(appBaseURL), Categories = value.Categories, Countries = value.Countries, Languages = value.Languages, 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 f1295fe14..111dc7f0f 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 @@ -11,6 +11,8 @@ 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); 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 b90f552c6..7054fdc9b 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,6 +41,8 @@ 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 a9551a82e..ed1a83d16 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,6 +30,8 @@ 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/OpportunitySharingResult.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunitySharingResult.cs new file mode 100644 index 000000000..29eb8b1aa --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Models/OpportunitySharingResult.cs @@ -0,0 +1,9 @@ +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/OpportunityBackgroundService.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityBackgroundService.cs index b7194e75f..9a150f8f5 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityBackgroundService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityBackgroundService.cs @@ -72,7 +72,7 @@ public async Task ProcessExpiration() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); _logger.LogInformation("Processing opportunity expiration"); @@ -134,7 +134,7 @@ public async Task ProcessExpirationNotifications() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); _logger.LogInformation("Processing opportunity expiration notifications"); @@ -184,7 +184,7 @@ public async Task ProcessDeletion() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); _logger.LogInformation("Processing opportunity deletion"); diff --git a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityInfoService.cs b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityInfoService.cs index 9cd487297..267b41e8d 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityInfoService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Opportunity/Services/OpportunityInfoService.cs @@ -45,14 +45,13 @@ public OpportunityInfo GetPublishedOrExpiredById(Guid id) { var opportunity = _opportunityService.GetById(id, true, true, false); - //inactive organization - if (opportunity.OrganizationStatus != Entity.OrganizationStatus.Active) - throw new EntityNotFoundException($"Opportunity with id '{id}' belongs to an inactive organization"); - - //status criteria not met - var statuses = new List() { Status.Active, Status.Expired }; //ignore DateStart, includes both not started and started - if (!statuses.Contains(opportunity.Status)) - throw new EntityNotFoundException($"Opportunity with id '{id}' has an invalid status. Expected status(es): '{string.Join(", ", statuses)}'"); + var (publishedOrExpiredResult, message) = opportunity.PublishedOrExpired(); + + if (!publishedOrExpiredResult) + { + ArgumentException.ThrowIfNullOrEmpty(message); + throw new EntityNotFoundException(message); + } var result = opportunity.ToOpportunityInfo(_appSettings.AppBaseURL); SetParticipantCounts(result); 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 ff4d9e4fa..472eefaf4 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 @@ -25,6 +25,7 @@ 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 { @@ -51,6 +52,7 @@ public class OpportunityService : IOpportunityService private readonly IEmailURLFactory _emailURLFactory; private readonly IEmailProviderClient _emailProviderClient; private readonly IIdentityProviderClient _identityProviderClient; + private readonly IShortLinkProviderClient _shortLinkProviderClient; private readonly OpportunityRequestValidatorCreate _opportunityRequestValidatorCreate; private readonly OpportunityRequestValidatorUpdate _opportunityRequestValidatorUpdate; @@ -94,6 +96,7 @@ public OpportunityService(ILogger logger, IEmailURLFactory emailURLFactory, IEmailProviderClientFactory emailProviderClientFactory, IIdentityProviderClientFactory identityProviderClientFactory, + IShortLinkProviderClientFactory shortLinkProviderClientFactory, OpportunityRequestValidatorCreate opportunityRequestValidatorCreate, OpportunityRequestValidatorUpdate opportunityRequestValidatorUpdate, OpportunitySearchFilterValidator opportunitySearchFilterValidator, @@ -126,6 +129,7 @@ public OpportunityService(ILogger logger, _emailURLFactory = emailURLFactory; _emailProviderClient = emailProviderClientFactory.CreateClient(); _identityProviderClient = identityProviderClientFactory.CreateClient(); + _shortLinkProviderClient = shortLinkProviderClientFactory.CreateClient(); _opportunityRequestValidatorCreate = opportunityRequestValidatorCreate; _opportunityRequestValidatorUpdate = opportunityRequestValidatorUpdate; @@ -194,6 +198,46 @@ 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)) diff --git a/src/api/src/domain/Yoma.Core.Domain/Reward/Services/RewardBackgroundService.cs b/src/api/src/domain/Yoma.Core.Domain/Reward/Services/RewardBackgroundService.cs index 4c20cd7e4..16f02d97d 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Reward/Services/RewardBackgroundService.cs +++ b/src/api/src/domain/Yoma.Core.Domain/Reward/Services/RewardBackgroundService.cs @@ -72,7 +72,7 @@ public async Task ProcessWalletCreation() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); _logger.LogInformation("Processing Reward wallet creation"); @@ -154,7 +154,7 @@ public async Task ProcessRewardTransactions() using (JobStorage.Current.GetConnection().AcquireDistributedLock(lockIdentifier, lockDuration)) { _logger.LogInformation("Lock '{lockIdentifier}' acquired by {hostName} at {dateStamp}. Lock duration set to {lockDurationInMinutes} minutes", - lockIdentifier, System.Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); + lockIdentifier, Environment.MachineName, DateTimeOffset.UtcNow, lockDuration.TotalMinutes); _logger.LogInformation("Processing reward transactions"); diff --git a/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Enumerations.cs b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Enumerations.cs new file mode 100644 index 000000000..f81007fe5 --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Enumerations.cs @@ -0,0 +1,13 @@ +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/Interfaces/IShortLinkProviderClient.cs b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Interfaces/IShortLinkProviderClient.cs index 773d8f04d..99d1322cc 100644 --- a/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Interfaces/IShortLinkProviderClient.cs +++ b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Interfaces/IShortLinkProviderClient.cs @@ -1,8 +1,10 @@ +using Yoma.Core.Domain.ShortLinkProvider.Models; + namespace Yoma.Core.Domain.ShortLinkProvider.Interfaces { public interface IShortLinkProviderClient { - + Task CreateShortLink(ShortLinkRequest request); } } 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 new file mode 100644 index 000000000..f4ddd2d1c --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkRequest.cs @@ -0,0 +1,15 @@ +namespace Yoma.Core.Domain.ShortLinkProvider.Models +{ + public class ShortLinkRequest + { + public EntityType Type { get; set; } + + public Action Action { get; set; } + + public string Title { get; set; } + + public string URL { get; set; } + + public List? ExtraTags { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkResponse.cs b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkResponse.cs new file mode 100644 index 000000000..7d30cda3f --- /dev/null +++ b/src/api/src/domain/Yoma.Core.Domain/ShortLinkProvider/Models/ShortLinkResponse.cs @@ -0,0 +1,9 @@ +namespace Yoma.Core.Domain.ShortLinkProvider.Models +{ + public class ShortLinkResponse + { + public string Id { get; set; } + + public string Link { get; set; } + } +} diff --git a/src/api/src/domain/Yoma.Core.Domain/Yoma.Core.Domain.csproj b/src/api/src/domain/Yoma.Core.Domain/Yoma.Core.Domain.csproj index e0c046b77..ff15a8809 100644 --- a/src/api/src/domain/Yoma.Core.Domain/Yoma.Core.Domain.csproj +++ b/src/api/src/domain/Yoma.Core.Domain/Yoma.Core.Domain.csproj @@ -35,8 +35,9 @@ - - + + + diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClient.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClient.cs index ece55ac5d..fb2d74094 100644 --- a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClient.cs +++ b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClient.cs @@ -1,6 +1,12 @@ +using Flurl; +using Flurl.Http; using Microsoft.Extensions.Logging; +using System.Net; +using Yoma.Core.Domain.Core.Extensions; using Yoma.Core.Domain.Core.Interfaces; +using Yoma.Core.Domain.Core.Models; using Yoma.Core.Domain.ShortLinkProvider.Interfaces; +using Yoma.Core.Domain.ShortLinkProvider.Models; using Yoma.Core.Infrastructure.Bitly.Models; namespace Yoma.Core.Infrastructure.Bitly.Client @@ -10,54 +16,108 @@ public class BitlyClient : IShortLinkProviderClient #region Class Variables private readonly ILogger _logger; private readonly IEnvironmentProvider _environmentProvider; + private readonly AppSettings _appSettings; private readonly BitlyOptions _options; + + private const string Header_Authorization = "Authorization"; + private const string Header_Authorization_Value_Prefix = "Bearer"; #endregion #region Constructor public BitlyClient(ILogger logger, - IEnvironmentProvider environmentProvider, - BitlyOptions options) + IEnvironmentProvider environmentProvider, + AppSettings appSettings, + BitlyOptions options) { _logger = logger; _environmentProvider = environmentProvider; + _appSettings = appSettings; _options = options; } #endregion #region Public Members - public string CreateShortLink(string title, string url, string? titleUrl) + public async Task CreateShortLink(ShortLinkRequest request) { - ArgumentException.ThrowIfNullOrEmpty(title, nameof(title)); + ArgumentNullException.ThrowIfNull(request, nameof(request)); + + if (string.IsNullOrWhiteSpace(request.Title)) + throw new ArgumentNullException(nameof(request), "Title is required"); + request.Title = request.Title.Trim().RemoveSpecialCharacters(); + + if (string.IsNullOrWhiteSpace(request.URL)) + throw new ArgumentNullException(nameof(request), "URL is required"); + request.URL = request.URL.Trim(); - if (!Uri.IsWellFormedUriString(url, UriKind.Absolute)) - throw new ArgumentException("Invalid URL", nameof(url)); + if (!Uri.IsWellFormedUriString(request.URL, UriKind.Absolute)) + throw new ArgumentException("Invalid URL", nameof(request)); - var request = new BitLinksRequest + if (!_appSettings.ShortLinkProviderAsSourceEnabledEnvironmentsAsEnum.HasFlag(_environmentProvider.Environment)) { - LongURL = url, + _logger.LogInformation("Used dummy short link for environment '{environment}'", _environmentProvider.Environment); + return GenerateDummyShortLink(); + } + + var tags = (request.ExtraTags ?? []).Where(tag => !string.IsNullOrWhiteSpace(tag)).Select(tag => tag.Trim()).ToList(); + tags.Add(_environmentProvider.Environment.ToString()); + tags.Add("Yoma"); + tags.Add(request.Type.ToString()); + tags.Add(request.Action.ToString()); + tags = tags.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList(); + + var requestCreate = new BitLinkRequestCreate + { + LongURL = request.URL, + Domain = _options.CustomDomain, GroupId = _options.GroupId, - Title = title, - Tags = _options.Tags + Title = request.Title, + Tags = tags, }; - switch (_options.ShortLinkType) + //idempotent + var response = await _options.BaseUrl + .AppendPathSegment($"v4/bitlinks") + .WithAuthHeader(GetAuthHeader()) + .PostJsonAsync(requestCreate) + .EnsureSuccessStatusCodeAsync([HttpStatusCode.Created]) + .ReceiveJson(); + + return new ShortLinkResponse { - case ShortLinkType.Generic: - break; + Id = response.Id, + Link = response.Link + }; + } + #endregion - case ShortLinkType.CustomDomain: - request.Domain = _options.DomainCustom; - break; + #region Private Members + private KeyValuePair GetAuthHeader() + { + return new KeyValuePair(Header_Authorization, $"{Header_Authorization_Value_Prefix} {_options.ApiKey}"); + } - case ShortLinkType.CustomBackHalf: - break; + private ShortLinkResponse GenerateDummyShortLink() + { + var path = GenerateRandomPath(6); + var urlRelative = $"{_options.CustomDomain}/{path}"; - case ShortLinkType.CustomDomainAndBackHalf: - request.Domain = _options.DomainCustom; - break; - } + return new ShortLinkResponse + { + Id = urlRelative, + Link = $"https://{urlRelative}" + }; + } + + private static string GenerateRandomPath(int length) + { + var random = new Random(); + + var chars = Enumerable.Range('0', '9' - '0' + 1).Select(i => (char)i) + .Concat(Enumerable.Range('A', 'Z' - 'A' + 1).Select(i => (char)i)) + .Concat(Enumerable.Range('a', 'z' - 'a' + 1).Select(i => (char)i)) + .ToArray(); - return string.Empty; + return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); } #endregion } diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClientFactory.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClientFactory.cs index f76d56784..5a2df554a 100644 --- a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClientFactory.cs +++ b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Client/BitlyClientFactory.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Yoma.Core.Domain.Core.Interfaces; +using Yoma.Core.Domain.Core.Models; using Yoma.Core.Domain.ShortLinkProvider.Interfaces; using Yoma.Core.Infrastructure.Bitly.Models; @@ -11,16 +12,19 @@ public class BitlyClientFactory : IShortLinkProviderClientFactory #region Class Variables private readonly ILogger _logger; private readonly IEnvironmentProvider _environmentProvider; + private readonly AppSettings _appSettings; private readonly BitlyOptions _options; #endregion #region Constructor public BitlyClientFactory(ILogger logger, - IEnvironmentProvider environmentProvider, - IOptions options) + IEnvironmentProvider environmentProvider, + IOptions appSettings, + IOptions options) { _logger = logger; _environmentProvider = environmentProvider; + _appSettings = appSettings.Value; _options = options.Value; } #endregion @@ -28,7 +32,7 @@ public BitlyClientFactory(ILogger logger, #region Public Members public IShortLinkProviderClient CreateClient() { - return new BitlyClient(_logger, _environmentProvider, _options); + return new BitlyClient(_logger, _environmentProvider, _appSettings, _options); } #endregion } diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Enumerations.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Enumerations.cs deleted file mode 100644 index 76d826d1c..000000000 --- a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Enumerations.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Yoma.Core.Infrastructure.Bitly -{ - public enum ShortLinkType - { - /// - /// Bitly's generic domain, no customized back-half - /// - Generic, - - /// - /// A custom domain, but no customized back-half - /// - CustomDomain, - - /// - /// Bitly's generic domain, with a customized back-half - /// - CustomBackHalf, - - /// - /// A custom domain and a customized back-half - /// - CustomDomainAndBackHalf, - } -} diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinksRequest.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkRequestCreate.cs similarity index 63% rename from src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinksRequest.cs rename to src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkRequestCreate.cs index 79c9dd943..fa76db490 100644 --- a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinksRequest.cs +++ b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkRequestCreate.cs @@ -2,7 +2,7 @@ namespace Yoma.Core.Infrastructure.Bitly.Models { - public class BitLinksRequest + public class BitLinkRequestCreate { [JsonProperty("long_url")] public string LongURL { get; set; } @@ -11,12 +11,17 @@ public class BitLinksRequest public string? Domain { get; set; } [JsonProperty("group_guid")] - public string GroupId { get; set; } + public string? GroupId { get; set; } [JsonProperty("title")] public string Title { get; set; } [JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)] - public string[]? Tags { get; set; } + public List? Tags { get; set; } + + public static implicit operator BitLinkRequestCreate(BitLinkResponse v) + { + throw new NotImplementedException(); + } } } diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkRequestPAtchCustomBack.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkRequestPAtchCustomBack.cs new file mode 100644 index 000000000..209af1614 --- /dev/null +++ b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkRequestPAtchCustomBack.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Yoma.Core.Infrastructure.Bitly.Models +{ + public class BitLinkRequestPAtchCustomBack + { + [JsonProperty("bitlink_id")] + public string Id { get; set; } + + [JsonProperty("custom_bitlink")] + public string CustomLink { get; set; } + } +} diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinksResponse.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkResponse.cs similarity index 80% rename from src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinksResponse.cs rename to src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkResponse.cs index 3b0c3deac..61f8ad84e 100644 --- a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinksResponse.cs +++ b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkResponse.cs @@ -2,7 +2,7 @@ namespace Yoma.Core.Infrastructure.Bitly.Models { - public class BitLinksResponse + public class BitLinkResponse { [JsonProperty("link")] public string Link { get; set; } @@ -33,5 +33,11 @@ public class BitLinksResponse [JsonProperty("tags")] public string[] Tags { get; set; } + + [JsonProperty("launchpad_ids")] + public string[] LaunchpadIds { get; set; } + + [JsonProperty("campaign_ids")] + public string[] CampaignIds { get; set; } } } diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkResponseCustomBack.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkResponseCustomBack.cs new file mode 100644 index 000000000..bc25a76a1 --- /dev/null +++ b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitLinkResponseCustomBack.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Yoma.Core.Infrastructure.Bitly.Models +{ + public class BitLinkResponseCustomBack + { + [JsonProperty("custom_bitlink")] + public string CustomBitLink { get; set; } + + [JsonProperty("bitlink")] + public BitLinkResponse BitLink { get; set; } + } +} diff --git a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitlyOptions.cs b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitlyOptions.cs index f44767e59..8b9cee66f 100644 --- a/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitlyOptions.cs +++ b/src/api/src/infrastructure/Yoam.Core.Infrastructure.Bitly/Models/BitlyOptions.cs @@ -4,14 +4,12 @@ public class BitlyOptions { public const string Section = "Bitly"; + public string BaseUrl { get; set; } + public string GroupId { get; set; } public string ApiKey { get; set; } - public ShortLinkType ShortLinkType { get; set; } - - public string DomainCustom { get; set; } - - public string[] Tags { get; set; } + public string CustomDomain { get; set; } } } diff --git a/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240418061727_ApplicationDb_OpportunitySharing.Designer.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240418061727_ApplicationDb_OpportunitySharing.Designer.cs new file mode 100644 index 000000000..9b063403d --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240418061727_ApplicationDb_OpportunitySharing.Designer.cs @@ -0,0 +1,2144 @@ +// +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("20240418061727_ApplicationDb_OpportunitySharing")] + partial class ApplicationDb_OpportunitySharing + { + /// + 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.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("ShortURL") + .HasColumnType("varchar(2048)"); + + 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.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/20240418061727_ApplicationDb_OpportunitySharing.cs b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240418061727_ApplicationDb_OpportunitySharing.cs new file mode 100644 index 000000000..dc6ad9bec --- /dev/null +++ b/src/api/src/infrastructure/Yoma.Core.Infrastructure.Database/Migrations/20240418061727_ApplicationDb_OpportunitySharing.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Yoma.Core.Infrastructure.Database.Migrations +{ + /// + public partial class ApplicationDb_OpportunitySharing : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ShortURL", + schema: "Opportunity", + table: "Opportunity", + type: "varchar(2048)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ShortURL", + schema: "Opportunity", + table: "Opportunity"); + } + } +} 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 4c2ff3965..a501333a7 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 @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("ProductVersion", "8.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -953,6 +953,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SSISchemaName") .HasColumnType("varchar(255)"); + b.Property("ShortURL") + .HasColumnType("varchar(2048)"); + b.Property("StatusId") .HasColumnType("uuid"); 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 87b18f253..9d28317aa 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,6 +45,9 @@ 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 eceac4991..20352af12 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,6 +42,7 @@ 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, @@ -143,6 +144,7 @@ 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, @@ -191,6 +193,7 @@ 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, @@ -244,6 +247,7 @@ 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; @@ -292,6 +296,7 @@ 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;