diff --git a/src/Application/Common/Services/ISavedWorkItemService.cs b/src/Application/Common/Services/ISavedWorkItemService.cs index eb73214..e0275a9 100644 --- a/src/Application/Common/Services/ISavedWorkItemService.cs +++ b/src/Application/Common/Services/ISavedWorkItemService.cs @@ -6,7 +6,7 @@ namespace Application.Common.Services public interface ISavedWorkItemService { Task CreateSavedWorkItemStatus(WorkItemStatus workItemStatus, string userId, string revitVersion, string objectKey, string fileName); - Task> GetSavedWorkItems(string userId); + List GetSavedWorkItems(string userId); Task UpdateSavedWorkItemStatus(WorkItemStatus workItemStatus); } } \ No newline at end of file diff --git a/src/Application/Common/Services/SavedWorkItemService.cs b/src/Application/Common/Services/SavedWorkItemService.cs index a016895..a8d8b33 100644 --- a/src/Application/Common/Services/SavedWorkItemService.cs +++ b/src/Application/Common/Services/SavedWorkItemService.cs @@ -3,6 +3,7 @@ using Azure.Data.Tables; using Domain.Entities; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -62,17 +63,17 @@ public async Task UpdateSavedWorkItemStatus(WorkItemStatus workItemStatus) } } - public async Task> GetSavedWorkItems(string userId) + public List GetSavedWorkItems(string userId) { TableClient tableClient = _tableServiceClient.GetTableClient(_partitionKey); tableClient.CreateIfNotExists(); - AsyncPageable queryResults = tableClient.QueryAsync(ent => + Pageable queryResults = tableClient.Query(ent => ent.UserId == userId && ent.Created >= DateTime.Now.AddDays(-30)); List savedWorkItems = new List(); - await foreach (SavedWorkItem savedWorkItem in queryResults) + foreach (SavedWorkItem savedWorkItem in queryResults) { savedWorkItems.Add(savedWorkItem); } diff --git a/src/Application/Files/Queries/GetDownloadUrlQuery/GetDownloadUrlQueryHandler.cs b/src/Application/Files/Queries/GetDownloadUrlQuery/GetDownloadUrlQueryHandler.cs index 134ac9c..c15ff5e 100644 --- a/src/Application/Files/Queries/GetDownloadUrlQuery/GetDownloadUrlQueryHandler.cs +++ b/src/Application/Files/Queries/GetDownloadUrlQuery/GetDownloadUrlQueryHandler.cs @@ -39,8 +39,13 @@ public async Task Handle(GetDownloadUrlQuery request, Signeds3downloadResponse signedUrl = await _ossClient.SignedS3DownloadAsync( twoLeggedToken.AccessToken, bucketKey, objectKey, responseContentDisposition: $"attachment; filename=\"{outputFileName}\"", - minutesExpiration: 60); + minutesExpiration: 60, + throwOnError:false); + if (signedUrl == null) + { + signedUrl = new Signeds3downloadResponse(); + } return signedUrl; } diff --git a/src/Application/WorkItems/Commands/CreateWorkItem/CreateWorkItemCommandHandler.cs b/src/Application/WorkItems/Commands/CreateWorkItem/CreateWorkItemCommandHandler.cs index ad86d32..591bd5f 100644 --- a/src/Application/WorkItems/Commands/CreateWorkItem/CreateWorkItemCommandHandler.cs +++ b/src/Application/WorkItems/Commands/CreateWorkItem/CreateWorkItemCommandHandler.cs @@ -57,7 +57,8 @@ public async Task Handle(CreateWorkItemCommand request, Cancella // 1. input file XrefTreeArgument inputFileArgument = new XrefTreeArgument() { - Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", inputBucketKey, objectKey + ".rvt"), + //Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", inputBucketKey, ), + Url = $"urn:adsk.objects:os.object:{inputBucketKey}/{objectKey}.rvt", Verb = Verb.Get, Headers = new Dictionary() { @@ -75,7 +76,7 @@ public async Task Handle(CreateWorkItemCommand request, Cancella // 3. output file XrefTreeArgument outputFileArgument = new XrefTreeArgument() { - Url = string.Format("https://developer.api.autodesk.com/oss/v2/buckets/{0}/objects/{1}", outputBucketKey, objectKey + ".ifc"), + Url = $"urn:adsk.objects:os.object:{outputBucketKey}/{objectKey}.ifc", Verb = Verb.Put, Headers = new Dictionary() { @@ -83,6 +84,13 @@ public async Task Handle(CreateWorkItemCommand request, Cancella } }; + // 3. onComplete callback + XrefTreeArgument onCompleteArgument = new XrefTreeArgument() + { + Url = $"{_forgeConfiguration.CallbackUrl}/callback/oncomplete", + Verb = Verb.Post + }; + // prepare & submit workitem WorkItem workItemSpec = new WorkItem() { @@ -91,7 +99,8 @@ public async Task Handle(CreateWorkItemCommand request, Cancella { { "inputFile", inputFileArgument }, { "inputJson", inputJsonArgument }, - { "outputFile", outputFileArgument } + { "outputFile", outputFileArgument }, + { "onComplete", onCompleteArgument } } }; diff --git a/src/Application/WorkItems/Commands/UpdateWorkItemStatus/UpdateWorkItemStatusCommand.cs b/src/Application/WorkItems/Commands/UpdateWorkItemStatus/UpdateWorkItemStatusCommand.cs new file mode 100644 index 0000000..19d17f5 --- /dev/null +++ b/src/Application/WorkItems/Commands/UpdateWorkItemStatus/UpdateWorkItemStatusCommand.cs @@ -0,0 +1,21 @@ +using Autodesk.Forge.DesignAutomation.Model; +using Domain.Entities; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Application.WorkItems.Commands.UpdateWorkItemStatus +{ + public class UpdateWorkItemStatusCommand : IRequest + { + internal readonly WorkItemStatus Status; + + public UpdateWorkItemStatusCommand(WorkItemStatus status) + { + Status = status; + } + } +} diff --git a/src/Application/WorkItems/Commands/UpdateWorkItemStatus/UpdateWorkItemStatusCommandHandler.cs b/src/Application/WorkItems/Commands/UpdateWorkItemStatus/UpdateWorkItemStatusCommandHandler.cs new file mode 100644 index 0000000..a47f0e0 --- /dev/null +++ b/src/Application/WorkItems/Commands/UpdateWorkItemStatus/UpdateWorkItemStatusCommandHandler.cs @@ -0,0 +1,33 @@ +using Application.Common.Services; +using Application.Files.Queries.GetUploadUrl; +using Application.WorkItems.Queries.GetWorkItemStatus; +using Autodesk.Authentication; +using Autodesk.Forge.DesignAutomation; +using Autodesk.Forge.DesignAutomation.Model; +using Autodesk.Oss; +using MediatR; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Application.WorkItems.Commands.UpdateWorkItemStatus +{ + + public class UpdateWorkItemStatusCommandHandler : IRequestHandler + { + private readonly ISavedWorkItemService _savedWorkItemService; + + public UpdateWorkItemStatusCommandHandler(ISavedWorkItemService savedWorkItemService) + { + _savedWorkItemService = savedWorkItemService; + } + public async Task Handle(UpdateWorkItemStatusCommand request, CancellationToken cancellationToken) + { + await _savedWorkItemService.UpdateSavedWorkItemStatus(request.Status); + } + } +} diff --git a/src/Application/WorkItems/Commands/UpdateWorkItemStatus/WorkItemStatusDTO.cs b/src/Application/WorkItems/Commands/UpdateWorkItemStatus/WorkItemStatusDTO.cs new file mode 100644 index 0000000..23fcb38 --- /dev/null +++ b/src/Application/WorkItems/Commands/UpdateWorkItemStatus/WorkItemStatusDTO.cs @@ -0,0 +1,58 @@ +using Autodesk.Forge.DesignAutomation.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Application.WorkItems.Commands.UpdateWorkItemStatus +{ + + public class WorkItemStatusDTO + { + /// + /// The current status of the workitem. + /// + /// The current status of the workitem. + public string? Status { get; set; } + + /// + /// The current status of the workitem. + /// + /// The current status of the workitem. + public string? Progress { get; set; } + + /// + /// The detailed report about the workitem, report url is valid for 1 hours from first receiving it. + /// + /// The detailed report about the workitem, report url is valid for 1 hours from first receiving it. + public string? ReportUrl { get; set; } + + /// + /// The debug information for the workitem, the url is valid for 1 hours from first receiving it. + /// + /// The debug information for the workitem, the url is valid for 1 hours from first receiving it. + public Uri? DebugInfoUrl { get; private set; } + + /// + /// Basic statistics about workitem processing. + /// + /// Basic statistics about workitem processing. + public Statistics? Stats { get; set; } + + public string? Id { get; set; } + + public static T ToEnum(string str) + { + var enumType = typeof(T); + foreach (var name in Enum.GetNames(enumType)) + { + var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField(name).GetCustomAttributes(typeof(EnumMemberAttribute), true)).Single(); + if (enumMemberAttribute.Value == str) return (T)Enum.Parse(enumType, name); + } + //throw exception or whatever handling you want or + return default(T); + } + } +} diff --git a/src/Application/WorkItems/Queries/GetWorkItem/GetWorkItemsQueryHandler.cs b/src/Application/WorkItems/Queries/GetWorkItem/GetWorkItemsQueryHandler.cs index 7fdd969..a38f5b2 100644 --- a/src/Application/WorkItems/Queries/GetWorkItem/GetWorkItemsQueryHandler.cs +++ b/src/Application/WorkItems/Queries/GetWorkItem/GetWorkItemsQueryHandler.cs @@ -20,9 +20,9 @@ public GetWorkItemsQueryHandler(IAppDbContext context, ISavedWorkItemService sav public async Task> Handle(GetWorkItemsQuery request, CancellationToken cancellationToken) { - List savedWorkItems = await _savedWorkItemService.GetSavedWorkItems(request.UserId); + List savedWorkItems = _savedWorkItemService.GetSavedWorkItems(request.UserId); - return savedWorkItems; + return await Task.FromResult>(savedWorkItems); } } } diff --git a/src/Domain/Entities/ForgeConfiguration.cs b/src/Domain/Entities/ForgeConfiguration.cs index ef07484..92f58f2 100644 --- a/src/Domain/Entities/ForgeConfiguration.cs +++ b/src/Domain/Entities/ForgeConfiguration.cs @@ -7,7 +7,7 @@ public class ForgeConfiguration public string InputBucketKey { get; set; } public string OutputBucketKey { get; set; } public ApplicationDetail ApplicationDetail { get; set; } - + public string CallbackUrl { get; set; } } public class ApplicationDetail diff --git a/src/WebApp/ConfigureServices.cs b/src/WebApp/ConfigureServices.cs index d21f391..9064c0c 100644 --- a/src/WebApp/ConfigureServices.cs +++ b/src/WebApp/ConfigureServices.cs @@ -1,6 +1,46 @@ -namespace WebApp +using Infrastructure; +using Microsoft.AspNetCore.Identity; +using Microsoft.OpenApi.Models; + +namespace WebApp { - public class ConfigureServices + public static class ConfigureServices { + public static IServiceCollection AddIdentityServices(this IServiceCollection services) + { + + services.AddSwaggerGen(c => + { + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + }, + Scheme = "oauth2", + Name = "Bearer", + In = ParameterLocation.Header, + + }, + new List() + } + }); + }); + + return services; + } } } diff --git a/src/WebApp/Controllers/CallbackController.cs b/src/WebApp/Controllers/CallbackController.cs new file mode 100644 index 0000000..ea8d85c --- /dev/null +++ b/src/WebApp/Controllers/CallbackController.cs @@ -0,0 +1,47 @@ +using Application.Files.Commands.CompleteUpload; +using Application.Files.Commands.CreateBucket; +using Application.Files.Queries.GetDownloadUrlQuery; +using Application.Files.Queries.GetFiles; +using Application.Files.Queries.GetUploadUrl; +using Application.WorkItems.Commands.CreateWorkItem; +using Application.WorkItems.Commands.UpdateWorkItemStatus; +using Application.WorkItems.Queries.GetWorkItem; +using Application.WorkItems.Queries.GetWorkItemStatus; +using Autodesk.Forge.DesignAutomation.Model; +using Autodesk.Oss.Model; +using Domain.Entities; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Identity.Web; +using WebApp.Models; +using Status = Autodesk.Forge.DesignAutomation.Model.Status; + + +namespace WebApp.Controllers +{ + /// + /// Manage files + /// + [ApiController] + [Route("[controller]")] + public class CallbackController : BaseController + { + /// + /// The onComplete callback controler + /// + /// + [HttpPost] + [Route("oncomplete")] + public async Task OnComplete(WorkItemStatusDTO workItemStatusBody) + { + WorkItemStatus workItemStatus = new WorkItemStatus(); + + workItemStatus.Status = WorkItemStatusDTO.ToEnum(workItemStatusBody.Status); + workItemStatus.Stats = workItemStatusBody.Stats; + workItemStatus.ReportUrl = workItemStatusBody.ReportUrl; + workItemStatus.Progress = workItemStatusBody.Progress; + workItemStatus.Id = workItemStatusBody.Id; + + await Mediator.Send(new UpdateWorkItemStatusCommand(workItemStatus)); + } + } +} diff --git a/src/WebApp/Controllers/FilesController.cs b/src/WebApp/Controllers/FilesController.cs index 5cd4e0c..baf256a 100644 --- a/src/WebApp/Controllers/FilesController.cs +++ b/src/WebApp/Controllers/FilesController.cs @@ -53,9 +53,9 @@ public async Task CompleteUpload(UploadCompletion upload /// [HttpGet] [Route("download")] - public async Task GetDownloadUrls(string objectKey, string fileName) + public async Task GetDownloadUrls(string objectKey, string fileName) { - Signeds3downloadResponse vm = await Mediator.Send(new GetDownloadUrlQuery(objectKey, fileName)); + Signeds3downloadResponse? vm = await Mediator.Send(new GetDownloadUrlQuery(objectKey, fileName)); return vm; } diff --git a/src/WebApp/Program.cs b/src/WebApp/Program.cs index 622eb01..0d09546 100644 --- a/src/WebApp/Program.cs +++ b/src/WebApp/Program.cs @@ -18,6 +18,7 @@ public static async Task Main(string[] args) builder.Logging.ClearProviders(); builder.Logging.AddConsole(); + builder.Services.AddIdentityServices(); string userAssignedClientId = builder.Configuration["ManagedIdentityClientId"]; diff --git a/src/WebApp/appsettings.json b/src/WebApp/appsettings.json index 627f94c..dc83031 100644 --- a/src/WebApp/appsettings.json +++ b/src/WebApp/appsettings.json @@ -44,6 +44,7 @@ "ClientSecret": "", "InputBucketKey": "revit-convert-bucket", "OutputBucketKey": "ifc-convert-bucket", + "CallbackUrl": "https://mouse-joint-mutt.ngrok-free.app", "ApplicationDetail": { "Nickname": "RevitToIfc_dev", "AppBundleName": "RvtToIfc", diff --git a/src/WebClient/Components/Convert/WorkItemsList.razor b/src/WebClient/Components/Convert/WorkItemsList.razor index 4bebb44..20ccc47 100644 --- a/src/WebClient/Components/Convert/WorkItemsList.razor +++ b/src/WebClient/Components/Convert/WorkItemsList.razor @@ -2,7 +2,7 @@ - + diff --git a/src/WebClient/Components/Convert/WorkItemsList.razor.cs b/src/WebClient/Components/Convert/WorkItemsList.razor.cs index faa1d75..cb9f4fa 100644 --- a/src/WebClient/Components/Convert/WorkItemsList.razor.cs +++ b/src/WebClient/Components/Convert/WorkItemsList.razor.cs @@ -36,7 +36,7 @@ public async Task DownloadUrl(SavedWorkItemDTO savedWorkItem) Signeds3downloadResponse signedDownload = await _dataService.GetDownloadUrl(savedWorkItem.ObjectKey, savedWorkItem.FileName); - savedWorkItem.DownloadUrl = signedDownload.Url; + savedWorkItem.DownloadUrl = signedDownload?.Url; } } } diff --git a/src/WebClient/Models/RevitFile.cs b/src/WebClient/Models/RevitFile.cs index 86fa1b3..327cb6a 100644 --- a/src/WebClient/Models/RevitFile.cs +++ b/src/WebClient/Models/RevitFile.cs @@ -168,7 +168,7 @@ public async Task UploadFile(IDataService dataService, IUploadService uploadServ return; case Models.Status.Success: Signeds3downloadResponse signedDownload = await dataService.GetDownloadUrl(objectKey, Name); - DownloadUrl = signedDownload.Url; + DownloadUrl = signedDownload?.Url; Status = FileStatus.Converted; return; default: