Skip to content
This repository was archived by the owner on Aug 15, 2023. It is now read-only.

Commit e23c6b9

Browse files
authored
Merge pull request #319 from Developer-Autodesk/poc/INVGEN-45244-to-be-merged-to-master
Refactorings of code done while working on POC for Inventor Configurator CPQ integration
2 parents d34f666 + 6240039 commit e23c6b9

File tree

8 files changed

+159
-76
lines changed

8 files changed

+159
-76
lines changed

WebApplication.Tests/InitializerTestBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ public InitializerTestBase(DefaultProjectsConfiguration defaultProjectsConfigura
105105
// TODO: linkGenerator should be mocked
106106
var dtoGenerator = new DtoGenerator(linkGenerator: null, localCache);
107107
var projectWork = new ProjectWork(new NullLogger<ProjectWork>(), arranger, fdaClient, dtoGenerator, userResolver);
108+
109+
var projectService = new ProjectService(new NullLogger<ProjectService>(), userResolver, projectWork);
110+
108111
initializer = new Initializer(new NullLogger<Initializer>(), fdaClient,
109-
defaultProjectsOptions, projectWork, userResolver, localCache, bucketPrefixProvider);
112+
defaultProjectsOptions, projectWork, userResolver, localCache,
113+
projectService, bucketPrefixProvider);
110114

111115
testFileDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
112116
httpClient = new HttpClient();

WebApplication.Tests/WebApplication.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.0" />
1011
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
1112
<PackageReference Include="xunit" Version="2.4.0" />
1213
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />

WebApplication/Controllers/JobsHub.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,10 @@ public async Task SendErrorAsync(object arg0, object arg1, object arg2)
117117
private readonly Sender _sender;
118118
private readonly Uploads _uploads;
119119
private readonly DtoGenerator _dtoGenerator;
120+
private readonly ProjectService _projectService;
120121

121122
public JobsHub(ILogger<JobsHub> logger, ProjectWork projectWork, LinkGenerator linkGenerator, UserResolver userResolver,
122-
ProfileProvider profileProvider, Uploads uploads, DtoGenerator dtoGenerator)
123+
ProfileProvider profileProvider, Uploads uploads, DtoGenerator dtoGenerator, ProjectService projectService)
123124
{
124125
_logger = logger;
125126
_projectWork = projectWork;
@@ -128,6 +129,7 @@ public JobsHub(ILogger<JobsHub> logger, ProjectWork projectWork, LinkGenerator l
128129
_userResolver = userResolver;
129130
_uploads = uploads;
130131
_dtoGenerator = dtoGenerator;
132+
_projectService = projectService;
131133

132134
_sender = new Sender(this);
133135
}

WebApplication/Controllers/ProjectsController.cs

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
using System;
2020
using System.Collections.Generic;
2121
using System.IO;
22-
using System.Linq;
2322
using System.Threading.Tasks;
2423
using Microsoft.AspNetCore.Mvc;
2524
using Microsoft.Extensions.Logging;
2625
using WebApplication.Definitions;
2726
using WebApplication.Services;
27+
using WebApplication.Services.Exceptions;
2828
using WebApplication.State;
2929
using WebApplication.Utilities;
3030
using Project = WebApplication.State.Project;
@@ -40,14 +40,16 @@ public class ProjectsController : ControllerBase
4040
private readonly UserResolver _userResolver;
4141
private readonly ProfileProvider _profileProvider;
4242
private readonly Uploads _uploads;
43+
private readonly ProjectService _projectService;
4344

44-
public ProjectsController(ILogger<ProjectsController> logger, DtoGenerator dtoGenerator, UserResolver userResolver, ProfileProvider profileProvider, Uploads uploads)
45+
public ProjectsController(ILogger<ProjectsController> logger, DtoGenerator dtoGenerator, UserResolver userResolver, ProfileProvider profileProvider, Uploads uploads, ProjectService projectService)
4546
{
4647
_logger = logger;
4748
_dtoGenerator = dtoGenerator;
4849
_userResolver = userResolver;
4950
_profileProvider = profileProvider;
5051
_uploads = uploads;
52+
_projectService = projectService;
5153
}
5254

5355
[HttpGet("")]
@@ -56,7 +58,7 @@ public async Task<IEnumerable<ProjectDTO>> ListAsync()
5658
var bucket = await _userResolver.GetBucketAsync(tryToCreate: true);
5759

5860
var projectDTOs = new List<ProjectDTO>();
59-
foreach (var projectName in await GetProjectNamesAsync(bucket))
61+
foreach (var projectName in await _projectService.GetProjectNamesAsync(bucket))
6062
{
6163
// TODO: in future bad projects should not affect project listing. It's a workaround
6264
try
@@ -79,7 +81,7 @@ public async Task<IEnumerable<ProjectDTO>> ListAsync()
7981
}
8082

8183
[HttpPost]
82-
public async Task<ActionResult<ProjectDTO>> CreateProject([FromForm]NewProjectModel projectModel)
84+
public async Task<ActionResult<string>> CreateProject([FromForm]NewProjectModel projectModel)
8385
{
8486
if (!_profileProvider.IsAuthenticated)
8587
{
@@ -88,10 +90,10 @@ public async Task<ActionResult<ProjectDTO>> CreateProject([FromForm]NewProjectMo
8890
}
8991

9092
var projectName = Path.GetFileNameWithoutExtension(projectModel.package.FileName);
91-
var bucket = await _userResolver.GetBucketAsync(true);
9293

9394
// Check if project already exists
94-
foreach (var existingProjectName in await GetProjectNamesAsync(bucket))
95+
var projectNames = await _projectService.GetProjectNamesAsync();
96+
foreach (var existingProjectName in projectNames)
9597
{
9698
if (projectName == existingProjectName)
9799
{
@@ -105,7 +107,7 @@ public async Task<ActionResult<ProjectDTO>> CreateProject([FromForm]NewProjectMo
105107
Name = projectName,
106108
TopLevelAssembly = projectModel.root
107109
};
108-
110+
109111
// download file locally (a place to improve... would be good to stream it directly to OSS)
110112
var fileName = Path.GetTempFileName();
111113
await using (var fileWriteStream = System.IO.File.OpenWrite(fileName))
@@ -116,8 +118,9 @@ public async Task<ActionResult<ProjectDTO>> CreateProject([FromForm]NewProjectMo
116118
var packageId = Guid.NewGuid().ToString();
117119
_uploads.AddUploadData(packageId, projectInfo, fileName);
118120

119-
return Ok(packageId);
121+
_logger.LogInformation($"created project with packageId {packageId}");
120122

123+
return Ok(packageId);
121124
}
122125

123126
[HttpDelete]
@@ -129,53 +132,9 @@ public async Task<StatusCodeResult> DeleteProjects([FromBody] List<string> proje
129132
return BadRequest();
130133
}
131134

132-
var bucket = await _userResolver.GetBucketAsync(true);
133-
134-
// collect all oss objects for all provided projects
135-
var tasks = new List<Task>();
136-
137-
foreach (var projectName in projectNameList)
138-
{
139-
tasks.Add(bucket.DeleteObjectAsync(Project.ExactOssName(projectName)));
140-
141-
foreach (var searchMask in ONC.ProjectFileMasks(projectName))
142-
{
143-
var objects = await bucket.GetObjectsAsync(searchMask);
144-
foreach (var objectDetail in objects)
145-
{
146-
tasks.Add(bucket.DeleteObjectAsync(objectDetail.ObjectKey));
147-
}
148-
}
149-
}
150-
151-
// delete the OSS objects
152-
await Task.WhenAll(tasks);
153-
for (var i = 0; i < tasks.Count; i++)
154-
{
155-
if (tasks[i].IsFaulted)
156-
{
157-
_logger.LogError($"Failed to delete file #{i}");
158-
}
159-
}
160-
161-
// delete local cache for all provided projects
162-
foreach (var projectName in projectNameList)
163-
{
164-
var projectStorage = await _userResolver.GetProjectStorageAsync(projectName, ensureDir: false);
165-
projectStorage.DeleteLocal();
166-
}
135+
await _projectService.DeleteProjects(projectNameList);
167136

168137
return NoContent();
169138
}
170-
171-
/// <summary>
172-
/// Get list of project names for a bucket.
173-
/// </summary>
174-
private async Task<ICollection<string>> GetProjectNamesAsync(OssBucket bucket)
175-
{
176-
return (await bucket.GetObjectsAsync(ONC.ProjectsMask))
177-
.Select(objDetails => ONC.ToProjectName(objDetails.ObjectKey))
178-
.ToList();
179-
}
180139
}
181140
}

WebApplication/Initializer.cs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,21 @@ public class Initializer
4646
private readonly BucketPrefixProvider _bucketPrefixProvider;
4747
private readonly LocalCache _localCache;
4848
private readonly OssBucket _bucket;
49+
private readonly ProjectService _projectService;
4950

5051
/// <summary>
5152
/// Constructor.
5253
/// </summary>
5354
public Initializer(ILogger<Initializer> logger, FdaClient fdaClient, IOptions<DefaultProjectsConfiguration> optionsAccessor,
54-
ProjectWork projectWork, UserResolver userResolver, LocalCache localCache, BucketPrefixProvider bucketPrefixProvider)
55+
ProjectWork projectWork, UserResolver userResolver, LocalCache localCache, ProjectService projectService,
56+
BucketPrefixProvider bucketPrefixProvider)
5557
{
5658
_logger = logger;
5759
_fdaClient = fdaClient;
5860
_projectWork = projectWork;
5961
_userResolver = userResolver;
6062
_localCache = localCache;
63+
_projectService = projectService;
6164
_defaultProjectsConfiguration = optionsAccessor.Value;
6265

6366
// bucket for anonymous user
@@ -94,28 +97,10 @@ await Task.WhenAll(
9497

9598
_logger.LogInformation($"Bucket {_bucket.BucketKey} created");
9699

97-
// OSS bucket might be not ready yet, so repeat attempts
98-
var waitForBucketPolicy = Policy
99-
.Handle<ApiException>(e => e.ErrorCode == StatusCodes.Status404NotFound)
100-
.WaitAndRetryAsync(
101-
retryCount: 4,
102-
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
103-
(exception, timeSpan) => _logger.LogWarning("Cannot get fresh OSS bucket. Repeating")
104-
);
105-
106100
// publish default project files (specified by the appsettings.json)
107101
foreach (DefaultProjectConfiguration defaultProjectConfig in _defaultProjectsConfiguration.Projects)
108102
{
109-
var projectUrl = defaultProjectConfig.Url;
110-
var project = await _userResolver.GetProjectAsync(defaultProjectConfig.Name);
111-
112-
_logger.LogInformation($"Launching 'TransferData' for {projectUrl}");
113-
string signedUrl = await waitForBucketPolicy.ExecuteAsync(async () => await _bucket.CreateSignedUrlAsync(project.OSSSourceModel, ObjectAccess.ReadWrite));
114-
115-
// TransferData from s3 to temporary oss url
116-
await _projectWork.FileTransferAsync(projectUrl, signedUrl);
117-
118-
_logger.LogInformation($"'TransferData' for {projectUrl} is done.");
103+
var signedUrl = await _projectService.TransferProjectToOssAsync(_bucket, defaultProjectConfig);
119104

120105
await _projectWork.AdoptAsync(defaultProjectConfig, signedUrl);
121106
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace WebApplication.Services.Exceptions
4+
{
5+
public class ProjectAlreadyExistsException : Exception
6+
{
7+
private readonly string _projectName;
8+
9+
public ProjectAlreadyExistsException(string projectName) : base($"Project with name {projectName} already exists")
10+
{
11+
_projectName = projectName;
12+
}
13+
}
14+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using Autodesk.Forge.Client;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.Logging;
9+
using Polly;
10+
using Polly.Retry;
11+
using WebApplication.Definitions;
12+
using WebApplication.Processing;
13+
using WebApplication.Services.Exceptions;
14+
using WebApplication.State;
15+
using WebApplication.Utilities;
16+
17+
namespace WebApplication.Services
18+
{
19+
public class ProjectService
20+
{
21+
private readonly ILogger<ProjectService> _logger;
22+
private readonly UserResolver _userResolver;
23+
private readonly ProjectWork _projectWork;
24+
private readonly RetryPolicy _waitForBucketPolicy;
25+
26+
public ProjectService(ILogger<ProjectService> logger, UserResolver userResolver, ProjectWork projectWork)
27+
{
28+
_logger = logger;
29+
_userResolver = userResolver;
30+
_projectWork = projectWork;
31+
32+
_waitForBucketPolicy = Policy
33+
.Handle<ApiException>(e => e.ErrorCode == StatusCodes.Status404NotFound)
34+
.WaitAndRetryAsync(
35+
retryCount: 4,
36+
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
37+
(exception, timeSpan) => _logger.LogWarning("Cannot get fresh OSS bucket. Repeating")
38+
);
39+
}
40+
41+
public async Task<string> TransferProjectToOssAsync(OssBucket bucket, DefaultProjectConfiguration projectConfig)
42+
{
43+
_logger.LogInformation($"Bucket {bucket.BucketKey} created");
44+
45+
var projectUrl = projectConfig.Url;
46+
var project = await _userResolver.GetProjectAsync(projectConfig.Name);
47+
48+
_logger.LogInformation($"Launching 'TransferData' for {projectUrl}");
49+
50+
// OSS bucket might be not ready yet, so repeat attempts
51+
string signedUrl = await _waitForBucketPolicy.ExecuteAsync(async () =>
52+
await bucket.CreateSignedUrlAsync(project.OSSSourceModel, ObjectAccess.ReadWrite));
53+
54+
// TransferData from outside URL to temporary oss url
55+
await _projectWork.FileTransferAsync(projectUrl, signedUrl);
56+
57+
_logger.LogInformation($"'TransferData' for {projectUrl} is done.");
58+
59+
return signedUrl;
60+
}
61+
62+
/// <summary>
63+
/// Get list of project names for a bucket.
64+
/// </summary>
65+
public async Task<ICollection<string>> GetProjectNamesAsync(OssBucket bucket = null)
66+
{
67+
bucket ??= await _userResolver.GetBucketAsync(true);
68+
69+
var objectDetails = (await bucket.GetObjectsAsync(ONC.ProjectsMask));
70+
var projectNames = objectDetails
71+
.Select(objDetails => ONC.ToProjectName(objDetails.ObjectKey))
72+
.ToList();
73+
return projectNames;
74+
}
75+
76+
public async Task DeleteProjects(ICollection<string> projectNameList, OssBucket bucket = null)
77+
{
78+
bucket ??= await _userResolver.GetBucketAsync(true);
79+
80+
_logger.LogInformation($"deleting projects [{string.Join(", ", projectNameList)}] from bucket {bucket.BucketKey}");
81+
82+
// collect all oss objects for all provided projects
83+
var tasks = new List<Task>();
84+
85+
foreach (var projectName in projectNameList)
86+
{
87+
tasks.Add(bucket.DeleteObjectAsync(Project.ExactOssName(projectName)));
88+
89+
foreach (var searchMask in ONC.ProjectFileMasks(projectName))
90+
{
91+
var objects = await bucket.GetObjectsAsync(searchMask);
92+
foreach (var objectDetail in objects)
93+
{
94+
tasks.Add(bucket.DeleteObjectAsync(objectDetail.ObjectKey));
95+
}
96+
}
97+
}
98+
99+
// delete the OSS objects
100+
await Task.WhenAll(tasks);
101+
for (var i = 0; i < tasks.Count; i++)
102+
{
103+
if (tasks[i].IsFaulted)
104+
{
105+
_logger.LogError($"Failed to delete file #{i}");
106+
}
107+
}
108+
109+
// delete local cache for all provided projects
110+
foreach (var projectName in projectNameList)
111+
{
112+
var projectStorage = await _userResolver.GetProjectStorageAsync(projectName, ensureDir: false);
113+
projectStorage.DeleteLocal();
114+
}
115+
}
116+
}
117+
}

WebApplication/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@ public void ConfigureServices(IServiceCollection services)
110110
services.AddSingleton<BucketPrefixProvider>();
111111
services.AddSingleton<LocalCache>();
112112
services.AddSingleton<Uploads>();
113+
services.AddScoped<ProjectService>();
113114

114-
if(Configuration.GetValue<bool>("migration"))
115+
if (Configuration.GetValue<bool>("migration"))
115116
{
116117
services.AddHostedService<MigrationApp.Worker>();
117118
}

0 commit comments

Comments
 (0)