diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..724942e1 Binary files /dev/null and b/.DS_Store differ diff --git a/SW.Bitween.Api/Controllers/GatewayController.cs b/SW.Bitween.Api/Controllers/GatewayController.cs new file mode 100644 index 00000000..7b386659 --- /dev/null +++ b/SW.Bitween.Api/Controllers/GatewayController.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using SW.Bitween.Domain; +using SW.Bitween.Domain.Gateway; +using SW.Bitween.Model; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class GatewayController( + BitweenDbContext dbContext, + RequestContext requestContext, + IInfolinkCache cache, + XchangeService xchangeService, + BitweenOptions bitweenSettings) : ControllerBase +{ + [HttpPost("{gatewayApiName}/sync")] + public Task PostSync([FromRoute] string gatewayApiName) + { + return ProcessAsync(gatewayApiName, resultSync: true); + } + + [HttpPost("{gatewayApiName}/async")] + public Task PostAsync([FromRoute] string gatewayApiName) + { + return ProcessAsync(gatewayApiName, resultSync: false); + } + + private async Task ProcessAsync([FromRoute] string gatewayApiName, bool resultSync) + { + var globalAdapterValuesSet = await cache.ListGlobalAdapterValuesSetsAsync(); + var apiGateway = await dbContext.Set() + .Include(ag => ag.Partners) + .ThenInclude(agp => agp.Partner) + .FirstOrDefaultAsync(ag => ag.UrlName == gatewayApiName); + + if (apiGateway == null) + return NotFound(); + + // Resolve partner using API key + var (authorized, partner, keyName) = await dbContext.CheckPartnerAuthorized(requestContext); + + if (!authorized) + return Unauthorized(); + + // Verify partner is part of the API Gateway + var apiGatewayPartner = apiGateway.Partners.FirstOrDefault(agp => agp.PartnerId == partner.Id); + if (apiGatewayPartner == null) + return Unauthorized(); + + var subscription = await cache.SubscriptionByIdAsync(apiGatewayPartner.SubscriptionId); + + + var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); + + var xchangeFile = new XchangeFile(json); + + var validatorProperties = subscription.ValidatorProperties.ToDictionary() + .Fill(partner, globalAdapterValuesSet); + await xchangeService.RunValidator(subscription.ValidatorId, validatorProperties, + xchangeFile); + + var xchangeReferences = new List { $"partnerkey: {keyName}" }; + var globalAdapterValuesSets = await dbContext.Set().ToArrayAsync(); + var xchangeId = await xchangeService.SubmitSubscriptionXchange(subscription.Id, xchangeFile, + xchangeReferences.ToArray(), partner, globalAdapterValuesSets); + if (!resultSync) + { + return Accepted(xchangeId); + } + + var waitResponse = 120; + // check headers for wait response value + var waitResponseHeader = Request.Headers["Wait-Period"].FirstOrDefault(); + if (int.TryParse(waitResponseHeader, out var waitResponseValue)) + { + waitResponse = waitResponseValue <= 0 ? 120 : waitResponseValue; + } + + var currentFibTerm = 1; + var previousTerm = 1; + while (currentFibTerm <= waitResponse) + { + await Task.Delay(TimeSpan.FromSeconds(currentFibTerm)); + var nextTerm = Math.Min(currentFibTerm + previousTerm, 8); + previousTerm = currentFibTerm; + currentFibTerm = nextTerm; + if (!await dbContext.Set() + .AsNoTracking() + .AnyAsync(i => i.Id == xchangeId)) continue; + + var xchangeResult = await dbContext.FindAsync(xchangeId); + + + switch (xchangeResult!.Success) + { + case true when xchangeResult.ResponseSize == 0: + { + return Ok(xchangeId); + } + case true when xchangeResult.ResponseSize != 0: + { + var response = await xchangeService.GetFile(xchangeId, XchangeFileType.Response); + return new ContentResult + { + StatusCode = xchangeResult.ResponseBad ? 400 : 200, + Content = response, + ContentType = xchangeResult.ResponseContentType ?? MediaTypeNames.Application.Json, + }; + } + case false: + return BadRequest(); + } + } + + return Accepted(xchangeId); + } +} \ No newline at end of file diff --git a/SW.Bitween.Api/Data/BitweenDbContext.cs b/SW.Bitween.Api/Data/BitweenDbContext.cs index 77295fd6..bad5f545 100644 --- a/SW.Bitween.Api/Data/BitweenDbContext.cs +++ b/SW.Bitween.Api/Data/BitweenDbContext.cs @@ -1,15 +1,14 @@ using System; -using System.IO; using Microsoft.EntityFrameworkCore; using SW.EfCoreExtensions; using SW.Bitween.Domain; using SW.PrimitiveTypes; using System.Linq; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using SW.Bitween.Domain.Accounts; +using SW.Bitween.Domain.Gateway; using SW.Bitween.JsonConverters; namespace SW.Bitween @@ -92,11 +91,43 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }); + modelBuilder.Entity(ag => + { + ag.ToTable("ApiGateways"); + ag.HasKey(i => i.Id); + ag.Property(i => i.Id).ValueGeneratedOnAdd(); + ag.Property(p => p.Name).IsRequired().HasMaxLength(200); + ag.Property(p => p.UrlName).IsRequired().HasMaxLength(200); + ag.HasIndex(p => p.UrlName).IsUnique(); + ag.HasMany(p => p.Partners).WithOne(p => p.ApiGateway).HasForeignKey(p => p.ApiGatewayId) + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity(agp => + { + agp.ToTable("ApiGatewayPartners"); + agp.HasKey(p => new { p.ApiGatewayId, p.PartnerId, p.SubscriptionId }); + agp.HasOne(p => p.ApiGateway).WithMany(p => p.Partners).HasForeignKey(p => p.ApiGatewayId) + .OnDelete(DeleteBehavior.Restrict); + agp.HasOne(p => p.Partner).WithMany().HasForeignKey(p => p.PartnerId) + .OnDelete(DeleteBehavior.Restrict); + agp.HasOne(p => p.Subscription).WithMany().HasForeignKey(p => p.SubscriptionId) + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity(gav => + { + gav.ToTable("GlobalAdapterValuesSets"); + gav.HasKey(i => i.Id); + gav.Property(p => p.Id).IsUnicode(false).HasMaxLength(200); + gav.Property(p => p.Values).StoreAsJson(); + }); modelBuilder.Entity(b => { b.ToTable("Partners"); b.Metadata.SetNavigationAccessMode(PropertyAccessMode.Field); b.Property(p => p.Name).IsRequired().IsUnicode(false).HasMaxLength(200); + b.Property(p => p.AdapterProperties).StoreAsJson(); b.HasMany(p => p.Subscriptions).WithOne().IsRequired(false).HasForeignKey(p => p.PartnerId) .OnDelete(DeleteBehavior.Restrict); b.OwnsMany(p => p.ApiCredentials, apicred => @@ -305,6 +336,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.Property(p => p.AccountId); b.Property(p => p.LoginMethod).HasConversion(); }); + } async public override Task SaveChangesAsync(CancellationToken cancellationToken = default) @@ -334,4 +366,5 @@ await publish.Publish(hasWorkGroup.GetBusMessageName(), return affectedRecords; } } -} \ No newline at end of file +} + diff --git a/SW.Bitween.Api/Domain/Gateway/ApiGateway.cs b/SW.Bitween.Api/Domain/Gateway/ApiGateway.cs new file mode 100644 index 00000000..7f304f7f --- /dev/null +++ b/SW.Bitween.Api/Domain/Gateway/ApiGateway.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Domain.Gateway; + +public class ApiGateway : BaseEntity,IAudited +{ + public string Name { get; set; } + public string UrlName { get; set; } + public ICollection Partners { get; set; } + public DateTime CreatedOn { get; set; } + public string CreatedBy { get; set; } + public DateTime? ModifiedOn { get; set; } + public string ModifiedBy { get; set; } +} \ No newline at end of file diff --git a/SW.Bitween.Api/Domain/Gateway/ApiGatewayPartner.cs b/SW.Bitween.Api/Domain/Gateway/ApiGatewayPartner.cs new file mode 100644 index 00000000..f82b51da --- /dev/null +++ b/SW.Bitween.Api/Domain/Gateway/ApiGatewayPartner.cs @@ -0,0 +1,18 @@ +using System; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Domain.Gateway; + +public class ApiGatewayPartner: IAudited +{ + public ApiGateway ApiGateway { get; set; } + public int ApiGatewayId { get; set; } + public Partner Partner { get; set; } + public int PartnerId { get; set; } + public Subscription Subscription { get; set; } + public int SubscriptionId { get; set; } + public DateTime CreatedOn { get; set; } + public string CreatedBy { get; set; } + public DateTime? ModifiedOn { get; set; } + public string ModifiedBy { get; set; } +} \ No newline at end of file diff --git a/SW.Bitween.Api/Domain/GlobalAdapterValue/GlobalAdapterValuesSet.cs b/SW.Bitween.Api/Domain/GlobalAdapterValue/GlobalAdapterValuesSet.cs new file mode 100644 index 00000000..15e6aadd --- /dev/null +++ b/SW.Bitween.Api/Domain/GlobalAdapterValue/GlobalAdapterValuesSet.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Domain; + +public class GlobalAdapterValuesSet:BaseEntity +{ + public string Name { get; set; } + public Dictionary Values { get; set; } +} \ No newline at end of file diff --git a/SW.Bitween.Api/Domain/Partner/Partner.cs b/SW.Bitween.Api/Domain/Partner/Partner.cs index 838f3772..004adc22 100644 --- a/SW.Bitween.Api/Domain/Partner/Partner.cs +++ b/SW.Bitween.Api/Domain/Partner/Partner.cs @@ -9,6 +9,7 @@ namespace SW.Bitween.Domain { public class Partner : BaseEntity { + public const string TemplateVariableNamePrefix = "partner"; public const int SystemId = 1; private Partner() @@ -29,7 +30,7 @@ public Partner(string name) } public string Name { get; set; } - + public Dictionary AdapterProperties { get; set; } readonly HashSet _Subscriptions; public IReadOnlyCollection Subscriptions => _Subscriptions; @@ -41,6 +42,7 @@ public void SetApiCredentials(IEnumerable apiCredentials) { _ApiCredentials.Update(apiCredentials); } + } } diff --git a/SW.Bitween.Api/Domain/Subscription/Subscription.cs b/SW.Bitween.Api/Domain/Subscription/Subscription.cs index 3664494b..b36d4451 100644 --- a/SW.Bitween.Api/Domain/Subscription/Subscription.cs +++ b/SW.Bitween.Api/Domain/Subscription/Subscription.cs @@ -35,6 +35,13 @@ public Subscription(string name, int documentId, SubscriptionType type, int part throw new ArgumentException(); } + public Subscription(string name, int documentId, SubscriptionType type): this(WorkGroup.None,name, documentId, + type) + { + Inactive = true; + if (type != SubscriptionType.GatewayApiCall) + throw new ArgumentException(); + } private Subscription(WorkGroup workGroup, string name, int documentId, SubscriptionType type, int? partnerId = null, int? aggregationForId = null, bool temporary = false) { diff --git a/SW.Bitween.Api/Domain/Xchange/Xchange.cs b/SW.Bitween.Api/Domain/Xchange/Xchange.cs index 879708c8..9233a37a 100644 --- a/SW.Bitween.Api/Domain/Xchange/Xchange.cs +++ b/SW.Bitween.Api/Domain/Xchange/Xchange.cs @@ -11,9 +11,10 @@ private Xchange() { } - public Xchange(int documentId, IWorkGroup workGroup, XchangeFile file, string[] references = null, SubscriptionType subscriptionType = SubscriptionType.Internal, string correlationId = null) + public Xchange(int documentId, IWorkGroup workGroup, XchangeFile file, string[] references = null, + SubscriptionType subscriptionType = SubscriptionType.Internal, string correlationId = null) { - Id = Guid.NewGuid().ToString("N"); + Id = Guid.NewGuid().ToString("N"); DocumentId = documentId; References = references ?? new string[] { }; InputName = file.Filename; @@ -28,6 +29,7 @@ public Xchange(int documentId, IWorkGroup workGroup, XchangeFile file, string[] //break; SubscriptionType.Internal => new InternalXchangeCreatedEvent(), SubscriptionType.ApiCall => new ApiXchangeCreatedEvent(), + SubscriptionType.GatewayApiCall => new ApiXchangeCreatedEvent(), SubscriptionType.Receiving => new ReceivingXchangeCreatedEvent(), SubscriptionType.Aggregation => new AggregateXchangeCreatedEvent(), _ => throw new ArgumentOutOfRangeException(nameof(subscriptionType), subscriptionType, null) @@ -38,22 +40,25 @@ public Xchange(int documentId, IWorkGroup workGroup, XchangeFile file, string[] Events.Add(xchangeEvent); } - public Xchange(Subscription subscription, XchangeFile file, string[] references = null, string correlationId = null) : + public Xchange(Subscription subscription, XchangeFile file, string[] references = null, + string correlationId = null, Partner gatewayPartner = null,GlobalAdapterValuesSet[] globalAdapterValuesSets = null) : this(subscription.DocumentId, subscription.WorkGroup, file, references, subscription.Type) { SubscriptionId = subscription.Id; MapperId = subscription.MapperId; HandlerId = subscription.HandlerId; - MapperProperties = subscription.MapperProperties; - HandlerProperties = subscription.HandlerProperties; ResponseSubscriptionId = subscription.ResponseSubscriptionId; ResponseMessageTypeName = subscription.ResponseMessageTypeName; + MapperProperties = subscription.MapperProperties.ToDictionary().Fill(gatewayPartner,globalAdapterValuesSets); + HandlerProperties = subscription.HandlerProperties.ToDictionary() + .Fill(gatewayPartner, globalAdapterValuesSets); CorrelationId = correlationId; + } //retry xchange - public Xchange(Xchange xchange, XchangeFile file,IWorkGroup workGroup) : - this(xchange.DocumentId,workGroup, file, xchange.References) + public Xchange(Xchange xchange, XchangeFile file, IWorkGroup workGroup) : + this(xchange.DocumentId, workGroup, file, xchange.References) { SubscriptionId = xchange.SubscriptionId; MapperId = xchange.MapperId; @@ -64,9 +69,10 @@ public Xchange(Xchange xchange, XchangeFile file,IWorkGroup workGroup) : RetryFor = xchange.Id; CorrelationId = xchange.CorrelationId; } + //retry with reset subscription properties - public Xchange(Subscription subscription, Xchange xchange, XchangeFile file) : - this(xchange.DocumentId,subscription.WorkGroup, file, xchange.References) + public Xchange(Subscription subscription, Xchange xchange, XchangeFile file) : + this(xchange.DocumentId, subscription.WorkGroup, file, xchange.References) { SubscriptionId = xchange.SubscriptionId; MapperId = subscription.MapperId; @@ -95,6 +101,5 @@ public Xchange(Subscription subscription, Xchange xchange, XchangeFile file) : public string RetryFor { get; private set; } public string CorrelationId { get; set; } - } -} +} \ No newline at end of file diff --git a/SW.Bitween.Api/Extensions/InfolinkDbContextExtensions.cs b/SW.Bitween.Api/Extensions/InfolinkDbContextExtensions.cs index ce90241c..6c8530fb 100644 --- a/SW.Bitween.Api/Extensions/InfolinkDbContextExtensions.cs +++ b/SW.Bitween.Api/Extensions/InfolinkDbContextExtensions.cs @@ -11,11 +11,21 @@ static class BitweenDbContextExtensions { public static async Task<(Partner Partner, string KeyName)> AuthorizePartner(this BitweenDbContext dbContext, RequestContext requestContext) + { + var (partnerAuthorized, partner, keyName) = await dbContext.CheckPartnerAuthorized(requestContext); + return !partnerAuthorized + ? throw new SWUnauthorizedException("Invalid or missing partner key") + : (partner, keyName); + } + + public static async Task<(bool Authorized, Partner Partner, string KeyName)> CheckPartnerAuthorized( + this BitweenDbContext dbContext, + RequestContext requestContext) { var partnerKey = requestContext.Values.Where(item => item.Name.ToLower() == "partnerkey") .Select(item => item.Value).FirstOrDefault(); if (partnerKey == null) - throw new SWUnauthorizedException(); + return (false, null, null); var partnerQuery = from partner in dbContext.Set() where partner.ApiCredentials.Any(cred => cred.Key == partnerKey) @@ -23,13 +33,12 @@ where partner.ApiCredentials.Any(cred => cred.Key == partnerKey) var par = await partnerQuery.AsNoTracking().SingleOrDefaultAsync(); if (par == null) - throw new SWUnauthorizedException(); + return (false, null, null); - return (par, par.ApiCredentials.First(c => c.Key == partnerKey).Name); + return (true, par, par.ApiCredentials.Single(c => c.Key == partnerKey).Name); } public static IQueryable Subscriptions(this BitweenDbContext dbContext) => dbContext.Set().Include(s => s.WorkGroup); } - } \ No newline at end of file diff --git a/SW.Bitween.Api/Helpers/StartupValuesFiller.cs b/SW.Bitween.Api/Helpers/StartupValuesFiller.cs new file mode 100644 index 00000000..e94cf91e --- /dev/null +++ b/SW.Bitween.Api/Helpers/StartupValuesFiller.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SW.Bitween.Domain; + +namespace SW.Bitween; + +public static class StartupValuesFiller +{ + + public static Dictionary Fill(this IDictionary inputTemplated, + Partner partner, GlobalAdapterValuesSet[] globals) + { + // First fill globals templates + var afterGlobals = inputTemplated.Fill(globals ?? []); + + // Then fill partner templates using AdapterProperties + var result = afterGlobals.Fill(partner.AdapterProperties ?? new Dictionary(), Partner.TemplateVariableNamePrefix); + + return result; + } + //{{partner.XY}} => input["XY"] + private static Dictionary Fill(this IDictionary inputTemplated, + Dictionary input, string variableNamePrefix) + { + var prefix = $"{{{{{variableNamePrefix}."; // {{partner. + + return FillTemplates(inputTemplated, prefix, (content) => + { + // Simple case: extract variable name and look up in input dictionary + // Look up in input dictionary (case-insensitive) + return input.FirstOrDefault(i => + i.Key.Equals(content, StringComparison.OrdinalIgnoreCase)).Value; + }); + } + + private static Dictionary Fill(this IDictionary inputTemplated, + GlobalAdapterValuesSet[] globals) + { + var prefix = "{{globals."; // {{globals. + + return FillTemplates(inputTemplated, prefix, (content) => + { + // Complex case: split into global ID and key name + var parts = content.Split('.', 2); + if (parts.Length != 2) + { + return null; // Keep original if format is invalid + } + + var globalId = parts[0]; + var keyName = parts[1]; + + // Find the matching global adapter values set + var globalSet = globals.FirstOrDefault(g => + g.Id.Equals(globalId, StringComparison.OrdinalIgnoreCase)); + + if (globalSet == null) + { + return null; // Keep original if global set not found + } + + // Look up the key in the Values dictionary (case-insensitive) + return globalSet.Values.FirstOrDefault(v => + v.Key.Equals(keyName, StringComparison.OrdinalIgnoreCase)).Value; + }); + } + + private static Dictionary FillTemplates( + IDictionary inputTemplated, + string prefix, + Func resolver) + { + var result = new Dictionary(); + + foreach (var kvp in inputTemplated) + { + var value = kvp.Value; + + if (value != null && value.Contains(prefix, StringComparison.OrdinalIgnoreCase)) + { + var sb = new System.Text.StringBuilder(value); + var searchFrom = 0; + + while (true) + { + var current = sb.ToString(); + var start = current.IndexOf(prefix, searchFrom, StringComparison.OrdinalIgnoreCase); + if (start == -1) break; + + var end = current.IndexOf("}}", start + prefix.Length, StringComparison.Ordinal); + if (end == -1) break; + + var content = current.Substring(start + prefix.Length, end - start - prefix.Length); + var resolvedValue = resolver(content); + + if (resolvedValue != null) + { + var fullToken = current.Substring(start, end - start + 2); + sb.Replace(fullToken, resolvedValue, start, fullToken.Length); + searchFrom = start + resolvedValue.Length; + } + else + { + // Skip past this token to avoid infinite loop + searchFrom = end + 2; + } + } + + result[kvp.Key] = sb.ToString(); + } + else + { + result[kvp.Key] = value; + } + } + + return result; + } +} \ No newline at end of file diff --git a/SW.Bitween.Api/Interfaces/IInfolinkCache.cs b/SW.Bitween.Api/Interfaces/IInfolinkCache.cs index 9f941e79..36fff4fe 100644 --- a/SW.Bitween.Api/Interfaces/IInfolinkCache.cs +++ b/SW.Bitween.Api/Interfaces/IInfolinkCache.cs @@ -19,4 +19,7 @@ public interface IInfolinkCache Task ListWorkGroupsAsync(); Task WorkGroupByIdAsync(int workGroupId); Task WorkGroupBySubscriptionIdAsync(int subscriptionId); + + Task GlobalAdapterValuesSetById (string globalAdapterValuesSetId); + Task ListGlobalAdapterValuesSetsAsync(); } \ No newline at end of file diff --git a/SW.Bitween.Api/Resources/Adapters/GetProperties.cs b/SW.Bitween.Api/Resources/Adapters/GetProperties.cs index 4974470e..67bb6312 100644 --- a/SW.Bitween.Api/Resources/Adapters/GetProperties.cs +++ b/SW.Bitween.Api/Resources/Adapters/GetProperties.cs @@ -12,15 +12,26 @@ namespace SW.Bitween.Resources.Adapters public class GetProperties : IGetHandler { private readonly IServerlessService serverless; + private readonly NativeAdapterDiscoveryService _nativeAdapterDiscovery; - public GetProperties(IServerlessService serverless) + public GetProperties(IServerlessService serverless, NativeAdapterDiscoveryService nativeAdapterDiscovery) { this.serverless = serverless; + _nativeAdapterDiscovery = nativeAdapterDiscovery; } async public Task Handle(string key) { - await serverless.StartAsync( Uri.UnescapeDataString(key), null); + var decodedKey = Uri.UnescapeDataString(key); + + // Check if it's a native adapter + if (decodedKey.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + return _nativeAdapterDiscovery.GetNativeAdapterProperties(decodedKey); + } + + // Handle serverless adapters + await serverless.StartAsync(decodedKey, null); var expected = await serverless.GetExpectedStartupValues(); return expected.ToList().ToDictionary(k => k.Key, v => $"{v.Key} {(v.Value.Optional ? $" ({v.Value.Default ?? "null"})" : " *")}"); } diff --git a/SW.Bitween.Api/Resources/Adapters/Search.cs b/SW.Bitween.Api/Resources/Adapters/Search.cs index 108afbe9..ee84d76a 100644 --- a/SW.Bitween.Api/Resources/Adapters/Search.cs +++ b/SW.Bitween.Api/Resources/Adapters/Search.cs @@ -10,17 +10,23 @@ public class Search : IQueryHandler { private readonly ServerlessOptions _serverlessOptions; private readonly ICloudFilesService _cloudFilesService; + private readonly NativeAdapterDiscoveryService _nativeAdapterDiscovery; - public Search(ServerlessOptions serverlessOptions, ICloudFilesService cloudFilesService) + public Search(ServerlessOptions serverlessOptions, ICloudFilesService cloudFilesService, + NativeAdapterDiscoveryService nativeAdapterDiscovery) { _serverlessOptions = serverlessOptions; _cloudFilesService = cloudFilesService; + _nativeAdapterDiscovery = nativeAdapterDiscovery; } public async Task Handle(AdapterSearchRequest request) { + // Get native adapters first + var nativeAdapters = _nativeAdapterDiscovery.GetNativeAdapters(request.Prefix).ToList(); + // Get external adapters from storage var cloudFilesList = (await _cloudFilesService.ListAsync( $"{_serverlessOptions.AdapterRemotePath}/infolink6.{request.Prefix}")) @@ -33,10 +39,13 @@ public async Task Handle(AdapterSearchRequest request) return key; }) + .Distinct() .ToList(); + // Combine native (first) and external adapters + var allAdapters = nativeAdapters.Concat(cloudFilesList); - return cloudFilesList.Distinct().ToDictionary(k => k, v => v); + return allAdapters.ToDictionary(k => k, v => v); } } } \ No newline at end of file diff --git a/SW.Bitween.Api/Resources/Adapters/SearchVersioned.cs b/SW.Bitween.Api/Resources/Adapters/SearchVersioned.cs index 3bb3d313..1f4528d4 100644 --- a/SW.Bitween.Api/Resources/Adapters/SearchVersioned.cs +++ b/SW.Bitween.Api/Resources/Adapters/SearchVersioned.cs @@ -12,11 +12,14 @@ public class SearchVersioned : IQueryHandler { private readonly ServerlessOptions _serverlessOptions; private readonly ICloudFilesService _cloudFilesService; + private readonly NativeAdapterDiscoveryService _nativeAdapterDiscovery; - public SearchVersioned(ServerlessOptions serverlessOptions, ICloudFilesService cloudFilesService) + public SearchVersioned(ServerlessOptions serverlessOptions, ICloudFilesService cloudFilesService, + NativeAdapterDiscoveryService nativeAdapterDiscovery) { _serverlessOptions = serverlessOptions; _cloudFilesService = cloudFilesService; + _nativeAdapterDiscovery = nativeAdapterDiscovery; } @@ -24,6 +27,16 @@ public async Task Handle(AdapterSearchRequest request) { var index = _serverlessOptions.AdapterRemotePath.Length + 1; + // Get native adapters first (they don't have versions) + var nativeAdapters = _nativeAdapterDiscovery.GetNativeAdapters(request.Prefix) + .Select(key => new + { + Key = key, + Versions = new List() // Native adapters have no versions + }) + .ToList(); + + // Get external adapters from storage var cloudFilesList = (await _cloudFilesService.ListAsync( $"{_serverlessOptions.AdapterRemotePath}/infolink6.{request.Prefix}")) @@ -40,7 +53,7 @@ public async Task Handle(AdapterSearchRequest request) return key; }); - return grouped.Select(i => new + var externalAdapters = grouped.Select(i => new { i.Key, Versions = i.Where(v => v.Key != i.Key && Semver.IsVersionNumber(v.Key.Split("/").Last())) @@ -49,6 +62,9 @@ public async Task Handle(AdapterSearchRequest request) Key = v.Key[index..] }).ToList() }); + + // Return native adapters first, then external + return nativeAdapters.Concat(externalAdapters); } } } \ No newline at end of file diff --git a/SW.Bitween.Api/Resources/ApiGateways/AddPartner.cs b/SW.Bitween.Api/Resources/ApiGateways/AddPartner.cs new file mode 100644 index 00000000..78e08409 --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/AddPartner.cs @@ -0,0 +1,67 @@ +using SW.Bitween.Domain.Gateway; +using SW.Bitween.Model; +using SW.PrimitiveTypes; +using System.Threading.Tasks; +using SW.Bitween.Domain.Accounts; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using SW.Bitween.Domain; + +namespace SW.Bitween.Resources.ApiGateways +{ + [HandlerName(nameof(AddPartner))] + public class AddPartner : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public AddPartner(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(int gatewayId, ApiGatewayPartnerCreate model) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + var gateway = await _dbContext.Set() + .Include(ag => ag.Partners) + .FirstOrDefaultAsync(ag => ag.Id == gatewayId); + + if (gateway == null) + throw new SWNotFoundException($"ApiGateway with Id {gatewayId} not found"); + + // Validate subscription exists and is of type GatewayApiCall + var subscription = await _dbContext.Set() + .FirstOrDefaultAsync(s => s.Id == model.SubscriptionId); + + if (subscription == null) + throw new SWNotFoundException($"Subscription with Id {model.SubscriptionId} not found"); + + if (subscription.Type != SubscriptionType.GatewayApiCall) + throw new SWException($"Subscription must be of type GatewayApiCall. Current type: {subscription.Type}"); + + // Check if partner already exists + var existingPartner = gateway.Partners != null + ? gateway.Partners.FirstOrDefault(p => p.PartnerId == model.PartnerId && p.SubscriptionId == model.SubscriptionId) + : null; + + if (existingPartner != null) + throw new SWException("Partner already exists in this gateway"); + + var partnerLink = new ApiGatewayPartner + { + ApiGatewayId = gatewayId, + PartnerId = model.PartnerId, + SubscriptionId = model.SubscriptionId + }; + + _dbContext.Add(partnerLink); + await _dbContext.SaveChangesAsync(); + + return null; + } + } +} + diff --git a/SW.Bitween.Api/Resources/ApiGateways/Create.cs b/SW.Bitween.Api/Resources/ApiGateways/Create.cs new file mode 100644 index 00000000..c4702745 --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/Create.cs @@ -0,0 +1,39 @@ +using SW.Bitween.Domain.Gateway; +using SW.Bitween.Model; +using SW.PrimitiveTypes; +using System.Threading.Tasks; +using SW.Bitween.Domain.Accounts; + +namespace SW.Bitween.Resources.ApiGateways +{ + public class Create : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public Create(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(ApiGatewayCreate model) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + if (string.IsNullOrWhiteSpace(model.UrlName)) + throw new SWException("UrlName is required"); + + var entity = new ApiGateway + { + Name = model.Name, + UrlName = model.UrlName + }; + + _dbContext.Add(entity); + await _dbContext.SaveChangesAsync(); + return entity.Id; + } + } +} + diff --git a/SW.Bitween.Api/Resources/ApiGateways/Delete.cs b/SW.Bitween.Api/Resources/ApiGateways/Delete.cs new file mode 100644 index 00000000..88143b5a --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/Delete.cs @@ -0,0 +1,29 @@ +using SW.EfCoreExtensions; +using SW.Bitween.Domain.Gateway; +using SW.PrimitiveTypes; +using System.Threading.Tasks; +using SW.Bitween.Domain.Accounts; + +namespace SW.Bitween.Resources.ApiGateways +{ + public class Delete : IDeleteHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public Delete(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(int key) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + await _dbContext.DeleteByKeyAsync(key); + return null; + } + } +} + diff --git a/SW.Bitween.Api/Resources/ApiGateways/Get.cs b/SW.Bitween.Api/Resources/ApiGateways/Get.cs new file mode 100644 index 00000000..0b935c0b --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/Get.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using SW.Bitween.Domain.Gateway; +using SW.PrimitiveTypes; +using System.Linq; +using System.Threading.Tasks; +using SW.Bitween.Model; + +namespace SW.Bitween.Resources.ApiGateways +{ + public class Get : IGetHandler + { + private readonly BitweenDbContext _dbContext; + + public Get(BitweenDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task Handle(int key) + { + var gateway = await _dbContext.Set() + .AsNoTracking() + .Include(ag => ag.Partners) + .ThenInclude(p => p.Partner) + .Include(ag => ag.Partners) + .ThenInclude(p => p.Subscription) + .FirstOrDefaultAsync(ag => ag.Id == key); + + if (gateway == null) + throw new SWNotFoundException($"ApiGateway with id '{key}' was not found"); + + return new ApiGatewayRow + { + Id = gateway.Id, + Name = gateway.Name, + UrlName = gateway.UrlName, + PartnersCount = gateway.Partners.Count, + Partners = gateway.Partners.Select(p => new ApiGatewayPartnerDto + { + PartnerId = p.PartnerId, + SubscriptionId = p.SubscriptionId, + PartnerName = p.Partner.Name, + SubscriptionName = p.Subscription.Name + }).ToList() + }; + } + } +} + diff --git a/SW.Bitween.Api/Resources/ApiGateways/RemovePartner.cs b/SW.Bitween.Api/Resources/ApiGateways/RemovePartner.cs new file mode 100644 index 00000000..610ac43d --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/RemovePartner.cs @@ -0,0 +1,51 @@ +using SW.Bitween.Domain.Gateway; +using SW.PrimitiveTypes; +using System.Threading.Tasks; +using SW.Bitween.Domain.Accounts; +using Microsoft.EntityFrameworkCore; +using System.Linq; + +namespace SW.Bitween.Resources.ApiGateways +{ + [HandlerName(nameof(RemovePartner))] + public class RemovePartner : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public RemovePartner(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(int gatewayId, RemovePartnerRequest request) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + var gateway = await _dbContext.Set() + .Include(ag => ag.Partners) + .FirstOrDefaultAsync(ag => ag.Id == gatewayId); + + if (gateway == null) + throw new SWNotFoundException($"ApiGateway with Id {gatewayId} not found"); + + var partnerLink = gateway.Partners? + .FirstOrDefault(p => p.PartnerId == request.PartnerId); + + if (partnerLink == null) + throw new SWNotFoundException($"Partner with Id {request.PartnerId} not found in gateway {gatewayId}"); + + _dbContext.Remove(partnerLink); + await _dbContext.SaveChangesAsync(); + + return null; + } + } + + public class RemovePartnerRequest + { + public int PartnerId { get; set; } + } +} + diff --git a/SW.Bitween.Api/Resources/ApiGateways/Search.cs b/SW.Bitween.Api/Resources/ApiGateways/Search.cs new file mode 100644 index 00000000..b22f722f --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/Search.cs @@ -0,0 +1,49 @@ +using SW.PrimitiveTypes; +using System.Threading.Tasks; +using SW.EfCoreExtensions; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using SW.Bitween.Domain.Gateway; +using SW.Bitween.Model; + +namespace SW.Bitween.Resources.ApiGateways +{ + public class Search : ISearchyHandler + { + private readonly BitweenDbContext _dbContext; + + public Search(BitweenDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task Handle(SearchyRequest searchyRequest, bool lookup = false, string searchPhrase = null) + { + var query = from gateway in _dbContext.Set() + select new ApiGatewayRow + { + Id = gateway.Id, + Name = gateway.Name, + UrlName = gateway.UrlName, + PartnersCount = gateway.Partners.Count + }; + + query = query.AsNoTracking(); + + if (lookup) + { + return await query.Search(searchyRequest.Conditions).ToDictionaryAsync(k => k.Id.ToString(), v => v.Name); + } + + // Apply ordering by Id descending + query = query.OrderByDescending(g => g.Id); + + return new SearchyResponse + { + TotalCount = await query.Search(searchyRequest.Conditions).CountAsync(), + Result = await query.Search(searchyRequest.Conditions, searchyRequest.Sorts, searchyRequest.PageSize, searchyRequest.PageIndex).ToListAsync() + }; + } + } +} + diff --git a/SW.Bitween.Api/Resources/ApiGateways/Update.cs b/SW.Bitween.Api/Resources/ApiGateways/Update.cs new file mode 100644 index 00000000..b53bef93 --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/Update.cs @@ -0,0 +1,45 @@ +using SW.EfCoreExtensions; +using SW.Bitween.Domain.Gateway; +using SW.Bitween.Model; +using SW.PrimitiveTypes; +using System.Threading.Tasks; +using SW.Bitween.Domain.Accounts; +using Microsoft.EntityFrameworkCore; +using System.Linq; + +namespace SW.Bitween.Resources.ApiGateways +{ + public class Update : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public Update(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(int key, ApiGatewayUpdate model) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + var entity = await _dbContext.Set() + .Include(ag => ag.Partners) + .FirstOrDefaultAsync(ag => ag.Id == key); + + if (entity == null) + throw new SWNotFoundException($"ApiGateway with Id {key} not found"); + + if (string.IsNullOrWhiteSpace(model.UrlName)) + throw new SWException("UrlName is required"); + + entity.Name = model.Name; + entity.UrlName = model.UrlName; + + await _dbContext.SaveChangesAsync(); + return null; + } + } +} + diff --git a/SW.Bitween.Api/Resources/ApiGateways/UpdatePartner.cs b/SW.Bitween.Api/Resources/ApiGateways/UpdatePartner.cs new file mode 100644 index 00000000..87405abb --- /dev/null +++ b/SW.Bitween.Api/Resources/ApiGateways/UpdatePartner.cs @@ -0,0 +1,59 @@ +using SW.Bitween.Domain.Gateway; +using SW.Bitween.Model; +using SW.PrimitiveTypes; +using System.Threading.Tasks; +using SW.Bitween.Domain.Accounts; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using SW.Bitween.Domain; + +namespace SW.Bitween.Resources.ApiGateways +{ + [HandlerName(nameof(UpdatePartner))] + public class UpdatePartner : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public UpdatePartner(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(int gatewayId, ApiGatewayPartnerCreate model) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + var gateway = await _dbContext.Set() + .Include(ag => ag.Partners) + .FirstOrDefaultAsync(ag => ag.Id == gatewayId); + + if (gateway == null) + throw new SWNotFoundException($"ApiGateway with Id {gatewayId} not found"); + + // Validate subscription exists and is of type GatewayApiCall + var subscription = await _dbContext.Set() + .FirstOrDefaultAsync(s => s.Id == model.SubscriptionId); + + if (subscription == null) + throw new SWNotFoundException($"Subscription with Id {model.SubscriptionId} not found"); + + if (subscription.Type != SubscriptionType.GatewayApiCall) + throw new SWException($"Subscription must be of type GatewayApiCall. Current type: {subscription.Type}"); + + var partnerLink = gateway.Partners? + .FirstOrDefault(p => p.PartnerId == model.PartnerId); + + if (partnerLink == null) + throw new SWNotFoundException($"Partner with Id {model.PartnerId} not found in gateway {gatewayId}"); + + partnerLink.SubscriptionId = model.SubscriptionId; + + await _dbContext.SaveChangesAsync(); + + return null; + } + } +} + diff --git a/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs new file mode 100644 index 00000000..6d131146 --- /dev/null +++ b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; +using FluentValidation; +using Microsoft.EntityFrameworkCore; +using SW.Bitween.Domain; +using SW.Bitween.Domain.Accounts; +using SW.Bitween.Model; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Resources.GlobalAdapterValuesSets +{ + public class Create : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public Create(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(GlobalAdapterValuesSetCreate request) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + var exists = await _dbContext.Set().AnyAsync(x => x.Id == request.Id); + if (exists) + throw new SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id '{request.Id}' already exists"); + + var entity = new GlobalAdapterValuesSet + { + Id = request.Id, + Name = request.Name, + Values = request.Values + }; + + _dbContext.Add(entity); + await _dbContext.SaveChangesAsync(); + return new + { + entity.Id + }; + } + + private class Validate : AbstractValidator + { + public Validate() + { + RuleFor(i => i.Id).NotEmpty(); + RuleFor(i => i.Name).NotEmpty(); + RuleFor(i => i.Values).NotNull(); + } + } + } +} diff --git a/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Delete.cs b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Delete.cs new file mode 100644 index 00000000..6a8e557a --- /dev/null +++ b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Delete.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using SW.Bitween.Domain; +using SW.Bitween.Domain.Accounts; +using SW.Bitween.Model; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Resources.GlobalAdapterValuesSets +{ + [HandlerName("delete")] + public class Delete : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public Delete(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(string key, DeleteGlobalAdapterValuesSetModel _) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + var entity = await _dbContext.Set().FindAsync(key); + if (entity is null) + throw new SWValidationException("NOT_FOUND", $"GlobalAdapterValuesSet with id {key} was not found"); + + _dbContext.Remove(entity); + await _dbContext.SaveChangesAsync(); + return null; + } + } +} diff --git a/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs new file mode 100644 index 00000000..57a229e1 --- /dev/null +++ b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using SW.Bitween.Domain; +using SW.Bitween.Model; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Resources.GlobalAdapterValuesSets +{ + public class Get : IGetHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public Get(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(string key) + { + _requestContext.EnsureAccess(Domain.Accounts.AccountRole.Admin, Domain.Accounts.AccountRole.Member, Domain.Accounts.AccountRole.Viewer); + + var entity = await _dbContext.Set() + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == key); + + if (entity == null) + throw new SWNotFoundException($"GlobalAdapterValuesSet with id '{key}' was not found"); + + return new GlobalAdapterValuesSetRow + { + Id = entity.Id, + Name = entity.Name, + Values = entity.Values + }; + } + } +} diff --git a/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Search.cs b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Search.cs new file mode 100644 index 00000000..9f3d1f73 --- /dev/null +++ b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Search.cs @@ -0,0 +1,44 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using SW.Bitween.Domain; +using SW.Bitween.Model; +using SW.EfCoreExtensions; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Resources.GlobalAdapterValuesSets +{ + public class Search : ISearchyHandler + { + private readonly BitweenDbContext _dbContext; + + public Search(BitweenDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task Handle(SearchyRequest searchyRequest, bool lookup = false, string searchPhrase = null) + { + var query = from item in _dbContext.Set() + select new GlobalAdapterValuesSetRow + { + Id = item.Id, + Name = item.Name, + Values = item.Values + }; + + query = query.AsNoTracking(); + + if (lookup) + { + return await query.Search(searchyRequest.Conditions).ToDictionaryAsync(k => k.Id, v => v.Name); + } + + return new SearchyResponse + { + TotalCount = await query.Search(searchyRequest.Conditions).CountAsync(), + Result = await query.Search(searchyRequest.Conditions, searchyRequest.Sorts, searchyRequest.PageSize, searchyRequest.PageIndex).ToListAsync() + }; + } + } +} diff --git a/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Update.cs b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Update.cs new file mode 100644 index 00000000..43090d1b --- /dev/null +++ b/SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Update.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using FluentValidation; +using SW.Bitween.Domain; +using SW.Bitween.Domain.Accounts; +using SW.Bitween.Model; +using SW.PrimitiveTypes; + +namespace SW.Bitween.Resources.GlobalAdapterValuesSets +{ + public class Update : ICommandHandler + { + private readonly BitweenDbContext _dbContext; + private readonly RequestContext _requestContext; + + public Update(BitweenDbContext dbContext, RequestContext requestContext) + { + _dbContext = dbContext; + _requestContext = requestContext; + } + + public async Task Handle(string key, GlobalAdapterValuesSetUpdate request) + { + _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member); + + var entity = await _dbContext.Set().FindAsync(key); + if (entity is null) + throw new SWValidationException("NOT_FOUND", $"GlobalAdapterValuesSet with id {key} was not found"); + + entity.Name = request.Name; + entity.Values = request.Values; + + await _dbContext.SaveChangesAsync(); + return null; + } + + private class Validate : AbstractValidator + { + public Validate() + { + RuleFor(i => i.Name).NotEmpty(); + RuleFor(i => i.Values).NotNull(); + } + } + } +} diff --git a/SW.Bitween.Api/Resources/Partners/Get.cs b/SW.Bitween.Api/Resources/Partners/Get.cs index d4ac2740..bf96629b 100644 --- a/SW.Bitween.Api/Resources/Partners/Get.cs +++ b/SW.Bitween.Api/Resources/Partners/Get.cs @@ -38,7 +38,9 @@ async public Task Handle(int key) Type = sub.Type, DocumentId = sub.DocumentId, - }).ToList() + }).ToList(), + + AdapterProperties = partner.AdapterProperties }).AsNoTracking().SingleOrDefaultAsync(); } diff --git a/SW.Bitween.Api/Resources/Partners/Update.cs b/SW.Bitween.Api/Resources/Partners/Update.cs index 7be363f1..52d6601d 100644 --- a/SW.Bitween.Api/Resources/Partners/Update.cs +++ b/SW.Bitween.Api/Resources/Partners/Update.cs @@ -29,6 +29,7 @@ public async Task Handle(int key, PartnerUpdate model) var entity = await _dbContext.FindAsync(key); entity.SetApiCredentials(model.ApiCredentials.Select(kv => new ApiCredential(kv.Key, kv.Value))); + entity.AdapterProperties = model.AdapterProperties; _dbContext.Entry(entity).SetProperties(model); await _dbContext.SaveChangesAsync(); return null; diff --git a/SW.Bitween.Api/Resources/Subscriptions/Create.cs b/SW.Bitween.Api/Resources/Subscriptions/Create.cs index f36c78b0..c6afb528 100644 --- a/SW.Bitween.Api/Resources/Subscriptions/Create.cs +++ b/SW.Bitween.Api/Resources/Subscriptions/Create.cs @@ -36,6 +36,10 @@ public async Task Handle(SubscriptionCreate model) case SubscriptionType.Internal: entity = new Subscription(model.Name, model.DocumentId, model.Type, model.PartnerId!.Value); break; + case SubscriptionType.GatewayApiCall: + entity = new Subscription(model.Name, model.DocumentId, model.Type); + break; + case SubscriptionType.Unknown: default: throw new BitweenException(); @@ -57,7 +61,7 @@ public Validate() RuleFor(i => i.PartnerId).NotEqual(Partner.SystemId); RuleFor(i => i.Type).NotEqual(SubscriptionType.Unknown); - When(i => i.Type != SubscriptionType.Receiving, () => { RuleFor(i => i.PartnerId).NotEmpty(); }); + When(i => (i.Type != SubscriptionType.Receiving && i.Type != SubscriptionType.GatewayApiCall), () => { RuleFor(i => i.PartnerId).NotEmpty(); }); When(i => i.Type == SubscriptionType.Aggregation, () => { RuleFor(i => i.AggregationForId).NotEmpty(); }); diff --git a/SW.Bitween.Api/Resources/Subscriptions/Get.cs b/SW.Bitween.Api/Resources/Subscriptions/Get.cs index 3bdc6b66..19c46deb 100644 --- a/SW.Bitween.Api/Resources/Subscriptions/Get.cs +++ b/SW.Bitween.Api/Resources/Subscriptions/Get.cs @@ -23,7 +23,7 @@ public async Task Handle(int key) await dbContext.Set().AsNoTracking().Search("Id", key).SingleOrDefaultAsync(); return - new SubscriptionUpdate + new SubscriptionGet { AggregationForId = subscriber.AggregationForId, DocumentFilter = subscriber.DocumentFilter.ToKeyAndValueCollection(), diff --git a/SW.Bitween.Api/Resources/Subscriptions/Update.cs b/SW.Bitween.Api/Resources/Subscriptions/Update.cs index f918b028..21bc5906 100644 --- a/SW.Bitween.Api/Resources/Subscriptions/Update.cs +++ b/SW.Bitween.Api/Resources/Subscriptions/Update.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using SW.Bitween.Domain.Accounts; namespace SW.Bitween.Resources.Subscriptions @@ -92,7 +93,19 @@ private static bool ValidateMatch(IPropertyMatchSpecification model) private class Validate : AbstractValidator { - public Validate(IServiceProvider serviceProvider) + private ValueTask GetSub(BitweenDbContext dbContext,IHttpContextAccessor httpContextAccessor) + { + var path = httpContextAccessor.HttpContext?.Request.Path.Value; + + var lastSegment = path? + .Split('/', StringSplitOptions.RemoveEmptyEntries) + .LastOrDefault(); + if(lastSegment is null || !int.TryParse(lastSegment, out var subId)) + return new ValueTask((Subscription)null); + + return dbContext.FindAsync(subId); + } + public Validate(BitweenDbContext dbContext,IHttpContextAccessor httpContextAccessor,NativeAdapterDiscoveryService nativeAdapterDiscovery, IServerlessService serverless) { RuleFor(i => i.Name).NotEmpty(); RuleFor(i => i.MatchExpression).Must(ValidateMatch); @@ -100,12 +113,24 @@ public Validate(IServiceProvider serviceProvider) When(i => i.MapperId != null, () => { - RuleFor(i => i.MapperProperties).CustomAsync(async (i, context, ct) => + RuleFor(i => i.MapperProperties).CustomAsync(async (i, context, _) => { - var serverless = serviceProvider.GetService(); - await serverless.StartAsync(((SubscriptionUpdate)context.InstanceToValidate).MapperId, null); - var mustProps = (await serverless.GetExpectedStartupValues()) - .Where(p => p.Value.Optional == false).Select(p => p.Key); + var mapperId = ((SubscriptionUpdate)context.InstanceToValidate).MapperId; + var mustProps = Enumerable.Empty(); + + // Check if it's a native adapter + if (mapperId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var properties = nativeAdapterDiscovery.GetNativeAdapterProperties(mapperId); + mustProps = properties.Where(p => p.Value.EndsWith(" *")).Select(p => p.Key); + } + else + { + await serverless.StartAsync(mapperId, null); + mustProps = (await serverless.GetExpectedStartupValues()) + .Where(p => p.Value.Optional == false).Select(p => p.Key); + } + var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase) .Except(i.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p => p.Key)); if (missing.Any()) @@ -117,10 +142,23 @@ public Validate(IServiceProvider serviceProvider) { RuleFor(i => i.HandlerProperties).CustomAsync(async (i, context, ct) => { - var serverless = serviceProvider.GetService(); - await serverless.StartAsync(((SubscriptionUpdate)context.InstanceToValidate).HandlerId, null); - var mustProps = (await serverless.GetExpectedStartupValues()) - .Where(p => p.Value.Optional == false).Select(p => p.Key); + var handlerId = ((SubscriptionUpdate)context.InstanceToValidate).HandlerId; + var mustProps = Enumerable.Empty(); + + // Check if it's a native adapter + if (handlerId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var properties = nativeAdapterDiscovery.GetNativeAdapterProperties(handlerId); + mustProps = properties.Where(p => p.Value.EndsWith(" *")).Select(p => p.Key); + } + else + { + + await serverless.StartAsync(handlerId, null); + mustProps = (await serverless.GetExpectedStartupValues()) + .Where(p => p.Value.Optional == false).Select(p => p.Key); + } + var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase) .Except(i.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p => p.Key)); if (missing.Any()) @@ -128,33 +166,69 @@ public Validate(IServiceProvider serviceProvider) }); }); - When(i => i.Type == SubscriptionType.Receiving, () => + RuleFor(i => i).CustomAsync(async (model, context, ct) => { - RuleFor(i => i.ReceiverId).NotEmpty(); - RuleFor(i => i.Schedules).NotEmpty(); + var subscription = await GetSub(dbContext, httpContextAccessor); - When(i => i.ReceiverId != null, () => + if (subscription?.Type == SubscriptionType.Receiving) { - RuleFor(i => i.ReceiverProperties).CustomAsync(async (i, context, ct) => + if (string.IsNullOrEmpty(model.ReceiverId)) + context.AddFailure(nameof(model.ReceiverId), "ReceiverId is required for Receiving subscriptions"); + + if (model.Schedules == null || !model.Schedules.Any()) + context.AddFailure(nameof(model.Schedules), "Schedules are required for Receiving subscriptions"); + + if (!string.IsNullOrEmpty(model.ReceiverId)) { - var serverless = serviceProvider.GetService(); - await serverless.StartAsync(((SubscriptionUpdate)context.InstanceToValidate).ReceiverId, - null); - var mustProps = (await serverless.GetExpectedStartupValues()) - .Where(p => p.Value.Optional == false).Select(p => p.Key); + var mustProps = Enumerable.Empty(); + + // Check if it's a native adapter + if (model.ReceiverId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var properties = nativeAdapterDiscovery.GetNativeAdapterProperties(model.ReceiverId); + mustProps = properties.Where(p => p.Value.EndsWith(" *")).Select(p => p.Key); + } + else + { + await serverless.StartAsync(model.ReceiverId, null); + mustProps = (await serverless.GetExpectedStartupValues()) + .Where(p => p.Value.Optional == false).Select(p => p.Key); + } + var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase) - .Except(i.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p => p.Key)); + .Except(model.ReceiverProperties.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p => p.Key)); if (missing.Any()) - context.AddFailure($"Missing properties: {string.Join(",", missing)}"); - }); - }); + context.AddFailure(nameof(model.ReceiverProperties), $"Missing properties: {string.Join(",", missing)}"); + } + } }); - When(i => i.Type == SubscriptionType.Aggregation, () => + RuleFor(i => i).CustomAsync(async (model, context, ct) => { - RuleFor(i => i.Schedules).NotEmpty(); - RuleFor(i => i.AggregationForId).NotEmpty(); + var subscription = await GetSub(dbContext, httpContextAccessor); + + if (subscription?.Type == SubscriptionType.Aggregation) + { + if (model.Schedules == null || !model.Schedules.Any()) + context.AddFailure(nameof(model.Schedules), "Schedules are required for Aggregation subscriptions"); + + if (!model.AggregationForId.HasValue) + context.AddFailure(nameof(model.AggregationForId), "AggregationForId is required for Aggregation subscriptions"); + } }); + + RuleFor(i => i).CustomAsync(async (model, context, ct) => + { + + var subscription = await GetSub(dbContext, httpContextAccessor); + + if (subscription?.Type == SubscriptionType.GatewayApiCall) + { + if (model.PartnerId.HasValue) + context.AddFailure(nameof(model.PartnerId), "PartnerId must be null for GatewayApiCall subscriptions"); + } + }); + } } } diff --git a/SW.Bitween.Api/SW.Bitween.Api.csproj b/SW.Bitween.Api/SW.Bitween.Api.csproj index 5694d925..6f0b94e3 100644 --- a/SW.Bitween.Api/SW.Bitween.Api.csproj +++ b/SW.Bitween.Api/SW.Bitween.Api.csproj @@ -13,7 +13,9 @@ - + + + @@ -27,6 +29,7 @@ + diff --git a/SW.Bitween.Api/Services/Caching/InMemoryInfolinkCache.cs b/SW.Bitween.Api/Services/Caching/InMemoryInfolinkCache.cs index bf8022bb..05879daa 100644 --- a/SW.Bitween.Api/Services/Caching/InMemoryInfolinkCache.cs +++ b/SW.Bitween.Api/Services/Caching/InMemoryInfolinkCache.cs @@ -39,12 +39,14 @@ private async Task Load() var cachedDocuments = await repo.Set().AsNoTracking().ToArrayAsync(); var cachedNotifiers = await repo.Set().Where(i => !i.Inactive).AsNoTracking().ToArrayAsync(); var cachedWorkGroups = await repo.Set().AsNoTracking().ToArrayAsync(); + var cachedGlobalValues = await repo.Set().AsNoTracking().ToArrayAsync(); var span = TimeSpan.FromMinutes(10); _cache.Set(nameof(Document), cachedDocuments, span); _cache.Set(nameof(Subscription), cachedSubscriptions, span); _cache.Set(nameof(Notifier), cachedNotifiers, span); _cache.Set(nameof(WorkGroup), cachedWorkGroups, span); + _cache.Set(nameof(GlobalAdapterValuesSet), cachedGlobalValues, span); } public async Task ListSubscriptionsByDocumentAsync(int documentId) @@ -142,6 +144,28 @@ public async Task WorkGroupBySubscriptionIdAsync(int subscriptionId) return await WorkGroupByIdAsync(subscription.WorkGroupId.Value); } + public async Task GlobalAdapterValuesSetById(string globalAdapterValuesSetId) + { + if (!_cache.TryGetValue(nameof(GlobalAdapterValuesSet), out GlobalAdapterValuesSet[] cachedGlobalValues)) + { + await Load(); + return _cache.Get(nameof(GlobalAdapterValuesSet)).FirstOrDefault(gav => gav.Id == globalAdapterValuesSetId); + } + + return cachedGlobalValues.FirstOrDefault(gav => gav.Id == globalAdapterValuesSetId); + } + + public async Task ListGlobalAdapterValuesSetsAsync() + { + if (!_cache.TryGetValue(nameof(GlobalAdapterValuesSet), out GlobalAdapterValuesSet[] cachedGlobalValues)) + { + await Load(); + return _cache.Get(nameof(GlobalAdapterValuesSet)); + } + + return cachedGlobalValues; + } + public void Revoke() { _cache.Remove(nameof(Subscription)); diff --git a/SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs b/SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs new file mode 100644 index 00000000..6bf15119 --- /dev/null +++ b/SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using SW.Bitween.NativeAdapters; +using SW.PrimitiveTypes; + +namespace SW.Bitween +{ + public class NativeAdapterDiscoveryService + { + private readonly Dictionary> _adaptersCache; + + public NativeAdapterDiscoveryService() + { + _adaptersCache = new Dictionary>(); + DiscoverNativeAdapters(); + } + + private void DiscoverNativeAdapters() + { + var assemblies = new List() { typeof(DictionaryConverter).Assembly }; + + + + foreach (var assembly in assemblies) + { + try + { + var types = assembly.GetTypes() + .Where(t => t.IsClass && !t.IsAbstract); + + foreach (var type in types) + { + if (typeof(IInfolinkHandler).IsAssignableFrom(type)) + { + AddAdapter("handlers", type); + } + else if (typeof(IInfolinkValidator).IsAssignableFrom(type)) + { + AddAdapter("validators", type); + } + else if (typeof(IInfolinkReceiver).IsAssignableFrom(type)) + { + AddAdapter("receivers", type); + } + } + } + catch + { + // Skip assemblies that can't be loaded or scanned + } + } + } + + private void AddAdapter(string category, Type type) + { + if (!_adaptersCache.ContainsKey(category)) + { + _adaptersCache[category] = new List(); + } + + var adapterName = type.Name;//.Replace("Handler", "").Replace("Mapper", "") + //.Replace("Validator", "").Replace("Receiver", "").ToLower(); + + _adaptersCache[category].Add(new NativeAdapterInfo + { + Key = $"native.{adapterName}", + Name = type.Name, + Type = type, + Category = category + }); + } + + public IEnumerable GetNativeAdapters(string prefix) + { + if (string.IsNullOrEmpty(prefix)) + { + return _adaptersCache.Values.SelectMany(v => v).Select(a => a.Key); + } + + var category = prefix.ToLower().TrimStart('.'); + + if (_adaptersCache.TryGetValue(category, out var adapters)) + { + return adapters.Select(a => a.Key); + } + + return Enumerable.Empty(); + } + + public NativeAdapterInfo GetNativeAdapterInfo(string adapterId) + { + return _adaptersCache.Values + .SelectMany(v => v) + .FirstOrDefault(a => a.Key.Equals(adapterId, StringComparison.OrdinalIgnoreCase)); + } + + public Dictionary GetNativeAdapterProperties(string adapterId) + { + var adapterInfo = GetNativeAdapterInfo(adapterId); + if (adapterInfo == null) + return new Dictionary(); + + var result = new Dictionary(); + + // Get constructor parameters + var constructor = adapterInfo.Type.GetConstructors() + .FirstOrDefault(c => c.GetParameters().Length > 0); + + if (constructor == null) + return result; + + // Get the first parameter type (input model) + var inputParameter = constructor.GetParameters().FirstOrDefault(); + if (inputParameter == null) + return result; + + var inputType = inputParameter.ParameterType; + + // Get all properties from the input model + var properties = inputType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var prop in properties) + { + var defaultValue = GetDefaultValue(prop); + var hasRequiredAttribute = prop.GetCustomAttribute() != null; + var isRequired = hasRequiredAttribute || (!IsNullableType(prop.PropertyType) && defaultValue == null); + + if (isRequired) + { + result[prop.Name] = $"{prop.Name} *"; + } + else + { + result[prop.Name] = $"{prop.Name} ({defaultValue ?? "null"})"; + } + } + + return result; + } + + private string? GetDefaultValue(PropertyInfo property) + { + // Try to get default value from DefaultValueAttribute if it exists + var defaultAttr = property.GetCustomAttribute(); + if (defaultAttr != null) + return defaultAttr.Value?.ToString(); + + // For value types, return their default + if (property.PropertyType.IsValueType) + return Activator.CreateInstance(property.PropertyType)?.ToString(); + + return null; + } + + private bool IsNullableType(Type type) + { + return !type.IsValueType || + Nullable.GetUnderlyingType(type) != null || + (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)); + } + } + + public class NativeAdapterInfo + { + public string Key { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public Type Type { get; set; } = null!; + public string Category { get; set; } = string.Empty; + } +} diff --git a/SW.Bitween.Api/Services/ReceivingService.cs b/SW.Bitween.Api/Services/ReceivingService.cs index ab0bb40b..684001aa 100644 --- a/SW.Bitween.Api/Services/ReceivingService.cs +++ b/SW.Bitween.Api/Services/ReceivingService.cs @@ -78,26 +78,106 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) async Task RunReceiver(IServiceProvider serviceProvider, string serverlessId, IDictionary startupParameters, int subId) { - var serverless = serviceProvider.GetRequiredService(); - await serverless.StartAsync(serverlessId, null, startupParameters); - await serverless.InvokeAsync(nameof(IInfolinkReceiver.Initialize), null); - var fileList = - (await serverless.InvokeAsync>(nameof(IInfolinkReceiver.ListFiles), null)).ToList(); + // Check if it's a native adapter + if (serverlessId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var nativeAdapterDiscovery = serviceProvider.GetRequiredService(); + var receiver = InstantiateNativeReceiver(nativeAdapterDiscovery, serverlessId, startupParameters); + + await receiver.Initialize(); + var fileList = (await receiver.ListFiles()).ToList(); + + logger.LogInformation($"Subscription:'{subId}' found {fileList.Count()} items for retrieval."); + + foreach (var file in fileList) + { + var xchangeFile = await receiver.GetFile(file); - logger.LogInformation($"Subscription:'{subId}' found {fileList.Count()} items for retrieval."); + logger.LogInformation($"Submitting received file for subscriber: '{subId}'."); - foreach (var file in fileList) + var xchangeService = serviceProvider.GetService(); + await xchangeService.SubmitSubscriptionXchange(subId, xchangeFile); + await receiver.DeleteFile(file); + } + + await receiver.Finalize(); + } + else { - var xchangeFile = await serverless.InvokeAsync(nameof(IInfolinkReceiver.GetFile), file); + // Use serverless for external adapters + var serverless = serviceProvider.GetRequiredService(); + await serverless.StartAsync(serverlessId, null, startupParameters); + await serverless.InvokeAsync(nameof(IInfolinkReceiver.Initialize), null); + var fileList = + (await serverless.InvokeAsync>(nameof(IInfolinkReceiver.ListFiles), null)).ToList(); + + logger.LogInformation($"Subscription:'{subId}' found {fileList.Count()} items for retrieval."); + + foreach (var file in fileList) + { + var xchangeFile = await serverless.InvokeAsync(nameof(IInfolinkReceiver.GetFile), file); + + logger.LogInformation($"Submitting received file for subscriber: '{subId}'."); + + var xchangeService = serviceProvider.GetService(); + await xchangeService.SubmitSubscriptionXchange(subId, xchangeFile); + await serverless.InvokeAsync(nameof(IInfolinkReceiver.DeleteFile), file); + } + + await serverless.InvokeAsync(nameof(IInfolinkReceiver.Finalize), null); + } + } + + private IInfolinkReceiver InstantiateNativeReceiver(NativeAdapterDiscoveryService nativeAdapterDiscovery, + string adapterId, IDictionary properties) + { + var adapterInfo = nativeAdapterDiscovery.GetNativeAdapterInfo(adapterId); + if (adapterInfo == null) + throw new BitweenException($"Native adapter not found: {adapterId}"); + + // Get the constructor that takes a parameter + var constructor = adapterInfo.Type.GetConstructors() + .FirstOrDefault(c => c.GetParameters().Length > 0); + + if (constructor == null) + throw new BitweenException($"Native adapter {adapterId} must have a constructor that accepts an input model"); + + // Get the input parameter type + var inputParameter = constructor.GetParameters().First(); + var inputType = inputParameter.ParameterType; - logger.LogInformation($"Submitting received file for subscriber: '{subId}'."); + // Create an instance of the input model by mapping properties + var inputInstance = Activator.CreateInstance(inputType); - var xchangeService = serviceProvider.GetService(); - await xchangeService.SubmitSubscriptionXchange(subId, xchangeFile); - await serverless.InvokeAsync(nameof(IInfolinkReceiver.DeleteFile), file); + // Map dictionary properties to the input model + foreach (var prop in inputType.GetProperties()) + { + // Case-insensitive property lookup + var propEntry = properties.FirstOrDefault(p => + string.Equals(p.Key, prop.Name, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(propEntry.Key)) + { + var value = propEntry.Value; + try + { + var convertedValue = Convert.ChangeType(value, + Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); + prop.SetValue(inputInstance, convertedValue); + } + catch + { + // If conversion fails, set string value directly + if (prop.PropertyType == typeof(string)) + prop.SetValue(inputInstance, value); + } + } } - await serverless.InvokeAsync(nameof(IInfolinkReceiver.Finalize), null); + // Instantiate the adapter with the input model + var adapter = Activator.CreateInstance(adapterInfo.Type, inputInstance); + + return (IInfolinkReceiver)adapter; } diff --git a/SW.Bitween.Api/Services/XchangeService.cs b/SW.Bitween.Api/Services/XchangeService.cs index ecf5fc39..2035002d 100644 --- a/SW.Bitween.Api/Services/XchangeService.cs +++ b/SW.Bitween.Api/Services/XchangeService.cs @@ -33,16 +33,19 @@ public class XchangeService : private readonly IPublish _publish; private readonly ILogger _logger; private readonly IInfolinkCache _BitweenCache; + private readonly NativeAdapterDiscoveryService _nativeAdapterDiscovery; public XchangeService(BitweenOptions BitweenSettings, BitweenDbContext dbContext, FilterService filterService, ICloudFilesService cloudFiles, IServiceProvider serviceProvider, - IPublish publish, ILogger logger, IInfolinkCache BitweenCache) + IPublish publish, ILogger logger, IInfolinkCache BitweenCache, + NativeAdapterDiscoveryService nativeAdapterDiscovery) { _BitweenSettings = BitweenSettings; _dbContext = dbContext; _filterService = filterService; _cloudFiles = cloudFiles; + _nativeAdapterDiscovery = nativeAdapterDiscovery; _serviceProvider = serviceProvider; _publish = publish; _logger = logger; @@ -50,11 +53,13 @@ public XchangeService(BitweenOptions BitweenSettings, BitweenDbContext dbContext } public async Task SubmitSubscriptionXchange(int subscriptionId, XchangeFile file, - string[] references = null) + string[] references = null, Partner gatewayPartner = null, + GlobalAdapterValuesSet[] globalAdapterValuesSets = null) { var subscription = await _BitweenCache.SubscriptionByIdAsync(subscriptionId); - var xchange = await CreateXchange(subscription, file, references, Guid.NewGuid().ToString("N")); + var xchange = await CreateXchange(subscription, file, references, Guid.NewGuid().ToString("N"), gatewayPartner, + globalAdapterValuesSets); await _dbContext.SaveChangesAsync(); return xchange.Id; } @@ -105,9 +110,11 @@ public async Task CreateXchange(Document document, WorkGroup workGroup, } public async Task CreateXchange(Subscription subscription, XchangeFile file, - string[] references = null, string correlationId = null) + string[] references = null, string correlationId = null, Partner gatewayPartner = null, + GlobalAdapterValuesSet[] globalAdapterValuesSets = null) { - var xchange = new Xchange(subscription, file, references, correlationId); + var xchange = new Xchange(subscription, file, references, correlationId, gatewayPartner, + globalAdapterValuesSets); await AddFile(xchange.Id, XchangeFileType.Input, file); _dbContext.Add(xchange); return xchange; @@ -125,13 +132,23 @@ private async Task RunMapper(Xchange xchange, XchangeFile xchangeFi { if (xchange.MapperId == null) return xchangeFile; - var serverless = _serviceProvider.GetRequiredService(); - var mapperProperties = xchange.MapperProperties.ToDictionary(); mapperProperties["xchangeid"] = xchange.Id; - await serverless.StartAsync(xchange.MapperId, xchange.CorrelationId ?? xchange.Id, mapperProperties); - xchangeFile = await serverless.InvokeAsync(nameof(IInfolinkHandler.Handle), xchangeFile); + // Check if it's a native adapter + if (xchange.MapperId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var handler = InstantiateNativeAdapter(xchange.MapperId, mapperProperties); + xchangeFile = await handler.Handle(xchangeFile); + } + else + { + // Use serverless for external adapters + var serverless = _serviceProvider.GetRequiredService(); + await serverless.StartAsync(xchange.MapperId, xchange.CorrelationId ?? xchange.Id, mapperProperties); + xchangeFile = await serverless.InvokeAsync(nameof(IInfolinkHandler.Handle), xchangeFile); + } + if (xchangeFile is null) throw new BitweenException( $"Unexpected null return value after running mapping for exchange id: {xchange.Id}, adapter id: {xchange.MapperId}"); @@ -145,10 +162,23 @@ public async Task RunValidator(string validatorId, IDictionary p { if (validatorId == null) return; - var serverless = _serviceProvider.GetRequiredService(); - await serverless.StartAsync(validatorId, null, properties); - var result = - await serverless.InvokeAsync(nameof(IInfolinkValidator.Validate), xchangeFile); + InfolinkValidatorResult result; + + // Check if it's a native adapter + if (validatorId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var validator = InstantiateNativeAdapter(validatorId, properties); + result = await validator.Validate(xchangeFile); + } + else + { + // Use serverless for external adapters + var serverless = _serviceProvider.GetRequiredService(); + await serverless.StartAsync(validatorId, null, properties); + result = await serverless.InvokeAsync(nameof(IInfolinkValidator.Validate), + xchangeFile); + } + if (!result.Success) throw new SWValidationException(result.Validations); } @@ -157,18 +187,80 @@ private async Task RunHandler(Xchange xchange, XchangeFile xchangeF { if (xchange.HandlerId == null) return null; - var serverless = _serviceProvider.GetRequiredService(); - var handlerProperties = xchange.HandlerProperties.ToDictionary(); handlerProperties["xchangeid"] = xchange.Id; - await serverless.StartAsync(xchange.HandlerId, xchange.CorrelationId ?? xchange.Id, handlerProperties); - xchangeFile = await serverless.InvokeAsync(nameof(IInfolinkHandler.Handle), xchangeFile); + // Check if it's a native adapter + if (xchange.HandlerId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var handler = InstantiateNativeAdapter(xchange.HandlerId, handlerProperties); + xchangeFile = await handler.Handle(xchangeFile); + } + else + { + // Use serverless for external adapters + var serverless = _serviceProvider.GetRequiredService(); + await serverless.StartAsync(xchange.HandlerId, xchange.CorrelationId ?? xchange.Id, handlerProperties); + xchangeFile = await serverless.InvokeAsync(nameof(IInfolinkHandler.Handle), xchangeFile); + } + if (xchangeFile != null) await AddFile(xchange.Id, XchangeFileType.Response, xchangeFile); return xchangeFile; } + private T InstantiateNativeAdapter(string adapterId, IDictionary properties) + { + var adapterInfo = _nativeAdapterDiscovery.GetNativeAdapterInfo(adapterId); + if (adapterInfo == null) + throw new BitweenException($"Native adapter not found: {adapterId}"); + + // Get the constructor that takes a parameter + var constructor = adapterInfo.Type.GetConstructors() + .FirstOrDefault(c => c.GetParameters().Length > 0); + + if (constructor == null) + throw new BitweenException( + $"Native adapter {adapterId} must have a constructor that accepts an input model"); + + // Get the input parameter type + var inputParameter = constructor.GetParameters().First(); + var inputType = inputParameter.ParameterType; + + // Create an instance of the input model by mapping properties + var inputInstance = Activator.CreateInstance(inputType); + + // Map dictionary properties to the input model + foreach (var prop in inputType.GetProperties()) + { + // Case-insensitive property lookup + var propEntry = properties.FirstOrDefault(p => + string.Equals(p.Key, prop.Name, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(propEntry.Key)) + { + var value = propEntry.Value; + try + { + var convertedValue = Convert.ChangeType(value, + Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); + prop.SetValue(inputInstance, convertedValue); + } + catch + { + // If conversion fails, set string value directly + if (prop.PropertyType == typeof(string)) + prop.SetValue(inputInstance, value); + } + } + } + + // Instantiate the adapter with the input model + var adapter = Activator.CreateInstance(adapterInfo.Type, inputInstance); + + return (T)adapter; + } + private async Task AddFile(string xchangeId, XchangeFileType type, XchangeFile file) { await _cloudFiles.WriteTextAsync(file.Data, new WriteFileSettings @@ -354,16 +446,26 @@ private async Task NotifyResult(Notifier notifier, XchangeResult xchangeResult, CorrelationId = xchange.CorrelationId }; - var serverless = _serviceProvider.GetRequiredService(); var handlerProperties = notifier.HandlerProperties.ToDictionary(); handlerProperties["xchangeid"] = xchangeResult.Id; try { - await serverless.StartAsync(notifier.HandlerId, correlationId, handlerProperties); - await serverless.InvokeAsync(nameof(IInfolinkHandler.Handle), - new XchangeFile(JsonConvert.SerializeObject(notificationData), xchangeResult.Id)); + // Check if it's a native adapter + if (notifier.HandlerId.StartsWith("native.", StringComparison.OrdinalIgnoreCase)) + { + var handler = InstantiateNativeAdapter(notifier.HandlerId, handlerProperties); + await handler.Handle(new XchangeFile(JsonConvert.SerializeObject(notificationData), xchangeResult.Id)); + } + else + { + // Use serverless for external adapters + var serverless = _serviceProvider.GetRequiredService(); + await serverless.StartAsync(notifier.HandlerId, correlationId, handlerProperties); + await serverless.InvokeAsync(nameof(IInfolinkHandler.Handle), + new XchangeFile(JsonConvert.SerializeObject(notificationData), xchangeResult.Id)); + } _dbContext.Add(new XchangeNotification(xchangeResult.Id, notifier.Id, notifier.Name)); } @@ -410,7 +512,8 @@ public Task Process(string messageTypeName, string message) public async Task> GetMessageTypeNamesWithOptions() { - var workgroups = (await _BitweenCache.ListWorkGroupsAsync()).ToList(); + // var workgroups = (await _BitweenCache.ListWorkGroupsAsync()).ToList(); + var workgroups = await _dbContext.Set().ToListAsync(); workgroups.Add(WorkGroup.None); var messageTypeNamesWithOptions = new Dictionary(); foreach (var workGroup in workgroups) diff --git a/SW.Bitween.MsSql/Migrations/20260217150741_ApiGateWayAndGlobalValues.Designer.cs b/SW.Bitween.MsSql/Migrations/20260217150741_ApiGateWayAndGlobalValues.Designer.cs new file mode 100644 index 00000000..8ba0bd6b --- /dev/null +++ b/SW.Bitween.MsSql/Migrations/20260217150741_ApiGateWayAndGlobalValues.Designer.cs @@ -0,0 +1,1107 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SW.Bitween; + +#nullable disable + +namespace SW.Bitween.MsSql.Migrations +{ + [DbContext(typeof(BitweenDbContext))] + [Migration("20260217150741_ApiGateWayAndGlobalValues")] + partial class ApiGateWayAndGlobalValues + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.23") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("Deleted") + .HasColumnType("bit"); + + b.Property("Disabled") + .HasColumnType("bit"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Email") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("EmailProvider") + .HasColumnType("tinyint"); + + b.Property("LoginMethods") + .HasColumnType("tinyint"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Password") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("Phone") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("varchar(20)"); + + b.Property("Role") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasFilter("[Email] IS NOT NULL"); + + b.ToTable("Accounts", (string)null); + + b.HasData( + new + { + Id = 9999, + CreatedOn = new DateTime(2021, 12, 31, 22, 0, 0, 0, DateTimeKind.Utc), + Deleted = false, + Disabled = false, + DisplayName = "Admin", + Email = "admin@Bitween.systems", + EmailProvider = (byte)0, + LoginMethods = (byte)2, + Password = "$SWHASH$V1$10000$VQCi48eitH4Ml5juvBMOFZrMdQwBbhuIQVXe6RR7qJdDF2bJ", + Role = 0 + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.RefreshToken", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("LoginMethod") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("RefreshTokens", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Document", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("BusEnabled") + .HasColumnType("bit"); + + b.Property("BusMessageTypeName") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("DisregardsUnfilteredMessages") + .HasColumnType("bit"); + + b.Property("DocumentFormat") + .HasColumnType("int"); + + b.Property("DuplicateInterval") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(false) + .HasColumnType("varchar(100)"); + + b.Property("PromotedProperties") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BusMessageTypeName") + .IsUnique() + .HasFilter("[BusMessageTypeName] IS NOT NULL"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Documents", (string)null); + + b.HasData( + new + { + Id = 10001, + BusEnabled = false, + DocumentFormat = 0, + DuplicateInterval = 0, + Name = "Aggregation Document", + PromotedProperties = "{}" + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.DocumentTrail", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Code") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DocumentId") + .HasColumnType("int"); + + b.Property("StateAfter") + .HasColumnType("nvarchar(max)"); + + b.Property("StateBefore") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedOn"); + + b.HasIndex("DocumentId"); + + b.ToTable("DocumentTrail"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UrlName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("UrlName") + .IsUnique(); + + b.ToTable("ApiGateways", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.Property("ApiGatewayId") + .HasColumnType("int"); + + b.Property("PartnerId") + .HasColumnType("int"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("ApiGatewayId", "PartnerId", "SubscriptionId"); + + b.HasIndex("PartnerId"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("ApiGatewayPartners", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.GlobalAdapterValuesSet", b => + { + b.Property("Id") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Values") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GlobalAdapterValuesSets", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Notifier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("HandlerId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("HandlerProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Inactive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RunOnBadResult") + .HasColumnType("bit"); + + b.Property("RunOnFailedResult") + .HasColumnType("bit"); + + b.Property("RunOnSubscriptions") + .HasColumnType("nvarchar(max)"); + + b.Property("RunOnSuccessfulResult") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Notifiers", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.OnHoldXchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BadData") + .HasColumnType("bit"); + + b.Property("Data") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("References") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("OnHoldXchanges", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdapterProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Partners", (string)null); + + b.HasData( + new + { + Id = 1, + Name = "SYSTEM" + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AggregateOn") + .HasColumnType("datetime2"); + + b.Property("AggregationForId") + .HasColumnType("int"); + + b.Property("AggregationTarget") + .HasColumnType("tinyint"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ConsecutiveFailures") + .HasColumnType("int"); + + b.Property("DocumentFilter") + .HasColumnType("nvarchar(max)"); + + b.Property("DocumentId") + .HasColumnType("int"); + + b.Property("HandlerId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("HandlerProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Inactive") + .HasColumnType("bit"); + + b.Property("IsRunning") + .HasColumnType("bit"); + + b.Property("LastException") + .HasColumnType("nvarchar(max)"); + + b.Property("MapperId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("MapperProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("MatchExpression") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PartnerId") + .HasColumnType("int"); + + b.Property("PausedOn") + .HasColumnType("datetime2"); + + b.Property("ReceiveOn") + .HasColumnType("datetime2"); + + b.Property("ReceiverId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("ReceiverProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("ResponseMessageTypeName") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("ResponseSubscriptionId") + .HasColumnType("int"); + + b.Property("Temporary") + .HasColumnType("bit"); + + b.Property("Type") + .HasColumnType("tinyint"); + + b.Property("ValidatorId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("ValidatorProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("WorkGroupId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AggregationForId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DocumentId"); + + b.HasIndex("PartnerId"); + + b.HasIndex("ResponseSubscriptionId"); + + b.HasIndex("WorkGroupId"); + + b.ToTable("Subscriptions", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique() + .HasFilter("[Code] IS NOT NULL"); + + b.ToTable("SubscriptionCategory"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionTrail", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Code") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("StateAfter") + .HasColumnType("nvarchar(max)"); + + b.Property("StateBefore") + .HasColumnType("nvarchar(max)"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedOn"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("SubscriptionTrail"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.WorkGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BusMessageName") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(false) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Options") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("WorkGroup"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Xchange", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(max)"); + + b.Property("DocumentId") + .HasColumnType("int"); + + b.Property("HandlerId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("HandlerProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("InputContentType") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("InputHash") + .IsRequired() + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("InputName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("InputSize") + .HasColumnType("int"); + + b.Property("MapperId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("MapperProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("References") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.Property("ResponseMessageTypeName") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("ResponseSubscriptionId") + .HasColumnType("int"); + + b.Property("RetryFor") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("StartedOn") + .HasColumnType("datetime2"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("InputHash"); + + b.HasIndex("RetryFor"); + + b.HasIndex("StartedOn"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("Xchanges", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeAggregation", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("AggregatedOn") + .HasColumnType("datetime2"); + + b.Property("AggregationXchangeId") + .IsRequired() + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AggregationXchangeId"); + + b.ToTable("XchangeAggregations", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeDelivery", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("DeliveredOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DeliveredOn"); + + b.ToTable("XchangeDeliveries", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("FinishedOn") + .HasColumnType("datetime2"); + + b.Property("NotifierId") + .HasColumnType("int"); + + b.Property("NotifierName") + .HasColumnType("nvarchar(max)"); + + b.Property("Success") + .HasColumnType("bit"); + + b.Property("XchangeId") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.ToTable("XchangeNotifications", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangePromotedProperties", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("Hits") + .HasMaxLength(2000) + .IsUnicode(false) + .HasColumnType("varchar(2000)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("PropertiesRaw") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("PropertiesRaw"); + + b.ToTable("XchangePromotedProperties", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeResult", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("FinishedOn") + .HasColumnType("datetime2"); + + b.Property("OutputBad") + .HasColumnType("bit"); + + b.Property("OutputContentType") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("OutputHash") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("OutputName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OutputSize") + .HasColumnType("int"); + + b.Property("ResponseBad") + .HasColumnType("bit"); + + b.Property("ResponseContentType") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("ResponseHash") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("ResponseName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ResponseSize") + .HasColumnType("int"); + + b.Property("ResponseXchangeId") + .HasColumnType("nvarchar(max)"); + + b.Property("Success") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("XchangeResults", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.RunFlagUpdater+RunningResult", b => + { + b.Property("IsRunning") + .HasColumnType("bit"); + + b.ToTable((string)null); + + b.ToView(null, (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.RefreshToken", b => + { + b.HasOne("SW.Bitween.Domain.Accounts.Account", null) + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.DocumentTrail", b => + { + b.HasOne("SW.Bitween.Domain.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.HasOne("SW.Bitween.Domain.Gateway.ApiGateway", "ApiGateway") + .WithMany("Partners") + .HasForeignKey("ApiGatewayId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Partner", "Partner") + .WithMany() + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ApiGateway"); + + b.Navigation("Partner"); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.OwnsMany("SW.Bitween.Domain.ApiCredential", "ApiCredentials", b1 => + { + b1.Property("PartnerId") + .HasColumnType("int"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Key") + .IsRequired() + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b1.HasKey("PartnerId", "Id"); + + b1.HasIndex("Key") + .IsUnique(); + + b1.ToTable("PartnerApiCredentials", (string)null); + + b1.WithOwner() + .HasForeignKey("PartnerId"); + + b1.HasData( + new + { + PartnerId = 1, + Id = 1, + Key = "7facc758283844b49cc4ffd26a75b1de", + Name = "default" + }); + }); + + b.Navigation("ApiCredentials"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Subscription", b => + { + b.HasOne("SW.Bitween.Domain.Subscription", null) + .WithMany() + .HasForeignKey("AggregationForId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("FK_Subscriptions_AggFor"); + + b.HasOne("SW.Bitween.Domain.SubscriptionCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId"); + + b.HasOne("SW.Bitween.Domain.Document", null) + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Partner", null) + .WithMany("Subscriptions") + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SW.Bitween.Domain.Subscription", null) + .WithMany() + .HasForeignKey("ResponseSubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("FK_Subscriptions_RespSub"); + + b.HasOne("SW.Bitween.Domain.WorkGroup", "WorkGroup") + .WithMany() + .HasForeignKey("WorkGroupId"); + + b.OwnsMany("SW.Bitween.Domain.Schedule", "Schedules", b1 => + { + b1.Property("SubscriptionId") + .HasColumnType("int"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Backwards") + .HasColumnType("bit"); + + b1.Property("On") + .HasColumnType("bigint"); + + b1.Property("Recurrence") + .HasColumnType("tinyint"); + + b1.HasKey("SubscriptionId", "Id"); + + b1.ToTable("SubscriptionSchedules", (string)null); + + b1.WithOwner() + .HasForeignKey("SubscriptionId"); + }); + + b.Navigation("Category"); + + b.Navigation("Schedules"); + + b.Navigation("WorkGroup"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionTrail", b => + { + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Xchange", b => + { + b.HasOne("SW.Bitween.Domain.Document", null) + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeAggregation", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeAggregation", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeDelivery", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeDelivery", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangePromotedProperties", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangePromotedProperties", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeResult", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeResult", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Navigation("Partners"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.Navigation("Subscriptions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SW.Bitween.MsSql/Migrations/20260217150741_ApiGateWayAndGlobalValues.cs b/SW.Bitween.MsSql/Migrations/20260217150741_ApiGateWayAndGlobalValues.cs new file mode 100644 index 00000000..aed0d9df --- /dev/null +++ b/SW.Bitween.MsSql/Migrations/20260217150741_ApiGateWayAndGlobalValues.cs @@ -0,0 +1,120 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SW.Bitween.MsSql.Migrations +{ + /// + public partial class ApiGateWayAndGlobalValues : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AdapterProperties", + table: "Partners", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.CreateTable( + name: "ApiGateways", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + UrlName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + CreatedOn = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiGateways", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GlobalAdapterValuesSets", + columns: table => new + { + Id = table.Column(type: "varchar(200)", unicode: false, maxLength: 200, nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true), + Values = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GlobalAdapterValuesSets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiGatewayPartners", + columns: table => new + { + ApiGatewayId = table.Column(type: "int", nullable: false), + PartnerId = table.Column(type: "int", nullable: false), + SubscriptionId = table.Column(type: "int", nullable: false), + CreatedOn = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiGatewayPartners", x => new { x.ApiGatewayId, x.PartnerId, x.SubscriptionId }); + table.ForeignKey( + name: "FK_ApiGatewayPartners_ApiGateways_ApiGatewayId", + column: x => x.ApiGatewayId, + principalTable: "ApiGateways", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ApiGatewayPartners_Partners_PartnerId", + column: x => x.PartnerId, + principalTable: "Partners", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ApiGatewayPartners_Subscriptions_SubscriptionId", + column: x => x.SubscriptionId, + principalTable: "Subscriptions", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiGatewayPartners_PartnerId", + table: "ApiGatewayPartners", + column: "PartnerId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiGatewayPartners_SubscriptionId", + table: "ApiGatewayPartners", + column: "SubscriptionId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiGateways_UrlName", + table: "ApiGateways", + column: "UrlName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiGatewayPartners"); + + migrationBuilder.DropTable( + name: "GlobalAdapterValuesSets"); + + migrationBuilder.DropTable( + name: "ApiGateways"); + + migrationBuilder.DropColumn( + name: "AdapterProperties", + table: "Partners"); + } + } +} diff --git a/SW.Bitween.MsSql/Migrations/BitweenDbContextModelSnapshot.cs b/SW.Bitween.MsSql/Migrations/BitweenDbContextModelSnapshot.cs index aca44dbd..d8c13c42 100644 --- a/SW.Bitween.MsSql/Migrations/BitweenDbContextModelSnapshot.cs +++ b/SW.Bitween.MsSql/Migrations/BitweenDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.12") + .HasAnnotation("ProductVersion", "8.0.23") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -211,6 +211,94 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("DocumentTrail"); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UrlName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("UrlName") + .IsUnique(); + + b.ToTable("ApiGateways", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.Property("ApiGatewayId") + .HasColumnType("int"); + + b.Property("PartnerId") + .HasColumnType("int"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("ApiGatewayId", "PartnerId", "SubscriptionId"); + + b.HasIndex("PartnerId"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("ApiGatewayPartners", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.GlobalAdapterValuesSet", b => + { + b.Property("Id") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Values") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GlobalAdapterValuesSets", (string)null); + }); + modelBuilder.Entity("SW.Bitween.Domain.Notifier", b => { b.Property("Id") @@ -291,6 +379,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + b.Property("AdapterProperties") + .HasColumnType("nvarchar(max)"); + b.Property("Name") .IsRequired() .HasMaxLength(200) @@ -800,6 +891,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Document"); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.HasOne("SW.Bitween.Domain.Gateway.ApiGateway", "ApiGateway") + .WithMany("Partners") + .HasForeignKey("ApiGatewayId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Partner", "Partner") + .WithMany() + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ApiGateway"); + + b.Navigation("Partner"); + + b.Navigation("Subscription"); + }); + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => { b.OwnsMany("SW.Bitween.Domain.ApiCredential", "ApiCredentials", b1 => @@ -971,6 +1089,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Navigation("Partners"); + }); + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => { b.Navigation("Subscriptions"); diff --git a/SW.Bitween.MySql/Migrations/20260217151131_ApiGateWayAndGlobalValues.Designer.cs b/SW.Bitween.MySql/Migrations/20260217151131_ApiGateWayAndGlobalValues.Designer.cs new file mode 100644 index 00000000..efc157e5 --- /dev/null +++ b/SW.Bitween.MySql/Migrations/20260217151131_ApiGateWayAndGlobalValues.Designer.cs @@ -0,0 +1,1104 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SW.Bitween; + +#nullable disable + +namespace SW.Bitween.MySql.Migrations +{ + [DbContext(typeof(BitweenDbContext))] + [Migration("20260217151131_ApiGateWayAndGlobalValues")] + partial class ApiGateWayAndGlobalValues + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.23") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("EmailProvider") + .HasColumnType("tinyint unsigned"); + + b.Property("LoginMethods") + .HasColumnType("tinyint unsigned"); + + b.Property("ModifiedBy") + .HasColumnType("longtext"); + + b.Property("ModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("Phone") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("varchar(20)"); + + b.Property("Role") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.ToTable("Accounts", (string)null); + + b.HasData( + new + { + Id = 9999, + CreatedOn = new DateTime(2021, 12, 31, 22, 0, 0, 0, DateTimeKind.Utc), + Deleted = false, + Disabled = false, + DisplayName = "Admin", + Email = "admin@Bitween.systems", + EmailProvider = (byte)0, + LoginMethods = (byte)2, + Password = "$SWHASH$V1$10000$VQCi48eitH4Ml5juvBMOFZrMdQwBbhuIQVXe6RR7qJdDF2bJ", + Role = 0 + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.RefreshToken", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LoginMethod") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("RefreshTokens", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Document", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("BusEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("BusMessageTypeName") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("DisregardsUnfilteredMessages") + .HasColumnType("tinyint(1)"); + + b.Property("DocumentFormat") + .HasColumnType("int"); + + b.Property("DuplicateInterval") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(false) + .HasColumnType("varchar(100)"); + + b.Property("PromotedProperties") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("BusMessageTypeName") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Documents", (string)null); + + b.HasData( + new + { + Id = 10001, + BusEnabled = false, + DocumentFormat = 0, + DuplicateInterval = 0, + Name = "Aggregation Document", + PromotedProperties = "{}" + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.DocumentTrail", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("DocumentId") + .HasColumnType("int"); + + b.Property("StateAfter") + .HasColumnType("longtext"); + + b.Property("StateBefore") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CreatedOn"); + + b.HasIndex("DocumentId"); + + b.ToTable("DocumentTrail"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("ModifiedBy") + .HasColumnType("longtext"); + + b.Property("ModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UrlName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("UrlName") + .IsUnique(); + + b.ToTable("ApiGateways", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.Property("ApiGatewayId") + .HasColumnType("int"); + + b.Property("PartnerId") + .HasColumnType("int"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("ModifiedBy") + .HasColumnType("longtext"); + + b.Property("ModifiedOn") + .HasColumnType("datetime(6)"); + + b.HasKey("ApiGatewayId", "PartnerId", "SubscriptionId"); + + b.HasIndex("PartnerId"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("ApiGatewayPartners", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.GlobalAdapterValuesSet", b => + { + b.Property("Id") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("Values") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("GlobalAdapterValuesSets", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Notifier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("HandlerId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("HandlerProperties") + .HasColumnType("longtext"); + + b.Property("Inactive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RunOnBadResult") + .HasColumnType("tinyint(1)"); + + b.Property("RunOnFailedResult") + .HasColumnType("tinyint(1)"); + + b.Property("RunOnSubscriptions") + .HasColumnType("longtext"); + + b.Property("RunOnSuccessfulResult") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Notifiers", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.OnHoldXchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BadData") + .HasColumnType("tinyint(1)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("FileName") + .HasColumnType("longtext"); + + b.Property("References") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("OnHoldXchanges", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdapterProperties") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Partners", (string)null); + + b.HasData( + new + { + Id = 1, + Name = "SYSTEM" + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AggregateOn") + .HasColumnType("datetime(6)"); + + b.Property("AggregationForId") + .HasColumnType("int"); + + b.Property("AggregationTarget") + .HasColumnType("tinyint unsigned"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ConsecutiveFailures") + .HasColumnType("int"); + + b.Property("DocumentFilter") + .HasColumnType("longtext"); + + b.Property("DocumentId") + .HasColumnType("int"); + + b.Property("HandlerId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("HandlerProperties") + .HasColumnType("longtext"); + + b.Property("Inactive") + .HasColumnType("tinyint(1)"); + + b.Property("IsRunning") + .HasColumnType("tinyint(1)"); + + b.Property("LastException") + .HasColumnType("longtext"); + + b.Property("MapperId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("MapperProperties") + .HasColumnType("longtext"); + + b.Property("MatchExpression") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PartnerId") + .HasColumnType("int"); + + b.Property("PausedOn") + .HasColumnType("datetime(6)"); + + b.Property("ReceiveOn") + .HasColumnType("datetime(6)"); + + b.Property("ReceiverId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("ReceiverProperties") + .HasColumnType("longtext"); + + b.Property("ResponseMessageTypeName") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("ResponseSubscriptionId") + .HasColumnType("int"); + + b.Property("Temporary") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("ValidatorId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("ValidatorProperties") + .HasColumnType("longtext"); + + b.Property("WorkGroupId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AggregationForId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DocumentId"); + + b.HasIndex("PartnerId"); + + b.HasIndex("ResponseSubscriptionId"); + + b.HasIndex("WorkGroupId"); + + b.ToTable("Subscriptions", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("varchar(255)"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ModifiedBy") + .HasColumnType("longtext"); + + b.Property("ModifiedOn") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("SubscriptionCategory"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionTrail", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("StateAfter") + .HasColumnType("longtext"); + + b.Property("StateBefore") + .HasColumnType("longtext"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedOn"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("SubscriptionTrail"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.WorkGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BusMessageName") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(false) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("Options") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("WorkGroup"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Xchange", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("CorrelationId") + .HasColumnType("longtext"); + + b.Property("DocumentId") + .HasColumnType("int"); + + b.Property("HandlerId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("HandlerProperties") + .HasColumnType("longtext"); + + b.Property("InputContentType") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("InputHash") + .IsRequired() + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("InputName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("InputSize") + .HasColumnType("int"); + + b.Property("MapperId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("MapperProperties") + .HasColumnType("longtext"); + + b.Property("References") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ResponseMessageTypeName") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b.Property("ResponseSubscriptionId") + .HasColumnType("int"); + + b.Property("RetryFor") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("StartedOn") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("InputHash"); + + b.HasIndex("RetryFor"); + + b.HasIndex("StartedOn"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("Xchanges", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeAggregation", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("AggregatedOn") + .HasColumnType("datetime(6)"); + + b.Property("AggregationXchangeId") + .IsRequired() + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AggregationXchangeId"); + + b.ToTable("XchangeAggregations", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeDelivery", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("DeliveredOn") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("DeliveredOn"); + + b.ToTable("XchangeDeliveries", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Exception") + .HasColumnType("longtext"); + + b.Property("FinishedOn") + .HasColumnType("datetime(6)"); + + b.Property("NotifierId") + .HasColumnType("int"); + + b.Property("NotifierName") + .HasColumnType("longtext"); + + b.Property("Success") + .HasColumnType("tinyint(1)"); + + b.Property("XchangeId") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.ToTable("XchangeNotifications", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangePromotedProperties", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("Hits") + .HasMaxLength(2000) + .IsUnicode(false) + .HasColumnType("varchar(2000)"); + + b.Property("Properties") + .HasColumnType("longtext"); + + b.Property("PropertiesRaw") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("PropertiesRaw"); + + b.ToTable("XchangePromotedProperties", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeResult", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("Exception") + .HasColumnType("longtext"); + + b.Property("FinishedOn") + .HasColumnType("datetime(6)"); + + b.Property("OutputBad") + .HasColumnType("tinyint(1)"); + + b.Property("OutputContentType") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("OutputHash") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("OutputName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OutputSize") + .HasColumnType("int"); + + b.Property("ResponseBad") + .HasColumnType("tinyint(1)"); + + b.Property("ResponseContentType") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("ResponseHash") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("varchar(50)"); + + b.Property("ResponseName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ResponseSize") + .HasColumnType("int"); + + b.Property("ResponseXchangeId") + .HasColumnType("longtext"); + + b.Property("Success") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("XchangeResults", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.RunFlagUpdater+RunningResult", b => + { + b.Property("IsRunning") + .HasColumnType("tinyint(1)"); + + b.ToTable((string)null); + + b.ToView(null, (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.RefreshToken", b => + { + b.HasOne("SW.Bitween.Domain.Accounts.Account", null) + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.DocumentTrail", b => + { + b.HasOne("SW.Bitween.Domain.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.HasOne("SW.Bitween.Domain.Gateway.ApiGateway", "ApiGateway") + .WithMany("Partners") + .HasForeignKey("ApiGatewayId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Partner", "Partner") + .WithMany() + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ApiGateway"); + + b.Navigation("Partner"); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.OwnsMany("SW.Bitween.Domain.ApiCredential", "ApiCredentials", b1 => + { + b1.Property("PartnerId") + .HasColumnType("int"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b1.Property("Id")); + + b1.Property("Key") + .IsRequired() + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("varchar(500)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b1.HasKey("PartnerId", "Id"); + + b1.HasIndex("Key") + .IsUnique(); + + b1.ToTable("PartnerApiCredentials", (string)null); + + b1.WithOwner() + .HasForeignKey("PartnerId"); + + b1.HasData( + new + { + PartnerId = 1, + Id = 1, + Key = "7facc758283844b49cc4ffd26a75b1de", + Name = "default" + }); + }); + + b.Navigation("ApiCredentials"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Subscription", b => + { + b.HasOne("SW.Bitween.Domain.Subscription", null) + .WithMany() + .HasForeignKey("AggregationForId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("FK_Subscriptions_AggFor"); + + b.HasOne("SW.Bitween.Domain.SubscriptionCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId"); + + b.HasOne("SW.Bitween.Domain.Document", null) + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Partner", null) + .WithMany("Subscriptions") + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SW.Bitween.Domain.Subscription", null) + .WithMany() + .HasForeignKey("ResponseSubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("FK_Subscriptions_RespSub"); + + b.HasOne("SW.Bitween.Domain.WorkGroup", "WorkGroup") + .WithMany() + .HasForeignKey("WorkGroupId"); + + b.OwnsMany("SW.Bitween.Domain.Schedule", "Schedules", b1 => + { + b1.Property("SubscriptionId") + .HasColumnType("int"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b1.Property("Id")); + + b1.Property("Backwards") + .HasColumnType("tinyint(1)"); + + b1.Property("On") + .HasColumnType("bigint"); + + b1.Property("Recurrence") + .HasColumnType("tinyint unsigned"); + + b1.HasKey("SubscriptionId", "Id"); + + b1.ToTable("SubscriptionSchedules", (string)null); + + b1.WithOwner() + .HasForeignKey("SubscriptionId"); + }); + + b.Navigation("Category"); + + b.Navigation("Schedules"); + + b.Navigation("WorkGroup"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionTrail", b => + { + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Xchange", b => + { + b.HasOne("SW.Bitween.Domain.Document", null) + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeAggregation", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeAggregation", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeDelivery", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeDelivery", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangePromotedProperties", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangePromotedProperties", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeResult", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeResult", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Navigation("Partners"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.Navigation("Subscriptions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SW.Bitween.MySql/Migrations/20260217151131_ApiGateWayAndGlobalValues.cs b/SW.Bitween.MySql/Migrations/20260217151131_ApiGateWayAndGlobalValues.cs new file mode 100644 index 00000000..8a0bb921 --- /dev/null +++ b/SW.Bitween.MySql/Migrations/20260217151131_ApiGateWayAndGlobalValues.cs @@ -0,0 +1,134 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SW.Bitween.MySql.Migrations +{ + /// + public partial class ApiGateWayAndGlobalValues : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AdapterProperties", + table: "Partners", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ApiGateways", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + UrlName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedOn = table.Column(type: "datetime(6)", nullable: false), + CreatedBy = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ModifiedOn = table.Column(type: "datetime(6)", nullable: true), + ModifiedBy = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_ApiGateways", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "GlobalAdapterValuesSets", + columns: table => new + { + Id = table.Column(type: "varchar(200)", unicode: false, maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Name = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Values = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_GlobalAdapterValuesSets", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ApiGatewayPartners", + columns: table => new + { + ApiGatewayId = table.Column(type: "int", nullable: false), + PartnerId = table.Column(type: "int", nullable: false), + SubscriptionId = table.Column(type: "int", nullable: false), + CreatedOn = table.Column(type: "datetime(6)", nullable: false), + CreatedBy = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ModifiedOn = table.Column(type: "datetime(6)", nullable: true), + ModifiedBy = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_ApiGatewayPartners", x => new { x.ApiGatewayId, x.PartnerId, x.SubscriptionId }); + table.ForeignKey( + name: "FK_ApiGatewayPartners_ApiGateways_ApiGatewayId", + column: x => x.ApiGatewayId, + principalTable: "ApiGateways", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ApiGatewayPartners_Partners_PartnerId", + column: x => x.PartnerId, + principalTable: "Partners", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ApiGatewayPartners_Subscriptions_SubscriptionId", + column: x => x.SubscriptionId, + principalTable: "Subscriptions", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_ApiGatewayPartners_PartnerId", + table: "ApiGatewayPartners", + column: "PartnerId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiGatewayPartners_SubscriptionId", + table: "ApiGatewayPartners", + column: "SubscriptionId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiGateways_UrlName", + table: "ApiGateways", + column: "UrlName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiGatewayPartners"); + + migrationBuilder.DropTable( + name: "GlobalAdapterValuesSets"); + + migrationBuilder.DropTable( + name: "ApiGateways"); + + migrationBuilder.DropColumn( + name: "AdapterProperties", + table: "Partners"); + } + } +} diff --git a/SW.Bitween.MySql/Migrations/BitweenDbContextModelSnapshot.cs b/SW.Bitween.MySql/Migrations/BitweenDbContextModelSnapshot.cs index c9560dbd..b40d5d53 100644 --- a/SW.Bitween.MySql/Migrations/BitweenDbContextModelSnapshot.cs +++ b/SW.Bitween.MySql/Migrations/BitweenDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.12") + .HasAnnotation("ProductVersion", "8.0.23") .HasAnnotation("Relational:MaxIdentifierLength", 64); MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); @@ -209,6 +209,94 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("DocumentTrail"); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("ModifiedBy") + .HasColumnType("longtext"); + + b.Property("ModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UrlName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("UrlName") + .IsUnique(); + + b.ToTable("ApiGateways", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.Property("ApiGatewayId") + .HasColumnType("int"); + + b.Property("PartnerId") + .HasColumnType("int"); + + b.Property("SubscriptionId") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("ModifiedBy") + .HasColumnType("longtext"); + + b.Property("ModifiedOn") + .HasColumnType("datetime(6)"); + + b.HasKey("ApiGatewayId", "PartnerId", "SubscriptionId"); + + b.HasIndex("PartnerId"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("ApiGatewayPartners", (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.GlobalAdapterValuesSet", b => + { + b.Property("Id") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("varchar(200)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("Values") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("GlobalAdapterValuesSets", (string)null); + }); + modelBuilder.Entity("SW.Bitween.Domain.Notifier", b => { b.Property("Id") @@ -289,6 +377,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AdapterProperties") + .HasColumnType("longtext"); + b.Property("Name") .IsRequired() .HasMaxLength(200) @@ -797,6 +888,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Document"); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.HasOne("SW.Bitween.Domain.Gateway.ApiGateway", "ApiGateway") + .WithMany("Partners") + .HasForeignKey("ApiGatewayId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Partner", "Partner") + .WithMany() + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ApiGateway"); + + b.Navigation("Partner"); + + b.Navigation("Subscription"); + }); + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => { b.OwnsMany("SW.Bitween.Domain.ApiCredential", "ApiCredentials", b1 => @@ -968,6 +1086,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Navigation("Partners"); + }); + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => { b.Navigation("Subscriptions"); diff --git a/SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs b/SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs new file mode 100644 index 00000000..4f66dd04 --- /dev/null +++ b/SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs @@ -0,0 +1,160 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using DotLiquid; +using Newtonsoft.Json; +using SW.PrimitiveTypes; + +namespace SW.Bitween.NativeAdapters; + +public class HttpHandler : IInfolinkHandler +{ + private HttpMethod HttpMethodFromString(string method) + { + switch (method.ToLower()) + { + case "get": + return HttpMethod.Get; + case "delete": + return HttpMethod.Delete; + case "put": + return HttpMethod.Put; + default: + return HttpMethod.Post; + } + } + + private readonly HttpHandlerInput _options; + + public HttpHandler(HttpHandlerInput options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public async Task Handle(XchangeFile xchangeFile) + { + HttpClient client = new HttpClient(); + if (_options.AuthType == "ApiKey") + client.DefaultRequestHeaders.Add("ApiKey", _options.ApiKey); + else if (_options.AuthType == "Bearer") + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", _options.LoginPassword); + else if (_options.AuthType == "Basic") + { + string credentials = + Convert.ToBase64String( + Encoding.ASCII.GetBytes(_options.LoginUsername + ":" + _options.LoginPassword)); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); + } + else if (_options.AuthType == "Login") + { + string loginJson = JsonConvert.SerializeObject(new UserLoginModel() + { + Email = _options.LoginUsername, + Password = _options.LoginPassword + }); + HttpResponseMessage loginResponse = await client.PostAsync(new Uri(_options.LoginUrl!), + new StringContent(loginJson, Encoding.UTF8, "application/json")); + loginResponse.EnsureSuccessStatusCode(); + if (loginResponse.StatusCode != HttpStatusCode.OK) + throw new Exception(loginResponse.StatusCode.ToString()); + string rs = await loginResponse.Content.ReadAsStringAsync(); + LoginResponse? rsDeserialized = JsonConvert.DeserializeObject(rs); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", rsDeserialized?.Jwt); + } + else if (_options.AuthType == "OAuth2") + { + var oathRequest = new HttpRequestMessage(HttpMethod.Post, _options.LoginUrl); + var oauthContentDictionary = new List>(); + oauthContentDictionary.Add(new("client_id", _options.ClientId!)); + oauthContentDictionary.Add(new("client_secret", _options.ClientSecret!)); + oauthContentDictionary.Add(new("grant_type", "client_credentials")); + var oauthContent = new FormUrlEncodedContent(oauthContentDictionary); + oathRequest.Content = oauthContent; + var oauthResponse = await client.SendAsync(oathRequest); + var res = await oauthResponse.Content.ReadAsStringAsync(); + var resDeserialized = JsonConvert.DeserializeObject(res); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", resDeserialized?.access_token); + } + + string requestBody = xchangeFile.Data; + if (string.IsNullOrEmpty(requestBody)) + requestBody = _options.DefaultRequest ?? string.Empty; + string str = _options.ContentType.ToLower(); + HttpContent content; + MultipartFormDataContent multipartTmp; + byte[] fileContent; + switch (str) + { + case "application/x-www-form-urlencoded": + content = new FormUrlEncodedContent( + JsonConvert.DeserializeObject>(requestBody) + ?? new Dictionary()); + break; + case "multipart/form-data": + multipartTmp = new MultipartFormDataContent(); + fileContent = Encoding.UTF8.GetBytes(requestBody); + multipartTmp.Add(new ByteArrayContent(fileContent), "file", xchangeFile.Filename ?? "file"); + content = multipartTmp; + break; + case "application/json": + content = new StringContent(requestBody, Encoding.UTF8, "application/json"); + break; + default: + content = new StringContent(requestBody, Encoding.UTF8, _options.ContentType); + break; + } + + Uri uri; + if (!string.IsNullOrEmpty(xchangeFile.Data) && _options.Url.Contains("{{")) + { + Template parsedTemplate = Template.Parse(_options.Url); + IDictionary obj = + JsonConvert.DeserializeObject>(xchangeFile.Data, + new DictionaryConverter()) ?? new Dictionary(); + Hash jsonHash = Hash.FromDictionary(obj); + uri = new Uri(parsedTemplate.Render(jsonHash)); + } + else + uri = new Uri(_options.Url); + + var httpMethod = HttpMethodFromString(_options.Verb); + HttpRequestMessage request = new HttpRequestMessage() + { + RequestUri = uri, + Method = httpMethod, + Content = httpMethod == HttpMethod.Get ? null : content + }; + string? headers1 = _options.Headers; + IEnumerable>? headers = headers1 != null + ? (headers1.Split(',')).Select((Func>)(h => + { + string[] strArray = h.Split(':'); + return new KeyValuePair(strArray[0], strArray[1]); + })) + : null; + if (headers != null) + { + foreach (KeyValuePair keyValuePair1 in headers) + { + KeyValuePair keyValuePair = keyValuePair1; + request.Headers.Add(keyValuePair.Key, keyValuePair.Value); + } + } + + if (!string.IsNullOrEmpty(_options.CorrelationId)) + request.Headers.Add("request-context-correlation-id", _options.CorrelationId); + HttpResponseMessage response = await client.SendAsync(request); + if (response.StatusCode < HttpStatusCode.OK || response.StatusCode >= HttpStatusCode.InternalServerError) + throw new Exception(response.StatusCode.ToString()); + string resp = await response.Content.ReadAsStringAsync(); + XchangeFile xchangeFile1 = response.StatusCode < HttpStatusCode.BadRequest + ? new XchangeFile(resp) + : new XchangeFile(resp, badData: true); + return xchangeFile1; + } + + +} \ No newline at end of file diff --git a/SW.Bitween.NativeAdapters/HttpHandler/HttpHandlerInput.cs b/SW.Bitween.NativeAdapters/HttpHandler/HttpHandlerInput.cs new file mode 100644 index 00000000..c69fc53b --- /dev/null +++ b/SW.Bitween.NativeAdapters/HttpHandler/HttpHandlerInput.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace SW.Bitween.NativeAdapters; + +public class HttpHandlerInput +{ + public string? AuthType { get; set; } + public string? ApiKey { get; set; } + public string? LoginUrl { get; set; } + + [DefaultValue("post")] + public string Verb { get; set; } = "post"; + + public string? LoginUsername { get; set; } + public string? LoginPassword { get; set; } + + [Required] + public string Url { get; set; } = string.Empty; + + [DefaultValue("application/json")] + public string ContentType { get; set; } = "application/json"; + + public string? Headers { get; set; } + public string? CorrelationId { get; set; } + public string? ClientId { get; set; } + public string? ClientSecret { get; set; } + public string? DefaultRequest { get; set; } +} \ No newline at end of file diff --git a/SW.Bitween.NativeAdapters/HttpHandler/HttpHandlerModels.cs b/SW.Bitween.NativeAdapters/HttpHandler/HttpHandlerModels.cs new file mode 100644 index 00000000..623b5059 --- /dev/null +++ b/SW.Bitween.NativeAdapters/HttpHandler/HttpHandlerModels.cs @@ -0,0 +1,18 @@ +namespace SW.Bitween.NativeAdapters; + +public class UserLoginModel +{ + public string? Email { get; set; } + public string? Password { get; set; } +} + +public class LoginResponse +{ + public string? Jwt { get; set; } + public string? Refresh { get; set; } +} + +public class OAuth2Response +{ + public string? access_token { get; set; } +} diff --git a/SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj b/SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj new file mode 100644 index 00000000..f0d60c4b --- /dev/null +++ b/SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs b/SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs new file mode 100644 index 00000000..28edd394 --- /dev/null +++ b/SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs @@ -0,0 +1,157 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace SW.Bitween.NativeAdapters; + +public class DictionaryConverter : JsonConverter +{ + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + this.WriteValue(writer, value); + } + + private void WriteValue(JsonWriter writer, object? value) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + var t = JToken.FromObject(value); + switch (t.Type) + { + case JTokenType.Object: + this.WriteObject(writer, value); + break; + case JTokenType.Array: + this.WriteArray(writer, value); + break; + default: + writer.WriteValue(value); + break; + } + } + + private void WriteObject(JsonWriter writer, object value) + { + writer.WriteStartObject(); + var obj = value as IDictionary; + if (obj != null) + { + foreach (var kvp in obj) + { + writer.WritePropertyName(kvp.Key); + this.WriteValue(writer, kvp.Value); + } + } + writer.WriteEndObject(); + } + + private void WriteArray(JsonWriter writer, object value) + { + writer.WriteStartArray(); + var array = value as IEnumerable; + if (array != null) + { + foreach (var o in array) + { + this.WriteValue(writer, o); + } + } + writer.WriteEndArray(); + } + + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + return ReadValue(reader); + } + + private object? ReadValue(JsonReader reader) + { + while (reader.TokenType == JsonToken.Comment) + { + if (!reader.Read()) + throw new JsonSerializationException("Unexpected Token when converting IDictionary"); + } + + switch (reader.TokenType) + { + case JsonToken.StartObject: + return ReadObject(reader); + case JsonToken.StartArray: + return this.ReadArray(reader); + case JsonToken.Integer: + case JsonToken.Float: + case JsonToken.String: + case JsonToken.Boolean: + case JsonToken.Undefined: + case JsonToken.Null: + case JsonToken.Date: + case JsonToken.Bytes: + return reader.Value; + default: + throw new JsonSerializationException( + $"Unexpected token when converting IDictionary: {reader.TokenType}"); + } + } + + private object ReadArray(JsonReader reader) + { + IList list = new List(); + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.Comment: + break; + case JsonToken.EndArray: + return list; + default: + var v = ReadValue(reader); + if (v != null) + list.Add(v); + break; + } + } + + throw new JsonSerializationException("Unexpected end when reading IDictionary"); + } + + private object ReadObject(JsonReader reader) + { + var obj = new Dictionary(); + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + var propertyName = reader.Value?.ToString(); + if (propertyName == null) break; + + if (!reader.Read()) + { + throw new JsonSerializationException("Unexpected end when reading IDictionary"); + } + + var v = ReadValue(reader); + if (v != null) + obj[propertyName] = v; + break; + case JsonToken.Comment: + break; + case JsonToken.EndObject: + return obj; + } + } + + throw new JsonSerializationException("Unexpected end when reading IDictionary"); + } + + public override bool CanConvert(Type objectType) + { + return typeof(IDictionary).IsAssignableFrom(objectType); + } +} diff --git a/SW.Bitween.PgSql/BitweenDbContext.cs b/SW.Bitween.PgSql/BitweenDbContext.cs index a3a5cac6..fec2da8a 100644 --- a/SW.Bitween.PgSql/BitweenDbContext.cs +++ b/SW.Bitween.PgSql/BitweenDbContext.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using SW.EfCoreExtensions; using SW.Bitween.Domain; @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using SW.Bitween.Domain.Accounts; +using SW.Bitween.Domain.Gateway; namespace SW.Bitween.PgSql { @@ -86,6 +87,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) //b.ToTable("Partners"); b.Metadata.SetNavigationAccessMode(PropertyAccessMode.Field); b.Property(p => p.Name).IsRequired().HasMaxLength(200); + b.Property(p => p.AdapterProperties).HasColumnType("jsonb"); b.HasMany(p => p.Subscriptions).WithOne().IsRequired(false).HasForeignKey(p => p.PartnerId) .OnDelete(DeleteBehavior.Restrict); b.OwnsMany(p => p.ApiCredentials, apicred => @@ -114,6 +116,36 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity(ag => + { + ag.ToTable("api_gateway"); + ag.HasKey(i => i.Id); + ag.Property(i => i.Id).ValueGeneratedOnAdd(); + ag.Property(p => p.Name).IsRequired().HasMaxLength(200); + ag.Property(p => p.UrlName).IsRequired().HasMaxLength(200); + ag.HasIndex(p => p.UrlName).IsUnique(); + ag.HasMany(p => p.Partners).WithOne(p => p.ApiGateway).HasForeignKey(p => p.ApiGatewayId) + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity(agp => + { + agp.ToTable("api_gateway_partner"); + agp.HasKey(p => new { p.ApiGatewayId, p.PartnerId, p.SubscriptionId }); + agp.HasOne(p => p.ApiGateway).WithMany(p => p.Partners).HasForeignKey(p => p.ApiGatewayId) + .OnDelete(DeleteBehavior.Restrict); + agp.HasOne(p => p.Partner).WithMany().HasForeignKey(p => p.PartnerId) + .OnDelete(DeleteBehavior.Restrict); + agp.HasOne(p => p.Subscription).WithMany().HasForeignKey(p => p.SubscriptionId) + .IsRequired().OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity(gav => + { + gav.ToTable("global_adapter_values_set"); + gav.HasKey(i => i.Id); + gav.Property(p => p.Values).HasColumnType("jsonb"); + }); modelBuilder.Entity(b => { //b.ToTable("Subscriptions"); diff --git a/SW.Bitween.PgSql/DesignTimeDbContextFactory.cs b/SW.Bitween.PgSql/DesignTimeDbContextFactory.cs new file mode 100644 index 00000000..e69de29b diff --git a/SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.Designer.cs b/SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.Designer.cs new file mode 100644 index 00000000..72e3e041 --- /dev/null +++ b/SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.Designer.cs @@ -0,0 +1,1315 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SW.Bitween.Model; +using SW.Bitween.PgSql; + +#nullable disable + +namespace SW.Bitween.PgSql.Migrations +{ + [DbContext(typeof(BitweenDbContext))] + [Migration("20260217152930_ApiGateWayAndGlobalValues")] + partial class ApiGateWayAndGlobalValues + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("infolink") + .HasAnnotation("ProductVersion", "8.0.23") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("Disabled") + .HasColumnType("boolean") + .HasColumnName("disabled"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("display_name"); + + b.Property("Email") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("character varying(200)") + .HasColumnName("email"); + + b.Property("EmailProvider") + .HasColumnType("smallint") + .HasColumnName("email_provider"); + + b.Property("LoginMethods") + .HasColumnType("smallint") + .HasColumnName("login_methods"); + + b.Property("ModifiedBy") + .HasColumnType("text") + .HasColumnName("modified_by"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_on"); + + b.Property("Password") + .HasMaxLength(500) + .IsUnicode(false) + .HasColumnType("character varying(500)") + .HasColumnName("password"); + + b.Property("Phone") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character varying(20)") + .HasColumnName("phone"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.HasKey("Id") + .HasName("pk_accounts"); + + b.HasIndex("Email") + .IsUnique() + .HasDatabaseName("ix_accounts_email"); + + b.ToTable("Accounts", "infolink"); + + b.HasData( + new + { + Id = 9999, + CreatedOn = new DateTime(2021, 12, 31, 22, 0, 0, 0, DateTimeKind.Utc), + Deleted = false, + Disabled = false, + DisplayName = "Admin", + Email = "admin@Bitween.systems", + EmailProvider = (byte)0, + LoginMethods = (byte)2, + Password = "$SWHASH$V1$10000$VQCi48eitH4Ml5juvBMOFZrMdQwBbhuIQVXe6RR7qJdDF2bJ", + Role = 0 + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.RefreshToken", b => + { + b.Property("Id") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("character varying(50)") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("integer") + .HasColumnName("account_id"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("LoginMethod") + .HasColumnType("smallint") + .HasColumnName("login_method"); + + b.HasKey("Id") + .HasName("pk_refresh_tokens"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_refresh_tokens_account_id"); + + b.ToTable("RefreshTokens", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Document", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("BusEnabled") + .HasColumnType("boolean") + .HasColumnName("bus_enabled"); + + b.Property("BusMessageTypeName") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("bus_message_type_name"); + + b.Property("DisregardsUnfilteredMessages") + .HasColumnType("boolean") + .HasColumnName("disregards_unfiltered_messages"); + + b.Property("DocumentFormat") + .HasColumnType("integer") + .HasColumnName("document_format"); + + b.Property("DuplicateInterval") + .HasColumnType("integer") + .HasColumnName("duplicate_interval"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.Property("PromotedProperties") + .HasColumnType("jsonb") + .HasColumnName("promoted_properties"); + + b.HasKey("Id") + .HasName("pk_document"); + + b.HasIndex("BusMessageTypeName") + .IsUnique() + .HasDatabaseName("ix_document_bus_message_type_name"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_document_name"); + + b.ToTable("document", "infolink"); + + b.HasData( + new + { + Id = 10001, + BusEnabled = false, + DocumentFormat = 0, + DuplicateInterval = 0, + Name = "Aggregation Document", + PromotedProperties = "{}" + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.DocumentTrail", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("Code") + .HasColumnType("integer") + .HasColumnName("code"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("DocumentId") + .HasColumnType("integer") + .HasColumnName("document_id"); + + b.Property("StateAfter") + .HasColumnType("text") + .HasColumnName("state_after"); + + b.Property("StateBefore") + .HasColumnType("text") + .HasColumnName("state_before"); + + b.HasKey("Id") + .HasName("pk_document_trail"); + + b.HasIndex("CreatedOn") + .HasDatabaseName("ix_document_trail_created_on"); + + b.HasIndex("DocumentId") + .HasDatabaseName("ix_document_trail_document_id"); + + b.ToTable("document_trail", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("ModifiedBy") + .HasColumnType("text") + .HasColumnName("modified_by"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_on"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("UrlName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("url_name"); + + b.HasKey("Id") + .HasName("pk_api_gateway"); + + b.HasIndex("UrlName") + .IsUnique() + .HasDatabaseName("ix_api_gateway_url_name"); + + b.ToTable("api_gateway", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.Property("ApiGatewayId") + .HasColumnType("integer") + .HasColumnName("api_gateway_id"); + + b.Property("PartnerId") + .HasColumnType("integer") + .HasColumnName("partner_id"); + + b.Property("SubscriptionId") + .HasColumnType("integer") + .HasColumnName("subscription_id"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("ModifiedBy") + .HasColumnType("text") + .HasColumnName("modified_by"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_on"); + + b.HasKey("ApiGatewayId", "PartnerId", "SubscriptionId") + .HasName("pk_api_gateway_partner"); + + b.HasIndex("PartnerId") + .HasDatabaseName("ix_api_gateway_partner_partner_id"); + + b.HasIndex("SubscriptionId") + .HasDatabaseName("ix_api_gateway_partner_subscription_id"); + + b.ToTable("api_gateway_partner", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.GlobalAdapterValuesSet", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property>("Values") + .HasColumnType("jsonb") + .HasColumnName("values"); + + b.HasKey("Id") + .HasName("pk_global_adapter_values_set"); + + b.ToTable("global_adapter_values_set", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Notifier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("HandlerId") + .HasMaxLength(200) + .IsUnicode(false) + .HasColumnType("character varying(200)") + .HasColumnName("handler_id"); + + b.Property("HandlerProperties") + .HasColumnType("text") + .HasColumnName("handler_properties"); + + b.Property("Inactive") + .HasColumnType("boolean") + .HasColumnName("inactive"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.Property("RunOnBadResult") + .HasColumnType("boolean") + .HasColumnName("run_on_bad_result"); + + b.Property("RunOnFailedResult") + .HasColumnType("boolean") + .HasColumnName("run_on_failed_result"); + + b.Property("RunOnSubscriptions") + .HasColumnType("integer[]") + .HasColumnName("run_on_subscriptions"); + + b.Property("RunOnSuccessfulResult") + .HasColumnType("boolean") + .HasColumnName("run_on_successful_result"); + + b.HasKey("Id") + .HasName("pk_notifier"); + + b.ToTable("notifier", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.OnHoldXchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BadData") + .HasColumnType("boolean") + .HasColumnName("bad_data"); + + b.Property("Data") + .HasColumnType("text") + .HasColumnName("data"); + + b.Property("FileName") + .HasColumnType("text") + .HasColumnName("file_name"); + + b.Property("References") + .HasColumnType("text[]") + .HasColumnName("references"); + + b.Property("SubscriptionId") + .HasColumnType("integer") + .HasColumnName("subscription_id"); + + b.HasKey("Id") + .HasName("pk_on_hold_xchange"); + + b.HasIndex("SubscriptionId") + .HasDatabaseName("ix_on_hold_xchange_subscription_id"); + + b.ToTable("on_hold_xchange", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property>("AdapterProperties") + .HasColumnType("jsonb") + .HasColumnName("adapter_properties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_partner"); + + b.ToTable("partner", "infolink"); + + b.HasData( + new + { + Id = 1, + Name = "SYSTEM" + }); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AggregateOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("aggregate_on"); + + b.Property("AggregationForId") + .HasColumnType("integer") + .HasColumnName("aggregation_for_id"); + + b.Property("AggregationTarget") + .HasColumnType("smallint") + .HasColumnName("aggregation_target"); + + b.Property("CategoryId") + .HasColumnType("integer") + .HasColumnName("category_id"); + + b.Property("ConsecutiveFailures") + .HasColumnType("integer") + .HasColumnName("consecutive_failures"); + + b.Property>("DocumentFilter") + .HasColumnType("jsonb") + .HasColumnName("document_filter"); + + b.Property("DocumentId") + .HasColumnType("integer") + .HasColumnName("document_id"); + + b.Property("HandlerId") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("handler_id"); + + b.Property>("HandlerProperties") + .HasColumnType("jsonb") + .HasColumnName("handler_properties"); + + b.Property("Inactive") + .HasColumnType("boolean") + .HasColumnName("inactive"); + + b.Property("IsRunning") + .HasColumnType("boolean") + .HasColumnName("is_running"); + + b.Property("LastException") + .HasColumnType("text") + .HasColumnName("last_exception"); + + b.Property("MapperId") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("mapper_id"); + + b.Property>("MapperProperties") + .HasColumnType("jsonb") + .HasColumnName("mapper_properties"); + + b.Property("MatchExpression") + .HasColumnType("text") + .HasColumnName("match_expression"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.Property("PartnerId") + .HasColumnType("integer") + .HasColumnName("partner_id"); + + b.Property("PausedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("paused_on"); + + b.Property("ReceiveOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("receive_on"); + + b.Property("ReceiverId") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("receiver_id"); + + b.Property>("ReceiverProperties") + .HasColumnType("jsonb") + .HasColumnName("receiver_properties"); + + b.Property("ResponseMessageTypeName") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("response_message_type_name"); + + b.Property("ResponseSubscriptionId") + .HasColumnType("integer") + .HasColumnName("response_subscription_id"); + + b.Property("Temporary") + .HasColumnType("boolean") + .HasColumnName("temporary"); + + b.Property("Type") + .HasColumnType("smallint") + .HasColumnName("type"); + + b.Property("ValidatorId") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("validator_id"); + + b.Property>("ValidatorProperties") + .HasColumnType("jsonb") + .HasColumnName("validator_properties"); + + b.Property("WorkGroupId") + .HasColumnType("integer") + .HasColumnName("work_group_id"); + + b.HasKey("Id") + .HasName("pk_subscription"); + + b.HasIndex("AggregationForId") + .HasDatabaseName("ix_subscription_aggregation_for_id"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_subscription_category_id"); + + b.HasIndex("DocumentId") + .HasDatabaseName("ix_subscription_document_id"); + + b.HasIndex("PartnerId") + .HasDatabaseName("ix_subscription_partner_id"); + + b.HasIndex("ResponseSubscriptionId") + .HasDatabaseName("ix_subscription_response_subscription_id"); + + b.HasIndex("WorkGroupId") + .HasDatabaseName("ix_subscription_work_group_id"); + + b.ToTable("subscription", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("text") + .HasColumnName("code"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedBy") + .HasColumnType("text") + .HasColumnName("modified_by"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_on"); + + b.HasKey("Id") + .HasName("pk_subscription_category"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("ix_subscription_category_code"); + + b.ToTable("subscription_category", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionTrail", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("id"); + + b.Property("Code") + .HasColumnType("integer") + .HasColumnName("code"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("StateAfter") + .HasColumnType("text") + .HasColumnName("state_after"); + + b.Property("StateBefore") + .HasColumnType("text") + .HasColumnName("state_before"); + + b.Property("SubscriptionId") + .HasColumnType("integer") + .HasColumnName("subscription_id"); + + b.HasKey("Id") + .HasName("pk_subscription_trail"); + + b.HasIndex("CreatedOn") + .HasDatabaseName("ix_subscription_trail_created_on"); + + b.HasIndex("SubscriptionId") + .HasDatabaseName("ix_subscription_trail_subscription_id"); + + b.ToTable("subscription_trail", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.WorkGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BusMessageName") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(false) + .HasColumnType("character varying(100)") + .HasColumnName("bus_message_name"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Options") + .HasColumnType("jsonb") + .HasColumnName("options"); + + b.HasKey("Id") + .HasName("pk_work_group"); + + b.ToTable("work_group", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Xchange", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("id"); + + b.Property("CorrelationId") + .HasColumnType("text") + .HasColumnName("correlation_id"); + + b.Property("DocumentId") + .HasColumnType("integer") + .HasColumnName("document_id"); + + b.Property("HandlerId") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("handler_id"); + + b.Property>("HandlerProperties") + .HasColumnType("jsonb") + .HasColumnName("handler_properties"); + + b.Property("InputContentType") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("input_content_type"); + + b.Property("InputHash") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("input_hash"); + + b.Property("InputName") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("input_name"); + + b.Property("InputSize") + .HasColumnType("integer") + .HasColumnName("input_size"); + + b.Property("MapperId") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("mapper_id"); + + b.Property>("MapperProperties") + .HasColumnType("jsonb") + .HasColumnName("mapper_properties"); + + b.Property("References") + .HasColumnType("text[]") + .HasColumnName("references"); + + b.Property("ResponseMessageTypeName") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("response_message_type_name"); + + b.Property("ResponseSubscriptionId") + .HasColumnType("integer") + .HasColumnName("response_subscription_id"); + + b.Property("RetryFor") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("retry_for"); + + b.Property("StartedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_on"); + + b.Property("SubscriptionId") + .HasColumnType("integer") + .HasColumnName("subscription_id"); + + b.HasKey("Id") + .HasName("pk_xchange"); + + b.HasIndex("DocumentId") + .HasDatabaseName("ix_xchange_document_id"); + + b.HasIndex("InputHash") + .HasDatabaseName("ix_xchange_input_hash"); + + b.HasIndex("RetryFor") + .HasDatabaseName("ix_xchange_retry_for"); + + b.HasIndex("StartedOn") + .HasDatabaseName("ix_xchange_started_on"); + + b.HasIndex("SubscriptionId") + .HasDatabaseName("ix_xchange_subscription_id"); + + b.ToTable("xchange", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeAggregation", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("id"); + + b.Property("AggregatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("aggregated_on"); + + b.Property("AggregationXchangeId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("aggregation_xchange_id"); + + b.HasKey("Id") + .HasName("pk_xchange_aggregation"); + + b.HasIndex("AggregationXchangeId") + .HasDatabaseName("ix_xchange_aggregation_aggregation_xchange_id"); + + b.ToTable("xchange_aggregation", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeDelivery", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("id"); + + b.Property("DeliveredOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("delivered_on"); + + b.HasKey("Id") + .HasName("pk_xchange_delivery"); + + b.HasIndex("DeliveredOn") + .HasDatabaseName("ix_xchange_delivery_delivered_on"); + + b.ToTable("xchange_delivery", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exception") + .HasColumnType("text") + .HasColumnName("exception"); + + b.Property("FinishedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("finished_on"); + + b.Property("NotifierId") + .HasColumnType("integer") + .HasColumnName("notifier_id"); + + b.Property("NotifierName") + .HasColumnType("text") + .HasColumnName("notifier_name"); + + b.Property("Success") + .HasColumnType("boolean") + .HasColumnName("success"); + + b.Property("XchangeId") + .HasMaxLength(50) + .IsUnicode(false) + .HasColumnType("character varying(50)") + .HasColumnName("xchange_id"); + + b.HasKey("Id") + .HasName("pk_xchange_notification"); + + b.ToTable("xchange_notification", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangePromotedProperties", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("id"); + + b.Property("Hits") + .HasColumnType("integer[]") + .HasColumnName("hits"); + + b.Property>("Properties") + .HasColumnType("jsonb") + .HasColumnName("properties"); + + b.Property("PropertiesRaw") + .HasColumnType("text") + .HasColumnName("properties_raw"); + + b.HasKey("Id") + .HasName("pk_xchange_promoted_properties"); + + b.HasIndex("PropertiesRaw") + .HasDatabaseName("ix_xchange_promoted_properties_properties_raw"); + + b.ToTable("xchange_promoted_properties", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeResult", b => + { + b.Property("Id") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("id"); + + b.Property("Exception") + .HasColumnType("text") + .HasColumnName("exception"); + + b.Property("FinishedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("finished_on"); + + b.Property("OutputBad") + .HasColumnType("boolean") + .HasColumnName("output_bad"); + + b.Property("OutputContentType") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("output_content_type"); + + b.Property("OutputHash") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("output_hash"); + + b.Property("OutputName") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("output_name"); + + b.Property("OutputSize") + .HasColumnType("integer") + .HasColumnName("output_size"); + + b.Property("ResponseBad") + .HasColumnType("boolean") + .HasColumnName("response_bad"); + + b.Property("ResponseContentType") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("response_content_type"); + + b.Property("ResponseHash") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("response_hash"); + + b.Property("ResponseName") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("response_name"); + + b.Property("ResponseSize") + .HasColumnType("integer") + .HasColumnName("response_size"); + + b.Property("ResponseXchangeId") + .HasColumnType("text") + .HasColumnName("response_xchange_id"); + + b.Property("Success") + .HasColumnType("boolean") + .HasColumnName("success"); + + b.HasKey("Id") + .HasName("pk_xchange_result"); + + b.ToTable("xchange_result", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.RunFlagUpdater+RunningResult", b => + { + b.Property("IsRunning") + .HasColumnType("boolean") + .HasColumnName("is_running"); + + b.ToTable("running_result", "infolink"); + + b.ToView(null, (string)null); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Accounts.RefreshToken", b => + { + b.HasOne("SW.Bitween.Domain.Accounts.Account", null) + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_refresh_tokens_accounts_account_id"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.DocumentTrail", b => + { + b.HasOne("SW.Bitween.Domain.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_document_trail_document_document_id"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.HasOne("SW.Bitween.Domain.Gateway.ApiGateway", "ApiGateway") + .WithMany("Partners") + .HasForeignKey("ApiGatewayId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_api_gateway_partner_api_gateway_api_gateway_id"); + + b.HasOne("SW.Bitween.Domain.Partner", "Partner") + .WithMany() + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_api_gateway_partner_partner_partner_id"); + + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_api_gateway_partner_subscription_subscription_id"); + + b.Navigation("ApiGateway"); + + b.Navigation("Partner"); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.OwnsMany("SW.Bitween.Domain.ApiCredential", "ApiCredentials", b1 => + { + b1.Property("PartnerId") + .HasColumnType("integer") + .HasColumnName("partner_id"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("Key") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("key"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("name"); + + b1.HasKey("PartnerId", "Id") + .HasName("pk_partner_api_credential"); + + b1.HasIndex("Key") + .IsUnique() + .HasDatabaseName("ix_partner_api_credential_key"); + + b1.ToTable("partner_api_credential", "infolink"); + + b1.WithOwner() + .HasForeignKey("PartnerId") + .HasConstraintName("fk_partner_api_credential_partner_partner_id"); + + b1.HasData( + new + { + PartnerId = 1, + Id = 1, + Key = "7facc758283844b49cc4ffd26a75b1de", + Name = "default" + }); + }); + + b.Navigation("ApiCredentials"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Subscription", b => + { + b.HasOne("SW.Bitween.Domain.Subscription", null) + .WithMany() + .HasForeignKey("AggregationForId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_subscription_aggregation_for"); + + b.HasOne("SW.Bitween.Domain.SubscriptionCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_subscription_subscription_category_category_id"); + + b.HasOne("SW.Bitween.Domain.Document", null) + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_subscription_document_document_id"); + + b.HasOne("SW.Bitween.Domain.Partner", null) + .WithMany("Subscriptions") + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_subscription_partner_partner_id"); + + b.HasOne("SW.Bitween.Domain.Subscription", null) + .WithMany() + .HasForeignKey("ResponseSubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_subscription_response_subscriber"); + + b.HasOne("SW.Bitween.Domain.WorkGroup", "WorkGroup") + .WithMany() + .HasForeignKey("WorkGroupId") + .HasConstraintName("fk_subscription_work_group_work_group_id"); + + b.OwnsMany("SW.Bitween.Domain.Schedule", "Schedules", b1 => + { + b1.Property("SubscriptionId") + .HasColumnType("integer") + .HasColumnName("subscription_id"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("Backwards") + .HasColumnType("boolean") + .HasColumnName("backwards"); + + b1.Property("On") + .HasColumnType("bigint") + .HasColumnName("on"); + + b1.Property("Recurrence") + .HasColumnType("smallint") + .HasColumnName("recurrence"); + + b1.HasKey("SubscriptionId", "Id") + .HasName("pk_subscription_schedule"); + + b1.ToTable("subscription_schedule", "infolink"); + + b1.WithOwner() + .HasForeignKey("SubscriptionId") + .HasConstraintName("fk_subscription_schedule_subscription_subscription_id"); + }); + + b.Navigation("Category"); + + b.Navigation("Schedules"); + + b.Navigation("WorkGroup"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.SubscriptionTrail", b => + { + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_subscription_trail_subscription_subscription_id"); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Xchange", b => + { + b.HasOne("SW.Bitween.Domain.Document", null) + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_xchange_document_document_id"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeAggregation", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeAggregation", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xchange_aggregation_xchange_id"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeDelivery", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeDelivery", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xchange_delivery_xchange_id"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangePromotedProperties", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangePromotedProperties", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xchange_promoted_properties_xchange_id"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.XchangeResult", b => + { + b.HasOne("SW.Bitween.Domain.Xchange", null) + .WithOne() + .HasForeignKey("SW.Bitween.Domain.XchangeResult", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_xchange_result_xchange_id"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Navigation("Partners"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => + { + b.Navigation("Subscriptions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.cs b/SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.cs new file mode 100644 index 00000000..86f50999 --- /dev/null +++ b/SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace SW.Bitween.PgSql.Migrations +{ + /// + public partial class ApiGateWayAndGlobalValues : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn>( + name: "adapter_properties", + schema: "infolink", + table: "partner", + type: "jsonb", + nullable: true); + + migrationBuilder.CreateTable( + name: "api_gateway", + schema: "infolink", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + url_name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + created_on = table.Column(type: "timestamp with time zone", nullable: false), + created_by = table.Column(type: "text", nullable: true), + modified_on = table.Column(type: "timestamp with time zone", nullable: true), + modified_by = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_api_gateway", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "global_adapter_values_set", + schema: "infolink", + columns: table => new + { + id = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true), + values = table.Column>(type: "jsonb", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_global_adapter_values_set", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "api_gateway_partner", + schema: "infolink", + columns: table => new + { + api_gateway_id = table.Column(type: "integer", nullable: false), + partner_id = table.Column(type: "integer", nullable: false), + subscription_id = table.Column(type: "integer", nullable: false), + created_on = table.Column(type: "timestamp with time zone", nullable: false), + created_by = table.Column(type: "text", nullable: true), + modified_on = table.Column(type: "timestamp with time zone", nullable: true), + modified_by = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_api_gateway_partner", x => new { x.api_gateway_id, x.partner_id, x.subscription_id }); + table.ForeignKey( + name: "fk_api_gateway_partner_api_gateway_api_gateway_id", + column: x => x.api_gateway_id, + principalSchema: "infolink", + principalTable: "api_gateway", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_api_gateway_partner_partner_partner_id", + column: x => x.partner_id, + principalSchema: "infolink", + principalTable: "partner", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_api_gateway_partner_subscription_subscription_id", + column: x => x.subscription_id, + principalSchema: "infolink", + principalTable: "subscription", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "ix_api_gateway_url_name", + schema: "infolink", + table: "api_gateway", + column: "url_name", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_api_gateway_partner_partner_id", + schema: "infolink", + table: "api_gateway_partner", + column: "partner_id"); + + migrationBuilder.CreateIndex( + name: "ix_api_gateway_partner_subscription_id", + schema: "infolink", + table: "api_gateway_partner", + column: "subscription_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "api_gateway_partner", + schema: "infolink"); + + migrationBuilder.DropTable( + name: "global_adapter_values_set", + schema: "infolink"); + + migrationBuilder.DropTable( + name: "api_gateway", + schema: "infolink"); + + migrationBuilder.DropColumn( + name: "adapter_properties", + schema: "infolink", + table: "partner"); + } + } +} diff --git a/SW.Bitween.PgSql/Migrations/BitweenDbContextModelSnapshot.cs b/SW.Bitween.PgSql/Migrations/BitweenDbContextModelSnapshot.cs index fd12606c..2625c6c7 100644 --- a/SW.Bitween.PgSql/Migrations/BitweenDbContextModelSnapshot.cs +++ b/SW.Bitween.PgSql/Migrations/BitweenDbContextModelSnapshot.cs @@ -252,6 +252,115 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("document_trail", "infolink"); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("ModifiedBy") + .HasColumnType("text") + .HasColumnName("modified_by"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_on"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("UrlName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("url_name"); + + b.HasKey("Id") + .HasName("pk_api_gateway"); + + b.HasIndex("UrlName") + .IsUnique() + .HasDatabaseName("ix_api_gateway_url_name"); + + b.ToTable("api_gateway", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.Property("ApiGatewayId") + .HasColumnType("integer") + .HasColumnName("api_gateway_id"); + + b.Property("PartnerId") + .HasColumnType("integer") + .HasColumnName("partner_id"); + + b.Property("SubscriptionId") + .HasColumnType("integer") + .HasColumnName("subscription_id"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("ModifiedBy") + .HasColumnType("text") + .HasColumnName("modified_by"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_on"); + + b.HasKey("ApiGatewayId", "PartnerId", "SubscriptionId") + .HasName("pk_api_gateway_partner"); + + b.HasIndex("PartnerId") + .HasDatabaseName("ix_api_gateway_partner_partner_id"); + + b.HasIndex("SubscriptionId") + .HasDatabaseName("ix_api_gateway_partner_subscription_id"); + + b.ToTable("api_gateway_partner", "infolink"); + }); + + modelBuilder.Entity("SW.Bitween.Domain.GlobalAdapterValuesSet", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property>("Values") + .HasColumnType("jsonb") + .HasColumnName("values"); + + b.HasKey("Id") + .HasName("pk_global_adapter_values_set"); + + b.ToTable("global_adapter_values_set", "infolink"); + }); + modelBuilder.Entity("SW.Bitween.Domain.Notifier", b => { b.Property("Id") @@ -350,6 +459,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property>("AdapterProperties") + .HasColumnType("jsonb") + .HasColumnName("adapter_properties"); + b.Property("Name") .IsRequired() .HasMaxLength(200) @@ -960,6 +1073,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Document"); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGatewayPartner", b => + { + b.HasOne("SW.Bitween.Domain.Gateway.ApiGateway", "ApiGateway") + .WithMany("Partners") + .HasForeignKey("ApiGatewayId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_api_gateway_partner_api_gateway_api_gateway_id"); + + b.HasOne("SW.Bitween.Domain.Partner", "Partner") + .WithMany() + .HasForeignKey("PartnerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_api_gateway_partner_partner_partner_id"); + + b.HasOne("SW.Bitween.Domain.Subscription", "Subscription") + .WithMany() + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_api_gateway_partner_subscription_subscription_id"); + + b.Navigation("ApiGateway"); + + b.Navigation("Partner"); + + b.Navigation("Subscription"); + }); + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => { b.OwnsMany("SW.Bitween.Domain.ApiCredential", "ApiCredentials", b1 => @@ -1154,6 +1297,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasConstraintName("fk_xchange_result_xchange_id"); }); + modelBuilder.Entity("SW.Bitween.Domain.Gateway.ApiGateway", b => + { + b.Navigation("Partners"); + }); + modelBuilder.Entity("SW.Bitween.Domain.Partner", b => { b.Navigation("Subscriptions"); diff --git a/SW.Bitween.PgSql/SW.Bitween.PgSql.csproj b/SW.Bitween.PgSql/SW.Bitween.PgSql.csproj index de895838..37dc5a6a 100644 --- a/SW.Bitween.PgSql/SW.Bitween.PgSql.csproj +++ b/SW.Bitween.PgSql/SW.Bitween.PgSql.csproj @@ -16,4 +16,10 @@ + + + + + + diff --git a/SW.Bitween.Sdk/Model/ApiGateway.cs b/SW.Bitween.Sdk/Model/ApiGateway.cs new file mode 100644 index 00000000..dfb8a562 --- /dev/null +++ b/SW.Bitween.Sdk/Model/ApiGateway.cs @@ -0,0 +1,37 @@ +using SW.PrimitiveTypes; +using System.Collections.Generic; + +namespace SW.Bitween.Model +{ + public class ApiGatewayCreate : IName + { + public string Name { get; set; } + public string UrlName { get; set; } + } + + public class ApiGatewayRow : ApiGatewayUpdate + { + public int Id { get; set; } + public int? PartnersCount { get; set; } + } + + public class ApiGatewayUpdate : ApiGatewayCreate + { + public ICollection Partners { get; set; } + } + + public class ApiGatewayPartnerDto + { + public int PartnerId { get; set; } + public int SubscriptionId { get; set; } + public string PartnerName { get; set; } + public string SubscriptionName { get; set; } + } + + public class ApiGatewayPartnerCreate + { + public int PartnerId { get; set; } + public int SubscriptionId { get; set; } + } +} + diff --git a/SW.Bitween.Sdk/Model/GlobalAdapterValuesSet.cs b/SW.Bitween.Sdk/Model/GlobalAdapterValuesSet.cs new file mode 100644 index 00000000..3e93caf1 --- /dev/null +++ b/SW.Bitween.Sdk/Model/GlobalAdapterValuesSet.cs @@ -0,0 +1,25 @@ +using SW.PrimitiveTypes; +using System.Collections.Generic; + +namespace SW.Bitween.Model +{ + public class GlobalAdapterValuesSetCreate : IName + { + public string Id { get; set; } + public string Name { get; set; } + public Dictionary Values { get; set; } + } + + public class GlobalAdapterValuesSetRow : GlobalAdapterValuesSetUpdate + { + public string Id { get; set; } + } + + public class GlobalAdapterValuesSetUpdate : GlobalAdapterValuesSetCreate + { + } + + public class DeleteGlobalAdapterValuesSetModel + { + } +} diff --git a/SW.Bitween.Sdk/Model/Partner.cs b/SW.Bitween.Sdk/Model/Partner.cs index 8ab0df40..fa977724 100644 --- a/SW.Bitween.Sdk/Model/Partner.cs +++ b/SW.Bitween.Sdk/Model/Partner.cs @@ -20,5 +20,6 @@ public class PartnerUpdate : PartnerCreate { public ICollection ApiCredentials { get; set; } public ICollection Subscriptions { get; set; } + public Dictionary AdapterProperties { get; set; } } } diff --git a/SW.Bitween.Sdk/Model/Subscription.cs b/SW.Bitween.Sdk/Model/Subscription.cs index 7dae4617..fa9ca9e5 100644 --- a/SW.Bitween.Sdk/Model/Subscription.cs +++ b/SW.Bitween.Sdk/Model/Subscription.cs @@ -10,7 +10,8 @@ public enum SubscriptionType Internal = 1, ApiCall = 2, Receiving = 4, - Aggregation = 8 + Aggregation = 8, + GatewayApiCall = 16, } public class SubscriptionReceiveNow @@ -37,25 +38,27 @@ public class SearchSubscriptionTrailModel public int SubscriptionId { get; set; } } - public class SubscriptionCreate : IName + public abstract class SubscriptionCreateUpdateBase : IName { public string Name { get; set; } public int DocumentId { get; set; } - public SubscriptionType Type { get; set; } public int? PartnerId { get; set; } public int? AggregationForId { get; set; } } + + public class SubscriptionCreate :SubscriptionCreateUpdateBase + { + public SubscriptionType Type { get; set; } + } - public class SubscriptionSearch : SubscriptionUpdate + public class SubscriptionSearch : SubscriptionGet { public int Id { get; set; } public string DocumentName { get; set; } public bool? IsRunning { get; set; } - public string CategoryCode { get; set; } - public string CategoryDescription { get; set; } } - public class SubscriptionUpdate : SubscriptionCreate + public class SubscriptionUpdate : SubscriptionCreateUpdateBase { public string HandlerId { get; set; } public string MapperId { get; set; } @@ -88,4 +91,9 @@ public class SubscriptionUpdate : SubscriptionCreate public string CategoryCode { get; set; } public string CategoryDescription { get; set; } } + + public class SubscriptionGet : SubscriptionUpdate + { + public SubscriptionType Type { get; set; } + } } \ No newline at end of file diff --git a/SW.Bitween.Web/Properties/launchSettings.json b/SW.Bitween.Web/Properties/launchSettings.json index 00b5d594..68c5a1f0 100644 --- a/SW.Bitween.Web/Properties/launchSettings.json +++ b/SW.Bitween.Web/Properties/launchSettings.json @@ -1,3 +1,6 @@ + + + { "iisSettings": { "windowsAuthentication": false, @@ -14,15 +17,6 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "SW.Bitween.Web": { - "commandName": "Project", - "launchBrowser": false, - "launchUrl": "https://localhost:5001", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" } } } \ No newline at end of file diff --git a/SW.Bitween.Web/SW.Bitween.Web.csproj b/SW.Bitween.Web/SW.Bitween.Web.csproj index 795a599f..b8f2030f 100644 --- a/SW.Bitween.Web/SW.Bitween.Web.csproj +++ b/SW.Bitween.Web/SW.Bitween.Web.csproj @@ -34,6 +34,7 @@ + diff --git a/SW.Bitween.Web/Startup.cs b/SW.Bitween.Web/Startup.cs index 7d62f424..946195b2 100644 --- a/SW.Bitween.Web/Startup.cs +++ b/SW.Bitween.Web/Startup.cs @@ -57,7 +57,9 @@ public void ConfigureServices(IServiceCollection services) services.AddMemoryCache(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddScoped(); + services.AddHttpContextAccessor(); services.AddHostedService(); services.AddHostedService(); @@ -220,7 +222,7 @@ public void ConfigureServices(IServiceCollection services) connectionString += ";Authentication=Active Directory Default"; } } - + c.UseSqlServer(connectionString, b => { b.MigrationsAssembly(typeof(MsSql.DbType).Assembly.FullName); }); } diff --git a/SW.Bitween.Web/appsettings.Migration.json b/SW.Bitween.Web/appsettings.Migration.json new file mode 100644 index 00000000..3415bc9d --- /dev/null +++ b/SW.Bitween.Web/appsettings.Migration.json @@ -0,0 +1,8 @@ +{ + "ConnectionStrings": { + "BitweenDb": "Server=localhost;Database=bitween_migration;User=root;Password=password;" + }, + "Bitween": { + "DatabaseType": "MySql" + } +} diff --git a/SW.Bitween.sln b/SW.Bitween.sln index 2d76a9c1..0c82ab3e 100644 --- a/SW.Bitween.sln +++ b/SW.Bitween.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SW.Bitween.SampleValidator" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SW.Bitween.PgSql", "SW.Bitween.PgSql\SW.Bitween.PgSql.csproj", "{1474658D-E225-478E-80D6-D41A0376F88C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SW.Bitween.NativeAdapters", "SW.Bitween.NativeAdapters\SW.Bitween.NativeAdapters.csproj", "{5D7B6BD7-427E-4F1B-B4CA-CF6B3A3ED89F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +75,10 @@ Global {1474658D-E225-478E-80D6-D41A0376F88C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1474658D-E225-478E-80D6-D41A0376F88C}.Release|Any CPU.ActiveCfg = Release|Any CPU {1474658D-E225-478E-80D6-D41A0376F88C}.Release|Any CPU.Build.0 = Release|Any CPU + {5D7B6BD7-427E-4F1B-B4CA-CF6B3A3ED89F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D7B6BD7-427E-4F1B-B4CA-CF6B3A3ED89F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D7B6BD7-427E-4F1B-B4CA-CF6B3A3ED89F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D7B6BD7-427E-4F1B-B4CA-CF6B3A3ED89F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SW.Bus.RabbitMqExtensions/.DS_Store b/SW.Bus.RabbitMqExtensions/.DS_Store new file mode 100644 index 00000000..8badee4d Binary files /dev/null and b/SW.Bus.RabbitMqExtensions/.DS_Store differ diff --git a/SW.Bus/.DS_Store b/SW.Bus/.DS_Store new file mode 100644 index 00000000..15b7b311 Binary files /dev/null and b/SW.Bus/.DS_Store differ