Skip to content
Snippets Groups Projects
Sender.cs 9.07 KiB
Newer Older
using System.Text.RegularExpressions;
using Autofac;
using FitConnect.Encryption;
Klaus Fischer's avatar
Klaus Fischer committed
using FitConnect.Interfaces.Sender;
using FitConnect.Models;
using FitConnect.Models.Api.Metadata;
Klaus Fischer's avatar
Klaus Fischer committed
using FitConnect.Services.Models.v1.Submission;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Attachment = FitConnect.Models.Attachment;
using Data = FitConnect.Models.Api.Metadata.Data;
using Metadata = FitConnect.Models.Metadata;

namespace FitConnect;

/// <summary>
///     The fluent implementation for the <see cref="Sender" /> to encapsulate the FitConnect API.
/// </summary>
/// <example>
///     Reference for FluentSender
///     <code>
/// client.Sender
///     .Authenticate(clientId!, clientSecret!)
///     .CreateSubmission(new Submission { Attachments = new List() })
///     .UploadAttachments()
///     .WithData(data)
///     .Subm@it();
/// </code>
/// </example>
Klaus Fischer's avatar
Klaus Fischer committed
public class Sender : FitConnectClient, ISender, ISenderWithDestination,
    ISenderWithAttachments, ISenderWithData, ISenderWithService {
    public Sender(FitConnectEnvironment environment, string clientId, string clientSecret,
        ILogger? logger = null) : base(environment, clientId, clientSecret, logger) {
    public Sender(FitConnectEnvironment environment, string clientId, string clientSecret,
        IContainer container) : base(environment, clientId, clientSecret,
        container) {
    }
Klaus Fischer's avatar
Klaus Fischer committed

    public string? PublicKey { get; set; }

Klaus Fischer's avatar
Klaus Fischer committed
    public ISenderWithDestination FindDestinationId(string leiaKey, string? ags = null,
        string? ars = null,
        string? areaId = null) {
Klaus Fischer's avatar
Klaus Fischer committed
        if (ags == null && ars == null && areaId == null)
Klaus Fischer's avatar
Klaus Fischer committed
            throw new ArgumentException("One of the following must be provided: ags, ars, areaId");

        var destinationId = RouteService.GetDestinationIdAsync(leiaKey, ags, ars, areaId).Result;
Klaus Fischer's avatar
Klaus Fischer committed
        Logger?.LogInformation("Received destinations: {Destinations}",
            destinationId.Select(d => d.DestinationId).Aggregate((a, b) => a + "," + b));
        return WithDestination(destinationId.First().DestinationId);
Klaus Fischer's avatar
Klaus Fischer committed
    public ISenderWithDestination WithDestination(string destinationId) {
        if (!Regex.IsMatch(destinationId,
                "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"))
            throw new ArgumentException("The destination must be a valid GUID");

        Submission = CreateSubmission(destinationId).Result;
        return this;
    }

Klaus Fischer's avatar
Klaus Fischer committed
    public ISenderWithData WithData(string data) {
        try {
            JsonConvert.DeserializeObject(data);
        }
        catch (Exception e) {
            throw new ArgumentException("The data must be valid JSON string", e);
        }


        Submission!.Data = data;
        return this;
    }
    public Submission? Submission { get; set; }

Klaus Fischer's avatar
Klaus Fischer committed
    Submission ISenderReady.Submit() {
        if (Submission == null) {
            Logger?.LogCritical("Submission is null on submit");
            throw new InvalidOperationException("Submission is not ready");
        }

        var metadata = CreateMetadata(Submission);
        Logger?.LogTrace("MetaData: {metadata}", metadata);
        Logger?.LogInformation("Sending submission");
        var encryptedMeta = Encryption.Encrypt(metadata);
        Logger?.LogTrace("Encrypted metadata: {encryptedMeta}", encryptedMeta);
        Submission.EncryptedMetadata = encryptedMeta;
        if (Submission.Data != null)
            Submission.EncryptedData = Encryption.Encrypt(Submission.Data);

        var result = SubmissionService
            .SubmitSubmission(Submission.Id!, (SubmitSubmissionDto)Submission).Result;

        Logger?.LogInformation("Submission sent");
Klaus Fischer's avatar
Klaus Fischer committed
        return Submission;
Klaus Fischer's avatar
Klaus Fischer committed
    public ISenderWithService WithServiceType(string serviceName, string leikaKey) {
        if (string.IsNullOrWhiteSpace(leikaKey) || !Regex.IsMatch(leikaKey,
                "^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,.:=@;$_!*'%/?#-]+$")) {
            Logger?.LogError("The leikaKey must be a valid URN");
            throw new ArgumentException("Invalid leika key");
        }
        Submission!.ServiceType = new ServiceType {
            Name = serviceName,
            Identifier = leikaKey
        };
        return this;
Klaus Fischer's avatar
Klaus Fischer committed
    /// <summary>
    /// 
    /// </summary>
    /// <param name="attachments"></param>
    /// <returns></returns>
    public ISenderWithAttachments WithAttachments(params Attachment[] attachments) =>
        WithAttachments(attachments.ToList());
    
    
Klaus Fischer's avatar
Klaus Fischer committed
    /// <summary>
    /// </summary>
    /// <param name="attachments"></param>
Klaus Fischer's avatar
Klaus Fischer committed
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"></exception>
    /// <exception cref="ArgumentException"></exception>
Klaus Fischer's avatar
Klaus Fischer committed
    public ISenderWithAttachments WithAttachments(IEnumerable<Attachment> attachments) {
        Submission!.Attachments = new List<Attachment>();
        foreach (var attachment in attachments) Submission!.Attachments.Add(attachment);

        if (Submission.ServiceType == null) {
            Logger?.LogError("Submission has no service type");
            throw new ArgumentException("Submission has no service type");
        }

        var created = SubmissionService.CreateSubmission((CreateSubmissionDto)Submission);
        Submission.Id = created.SubmissionId;
        Submission.CaseId = created.CaseId;

        Logger?.LogInformation("Submission Id {CreatedSubmissionId}, CaseId {SubmissionCaseId}",
            created.SubmissionId, Submission.CaseId);

        var encryptedAttachments = Encrypt(PublicKey!, Submission.Attachments);
        UploadAttachmentsAsync(Submission.Id!, encryptedAttachments).Wait();

        return this;
Klaus Fischer's avatar
Klaus Fischer committed
    public ISenderWithDestination FindDestinationId(Destination destination) {
        throw new NotImplementedException();
    }
Klaus Fischer's avatar
Klaus Fischer committed
    /// <summary>
    ///     Creates a new <see cref="Submission" /> on the FitConnect server.
Klaus Fischer's avatar
Klaus Fischer committed
    /// </summary>
    /// <param name="destinationId">The id of the destination the submission has to be sent to</param>
Klaus Fischer's avatar
Klaus Fischer committed
    /// <returns></returns>
    /// <exception cref="InvalidOperationException">If sender is not authenticated</exception>
    /// <exception cref="ArgumentException">If submission is not ready to be sent</exception>
    private async Task<Submission> CreateSubmission(string destinationId) {
        PublicKey = await GetPublicKeyFromDestination(destinationId);
        Encryption = new FitEncryption(Logger) { PublicKeyEncryption = PublicKey };

        var submission = new Submission {
            DestinationId = destinationId
        };

        return submission;
    }
    private async Task<string> GetPublicKeyFromDestination(string destinationId) {
        var publicKey = await DestinationService.GetPublicKey(destinationId);
        return publicKey;
Klaus Fischer's avatar
Klaus Fischer committed
    /// <summary>
    ///     Create Metadata incl. Hash
Klaus Fischer's avatar
Klaus Fischer committed
    /// </summary>
    /// <param name="submission"></param>
Klaus Fischer's avatar
Klaus Fischer committed
    /// <returns></returns>
    public static string CreateMetadata(Submission submission) {
        var data = new Data {
            Hash = new DataHash {
                Type = "sha512",
                Content = FitEncryption.CalculateHash(submission.Data ?? "")
            SubmissionSchema = new Fachdatenschema {
                SchemaUri = submission.ServiceType.Identifier,
                MimeType = "application/json"
            }
        };
        var contentStructure = new ContentStructure {
            Data = data,
            Attachments = submission.Attachments.Select(a =>
                new Models.Api.Metadata.Attachment {
                    Description = a.Description,
                    AttachmentId = a.Id,
                    MimeType = a.MimeType,
                    Filename = a.Filename,
                    Purpose = "attachment",
                    Hash = new AttachmentHash {
                        Type = "sha512",
Klaus Fischer's avatar
Klaus Fischer committed
                        Content = a.Hash
Klaus Fischer's avatar
Klaus Fischer committed

        var metaData = new Metadata {
            ContentStructure = contentStructure
Klaus Fischer's avatar
Klaus Fischer committed
        };
        return JsonConvert.SerializeObject(metaData);
Klaus Fischer's avatar
Klaus Fischer committed
    public IEnumerable<Area> GetAreas(string filter, out int totalCount, int offset = 0,
        int limit = 100) {
        var dto = RouteService.GetAreas(filter, offset, limit).Result;
        totalCount = dto?.TotalCount ?? 0;
        return dto?.Areas ?? new List<Area>();
    }

Klaus Fischer's avatar
Klaus Fischer committed
    /// <summary>
    ///     Uploading the encrypted data to the server
Klaus Fischer's avatar
Klaus Fischer committed
    /// </summary>
Klaus Fischer's avatar
Klaus Fischer committed
    /// <param name="submissionId">Submissions ID</param>
Klaus Fischer's avatar
Klaus Fischer committed
    /// <param name="encryptedAttachments">Encrypted attachments with id and content</param>
Klaus Fischer's avatar
Klaus Fischer committed
    private async Task<bool> UploadAttachmentsAsync(string submissionId,
Klaus Fischer's avatar
Klaus Fischer committed
        Dictionary<string, string> encryptedAttachments) {
        try {
            foreach (var (id, content) in encryptedAttachments) {
Klaus Fischer's avatar
Klaus Fischer committed
                Logger?.LogInformation("Uploading attachment {id}", id);
                SubmissionService.UploadAttachment(submissionId, id, content);
            }

            return true;
        }
        catch (Exception e) {
Klaus Fischer's avatar
Klaus Fischer committed
            Logger?.LogError("Error Uploading attachment {message}", e.Message);
            throw;
Klaus Fischer's avatar
Klaus Fischer committed
}