private async Task <int> UpdateReportAndDetails(List <ReportDetail> reports, ConnectorIdentifier identifier) { if (reports == null || reports.Count <= 0) { return(0); } // Update telemetry var count = 0; var groups = reports.GroupBy(r => new { r.MessageId, r.CustomMessageId }); foreach (var group in groups) { var succeed = group.Where(r => r.State == MessageState.DELIVERED).Count(); var failed = group.Where(r => r.State != MessageState.DELIVERED).Count(); var updated = await this.telemetryManager.OnMessageReportUpdatedAsync( group.Key.MessageId, group.Key.CustomMessageId, group.ToList(), identifier); if (updated) { count += group.Count(); } } return(count); }
public async Task DeleteCredentialAssignmentsAsync(string engagementAccount, ConnectorIdentifier identifier) { var assignments = await this.store.ListCredentialAssignmentsByAccountAsync(engagementAccount, ChannelType.Both, false); Validator.IsTrue <ResourceNotFoundException>(assignments != null || assignments.Any(a => a.ConnectorIdentifier.Equals(identifier)), nameof(identifier), "The assignment does not exist."); await this.store.DeleteCredentialAssignmentsAsync(engagementAccount, identifier); }
public async Task <SmsConnectorCredential> GetConnectorCredentialByIdAsync(ConnectorIdentifier identifier) { var credential = await this.store.GetConnectorCredentialByIdAsync(identifier); Validator.IsTrue <ResourceNotFoundException>(credential != null, nameof(credential), "Credential '{0}' does not exist or is disabled.", identifier); return(credential); }
public async Task <AgentMetadata> GetAgentMetadataAsync(ConnectorIdentifier identifier) { using (var ctx = new SmsServiceDbEntities(this.connectionString)) { var entity = await ctx.ConnectorAgentMetadata.SingleOrDefaultAsync( a => a.Provider == identifier.ConnectorName && a.Id == identifier.ConnectorId); return(entity?.ToModel()); } }
public async Task <EmailConnectorCredential> GetConnectorCredentialByIdAsync(ConnectorIdentifier identifier) { using (var ctx = new EmailServiceDbEntities(this.connectionString)) { var entity = await ctx.ConnectorCredentials.SingleOrDefaultAsync( c => c.Provider == identifier.ConnectorName && c.Id == identifier.ConnectorId); return(entity != null && entity.Enabled ? JsonConvert.DeserializeObject <EmailConnectorCredential>(entity.Data) : null); } }
public async Task <ServiceProviderResponse> CreateOrUpdateCredentialAssignments( [FromHeader(Constant.OperationTrackingIdHeader)] string requestId, string account, [FromBody] CredentialAssignment request) { await ValidateAccount(account); Validator.ArgumentNotNull(request, nameof(request)); Validator.ArgumentNotNullOrEmpty(request.Provider, nameof(request.Provider)); Validator.ArgumentNotNullOrEmpty(request.ConnectorId, nameof(request.ConnectorId)); Validator.IsTrue <ArgumentException>(request.ChannelType != ChannelType.Invalid, nameof(request.ChannelType), "Invalid channel type."); var identifier = new ConnectorIdentifier(request.Provider, request.ConnectorId); var credential = await this.credentialManager.GetConnectorCredentialByIdAsync(identifier); Validator.IsTrue <ResourceNotFoundException>(credential != null, nameof(credential), "Credential '{0}' does not exist.", identifier); Validator.IsTrue <ArgumentException>(credential.ChannelType == request.ChannelType, nameof(request.ChannelType), "Credential '{0}' is for channel type '{1}' but not '{2}'", identifier, credential.ChannelType.ToString(), request.ChannelType.ToString()); if (string.IsNullOrEmpty(request.ExtendedCode)) { // Auto-assign account code var otherAccounts = await this.credentialManager.ListCredentialAssignmentsById(identifier, false); if (otherAccounts != null && otherAccounts.Count > 0) { var last = otherAccounts.Where(a => a.ExtendedCode != null).OrderBy(a => a.ExtendedCode).LastOrDefault(); if (int.TryParse(last.ExtendedCode, out int code) && code < Math.Pow(10, SmsConstant.ExtendedCodeCompanyLength) - 1) { request.ExtendedCode = (code + 1).ToString().PadLeft(SmsConstant.ExtendedCodeCompanyLength, '0'); } else { throw new ApplicationException($"Failed the generate new code. The last one is '{last.ExtendedCode}'"); } } else { request.ExtendedCode = 1.ToString().PadLeft(SmsConstant.ExtendedCodeCompanyLength, '0'); } } await this.credentialManager.CreateOrUpdateCredentialAssignmentAsync(request.ToDataContract(account)); var assignmentList = await this.credentialManager.ListCredentialAssignmentsByAccountAsync(account, ChannelType.Both, false); var response = assignmentList?.Select(a => new CredentialAssignment(a)).ToList(); return(ServiceProviderResponse.CreateJsonResponse(HttpStatusCode.OK, response)); }
public async Task DeleteConnectorCredentialAsync(ConnectorIdentifier identifier) { using (var ctx = new EmailServiceDbEntities(this.connectionString)) { var entity = await ctx.ConnectorCredentials.SingleOrDefaultAsync( c => c.Provider == identifier.ConnectorName && c.Id == identifier.ConnectorId); if (entity != null) { ctx.ConnectorCredentials.Remove(entity); await ctx.SaveChangesAsync(); } } }
public async Task DeleteCredentialAssignmentsAsync(string engagementAccount, ConnectorIdentifier identifier) { using (var ctx = new EmailServiceDbEntities(this.connectionString)) { var entities = await ctx.ConnectorCredentialAssignments.Where(c => c.EngagementAccount == engagementAccount).ToListAsync(); entities = entities.Where(e => identifier == null || (e.Provider.Equals(identifier.ConnectorName, StringComparison.OrdinalIgnoreCase) && e.Id.Equals(identifier.ConnectorId, StringComparison.OrdinalIgnoreCase))).ToList(); if (entities != null) { ctx.ConnectorCredentialAssignments.RemoveRange(entities); await ctx.SaveChangesAsync(); } } }
public async Task <List <ConnectorCredentialAssignment> > ListCredentialAssignmentsById(ConnectorIdentifier identifier, bool activeOnly = true) { return(await this.store.ListCredentialAssignmentsById(identifier, activeOnly)); }
private string GetAgentKey(ConnectorIdentifier identifer) { return(identifer != null?string.Concat(identifer.ConnectorName, identifer.ConnectorId) : null); }
public async Task <int> OnReportPushedAsync(List <ReportDetail> reports, ConnectorIdentifier identifier) { return(await this.UpdateReportAndDetails(reports, identifier)); }
public async Task <HttpResponseMessage> OnInboundMessageReceived(string connectorName, HttpRequestMessage request, CancellationToken cancellationToken) { try { var metadata = await this.credentialManager.GetMetadata(connectorName); if (metadata != null) { InboundAgent agent = null; try { agent = new InboundAgent(metadata.ConnectorUri); // Get credential var connectorId = await agent.ParseConnectorIdFromInboundMessageAsync(request, cancellationToken); if (string.IsNullOrEmpty(connectorId)) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.FailedMatch, $"Failed to get connector key for '{connectorName}'."); return(request.CreateResponse(HttpStatusCode.Unauthorized)); } var identifier = new ConnectorIdentifier(connectorName, connectorId); SmsConnectorCredential credential = null; try { credential = await this.credentialManager.GetConnectorCredentialByIdAsync(identifier); } catch { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.FailedMatch, $"Failed to get credential for {identifier}."); return(request.CreateResponse(HttpStatusCode.Unauthorized)); } // Parse and save messages var result = await agent.ParseInboundRequestAsync(request, credential.ToDataContract(metadata), cancellationToken); SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.InProgress, $"Complete parsing code={result.Response.HttpStatusCode} message={result.Messages?.Count ?? 0}"); if (result.Type == InboundType.MoMessage) { await SaveMoMessageAsync(result.Messages, agent, cancellationToken); } else if (result.Type == InboundType.Report) { var saveResult = await SaveReportMessageAsync(result.Messages, identifier); if (!saveResult) { result.Response.HttpStatusCode = (int)HttpStatusCode.InternalServerError; } } SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.Succeeded, $"Complete parsing code={result.Response.HttpStatusCode} message={result.Messages?.Count ?? 0}"); return(result.Response.ToHttpResponseMessage()); } finally { if (agent != null) { agent.Dispose(); } } } SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.FailedMatch, $"Inbound message received from invalid connector '{connectorName}'."); return(request.CreateResponse(HttpStatusCode.Forbidden)); } catch (Exception ex) { SmsProviderEventSource.Current.ErrorException(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnInboundMessageReceived), OperationStates.Failed, "Failed to process inbound message", ex); return(request.CreateResponse(HttpStatusCode.InternalServerError)); } }
private async Task <bool> SaveReportMessageAsync(Dictionary <ConnectorIdentifier, List <InboundMessage> > messages, ConnectorIdentifier identifier) { if (messages == null || messages.Count <= 0) { return(false); } var reports = new List <ReportDetail>(); foreach (var kv in messages) { reports.AddRange(kv.Value.Where(v => v.ReportMessage != null).Select(v => v.ReportMessage).ToList()); } if (reports.Count > 0) { var saved = await this.reportManager.OnReportPushedAsync(reports, identifier); return(saved == reports.Count); } return(false); }
public async Task <bool> OnMessageReportUpdatedAsync(string messageId, string customMessageId, List <ReportDetail> reports, ConnectorIdentifier connector) { if (string.IsNullOrEmpty(messageId) && !string.IsNullOrEmpty(customMessageId)) { // Get id mapping, retry 3 times if cannot found (in case racing with case #2 who has not insert the mapping yet) int retry = 3; while (retry > 0) { messageId = (await MessageIdMappingTableEntity.GetAsync(idMappingTable, customMessageId))?.MessageId; if (!string.IsNullOrEmpty(messageId)) { break; } retry--; await Task.Delay(1000); } if (string.IsNullOrEmpty(messageId)) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for customMessageId={customMessageId}"); return(false); } } // Get record from summary table var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId); if (entity == null) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for messageId={messageId}"); return(false); } // Calculating the unit according to provider // This is for long message that will be charged as multiple messages var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName); var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata); // Report might be ealier than dispatch result, so make sure units is updated if (entity.Units == null) { entity.Units = units; entity.LastUpdateTime = DateTime.UtcNow; await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity); } // Update detail table if (reports != null && reports.Count > 0) { await this.UpdateMessageDetailsAsync(entity.EngagementAccount, entity, reports); } // Get account detail var account = await this.store.GetAccountAsync(entity.EngagementAccount); var succeed = reports.Where(r => r.State == MessageState.DELIVERED).Count(); var failed = reports.Where(r => r.State != MessageState.DELIVERED && r.State != MessageState.UNKNOWN).Count(); // Push metric and billing usage if (Enum.TryParse(entity.MessageCategory, out MessageCategory category)) { if (succeed > 0) { this.metricManager.LogDeliverSuccess(succeed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category); if (Constants.MessageCategoryToUsageTypeMappings.TryGetValue(category, out ResourceUsageType usageType)) { var usage = new ResourceUsageRecord(); usage.EngagementAccount = entity.EngagementAccount; usage.UsageType = usageType; usage.Quantity = succeed * units; await this.billingAgent.StoreBillingUsageAsync(new List <ResourceUsageRecord> { usage }, CancellationToken.None); SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Succeeded, $"Usage pushed to billing service account={entity.EngagementAccount} usageType={usageType} quantity={succeed}x{units}"); } } if (failed > 0) { this.metricManager.LogDeliverFailed(failed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category); } } else { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Failed, $"Failed to parse MessageCategory={entity.MessageCategory} MessageId={entity.MessageId}"); } return(true); }
public async Task DeleteConnectorCredentialAsync(ConnectorIdentifier identifier) { var credential = GetConnectorCredentialByIdAsync(identifier); await this.store.DeleteConnectorCredentialAsync(identifier); }
public async Task <bool> OnMessageDispatchedAsync(string engagementAccount, string messageId, string customMessageId, List <string> targets, ConnectorIdentifier connector) { if (!string.IsNullOrEmpty(customMessageId)) { // Insert id mapping await MessageIdMappingTableEntity.InsertOrMergeAsync(this.idMappingTable, new MessageIdMappingTableEntity(engagementAccount, messageId, customMessageId)); } // Get record from summary table var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId); if (entity == null) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for account={engagementAccount} messageId={messageId}"); return(false); } // Calculating the unit according to provider var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName); var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata); if (entity.Units == null) { entity.Units = units; entity.LastUpdateTime = DateTime.UtcNow; await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity); } return(true); }
public async Task <List <CredentialAssignment> > ListCredentialAssignmentsById(ConnectorIdentifier identifier, bool activeOnly = true) { using (var ctx = new EmailServiceDbEntities(this.connectionString)) { var entities = await ctx.ConnectorCredentialAssignments.Where( c => c.Provider == identifier.ConnectorName && c.Id == identifier.ConnectorId && (!activeOnly || c.Active)).ToListAsync(); return(entities?.Select(e => e.ToModel()).ToList()); } }