using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System.Net; using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute; namespace CDP { public class FileRevision { private readonly ILogger _logger; private static Lazy lazyClient = new Lazy(InitializeCosmosClient); private static CosmosClient cosmosClient => lazyClient.Value; private static string DatabaseName = "CDP"; private static string ContainerName = "RevisionsContainer"; public FileRevision(ILogger logger) { _logger = logger; } private static CosmosClient InitializeCosmosClient() { // Perform any initialization here var uri = "https://cdplite.documents.azure.com:443/"; var authKey = "VPbg8RpzyI3XwhC2o0dIUtYFs33ghxORCqZeNAyg8vg4HWUBjM41BUxP0qLFXEvFh6ewQY1uKv52ACDbsEN1AQ=="; return new CosmosClient(uri, authKey); } public static async Task GetFileRevisionInternal(string appKey, string fileId) { Container container = cosmosClient.GetContainer(DatabaseName, ContainerName); string id = Helpers.HashAndShortenText(appKey + fileId); PartitionKey key = new PartitionKeyBuilder().Add(appKey).Add(fileId).Build(); var item = await container.ReadItemAsync(id, key); return item.Resource; } static internal async Task AddRevisionInternal(AddRevisionDocumentDto doc, ILogger _logger) { try { Container container = cosmosClient.GetContainer(DatabaseName, ContainerName); _logger.LogInformation("container id: ", container); RevisionDocument document = new RevisionDocument() { //AppKey = doc.AppKey, //FileId = doc.FileId, RevisionId = doc.RevisionId, EditedBy = doc.EditedBy, Comments = doc.Comments, RevisionDate = doc.RevisionDate }; string id = Helpers.HashAndShortenText(doc.AppKey + doc.FileId); PartitionKey partitionKey = new PartitionKeyBuilder() .Add(doc.AppKey) .Add(doc.FileId) .Build(); var res = await AddOrUpdateRevision(container, id, partitionKey, document, doc.AppKey, doc.FileId); return new OkObjectResult(res); } catch (Exception ex) { _logger.LogError(ex.ToString()); return new StatusCodeResult(500); } } public async static Task AddMailBodyRevision(RevisionDocument document, CompoundDocument compoundDocument, string AppKey, string FileId) { Container container = cosmosClient.GetContainer(DatabaseName, ContainerName); List documents = new List { document }; RevisionEntry entry = new RevisionEntry { AppKey = AppKey, FileId = FileId, FileRevisions = documents, CompoundDocument = compoundDocument }; ItemResponse item = await container.CreateItemAsync(entry); return item.Resource.id; } private async static Task AddOrUpdateRevision(Container container, string id, PartitionKey partitionKey, RevisionDocument document, string appKey, string fileId) { try { ItemResponse item = await container.ReadItemAsync(id, partitionKey); RevisionEntry revisions = item.Resource; revisions.FileRevisions.Add(document); ItemRequestOptions requestOptions = new ItemRequestOptions { IfMatchEtag = item.ETag }; var res = await container.UpsertItemAsync(revisions, partitionKey); return res.Resource; } catch (CosmosException ex) { if (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { List revisions = new List { document }; RevisionEntry entry = new RevisionEntry() { AppKey = appKey, FileId = fileId, FileRevisions = revisions }; ItemResponse item = await container.CreateItemAsync(entry); return item.Resource; } else if (ex.StatusCode == HttpStatusCode.PreconditionFailed) { return await AddOrUpdateRevision(container, id, partitionKey, document, appKey, fileId); } else { throw ex; } } } #region Versioning Functions [Function("AddRevisionDocument")] public async Task AddRevisionDocument([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("C# HTTP trigger function processed a request for update revision"); string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); AddRevisionDocumentDto doc; try { doc = JsonConvert.DeserializeObject(requestBody); _logger.LogInformation(doc.RevisionId + doc.AppKey + doc.FileId); } catch (Exception ex) { _logger.LogError(ex.ToString()); return new BadRequestObjectResult("Invalid request format"); } return await AddRevisionInternal(doc, _logger); } [Function("GetFileRevisions")] public async Task GetFileRevisions([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, [FromBody] GetRevisionsDto reqDto) { _logger.LogInformation("C# HTTP trigger function processed a request for update revision", JsonConvert.SerializeObject(reqDto)); try { var item = await GetFileRevisionInternal(reqDto.AppKey, reqDto.FileId); if (item.CompoundDocument != null) { return new OkObjectResult(item); } List revisions = item.FileRevisions; //_logger.LogInformation("docs: {0}", JsonConvert.SerializeObject(revisions)); return new OkObjectResult(revisions); } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return new StatusCodeResult(404); } } #endregion } public class GetRevisionsDto { public required string AppKey { get; set; } public required string FileId { get; set; } } public class AddRevisionDocumentDto { public required string AppKey { get; set; } public required string FileId { get; set; } public required string RevisionId { get; set; } public required string EditedBy { get; set; } public required DateTime RevisionDate { get; set; } public required string Comments { get; set; } } public class RevisionDocument { public required string RevisionId { get; set; } public required string EditedBy { get; set; } public required DateTime RevisionDate { get; set; } public required string Comments { get; set; } } public class RevisionEntry { public string id { get { string s = Helpers.HashAndShortenText(AppKey + FileId); return s.Replace('/', '-'); } } public required string AppKey { get; set; } public required string FileId { get; set; } public List FileRevisions { get; set; } public CompoundDocument? CompoundDocument { get; set; } } public class CompoundDocument { public required string Id { get; set; } public required string AppKey { get; set; } public required string Contents { get; set; } //can be MailRecord below public required string DocumentType { get; set; } } }