diff --git a/Backend/Backend.csproj b/Backend/Backend.csproj index e3c1125..ae412bd 100644 --- a/Backend/Backend.csproj +++ b/Backend/Backend.csproj @@ -18,6 +18,7 @@ + diff --git a/Backend/Controllers/ModelDerivativeController.cs b/Backend/Controllers/ModelDerivativeController.cs new file mode 100644 index 0000000..951cae1 --- /dev/null +++ b/Backend/Controllers/ModelDerivativeController.cs @@ -0,0 +1,53 @@ +using Autodesk.Forge; +using Autodesk.Forge.Model; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace forgeSample.Controllers +{ + [ApiController] + public class ModelDerivativeController : ControllerBase + { + /// + /// Start the translation job for a give bucketKey/objectName + /// + /// + /// + [HttpPost] + [Route("api/forge/modelderivative/jobs")] + public async Task TranslateObject([FromBody] TranslateObjectModel objModel) + { + dynamic oauth = await OAuthController.GetInternalAsync(); + + // prepare the payload + List outputs = new List() + { + new JobPayloadItem( + JobPayloadItem.TypeEnum.Svf, + new List() + { + JobPayloadItem.ViewsEnum._2d, + JobPayloadItem.ViewsEnum._3d + }) + }; + JobPayload job; + job = new JobPayload(new JobPayloadInput(objModel.objectName), new JobPayloadOutput(outputs)); + + // start the translation + DerivativesApi derivative = new DerivativesApi(); + derivative.Configuration.AccessToken = oauth.access_token; + dynamic jobPosted = await derivative.TranslateAsync(job); + return jobPosted; + } + + /// + /// Model for TranslateObject method + /// + public class TranslateObjectModel + { + public string bucketKey { get; set; } + public string objectName { get; set; } + } + } +} diff --git a/Backend/Controllers/OAuthController.cs b/Backend/Controllers/OAuthController.cs new file mode 100644 index 0000000..ddf54b5 --- /dev/null +++ b/Backend/Controllers/OAuthController.cs @@ -0,0 +1,70 @@ +using Autodesk.Forge; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; + +namespace forgeSample.Controllers +{ + [ApiController] + public class OAuthController : ControllerBase + { + // As both internal & public tokens are used for all visitors + // we don't need to request a new token on every request, so let's + // cache them using static variables. Note we still need to refresh + // them after the expires_in time (in seconds) + private static dynamic InternalToken { get; set; } + private static dynamic PublicToken { get; set; } + + /// + /// Get access token with public (viewables:read) scope + /// + [HttpGet] + [Route("api/forge/oauth/token")] + public async Task GetPublicAsync() + { + if (PublicToken == null || PublicToken.ExpiresAt < DateTime.UtcNow) + { + PublicToken = await Get2LeggedTokenAsync(new Scope[] { Scope.ViewablesRead }); + PublicToken.ExpiresAt = DateTime.UtcNow.AddSeconds(PublicToken.expires_in); + } + return PublicToken; + } + + /// + /// Get access token with internal (write) scope + /// + public static async Task GetInternalAsync() + { + if (InternalToken == null || InternalToken.ExpiresAt < DateTime.UtcNow) + { + InternalToken = await Get2LeggedTokenAsync(new Scope[] { Scope.BucketCreate, Scope.BucketRead, Scope.BucketDelete, Scope.DataRead, Scope.DataWrite, Scope.DataCreate, Scope.CodeAll }); + InternalToken.ExpiresAt = DateTime.UtcNow.AddSeconds(InternalToken.expires_in); + } + + return InternalToken; + } + + /// + /// Get the access token from Autodesk + /// + private static async Task Get2LeggedTokenAsync(Scope[] scopes) + { + TwoLeggedApi oauth = new TwoLeggedApi(); + string grantType = "client_credentials"; + dynamic bearer = await oauth.AuthenticateAsync( + GetAppSetting("FORGE_CLIENT_ID"), + GetAppSetting("FORGE_CLIENT_SECRET"), + grantType, + scopes); + return bearer; + } + + /// + /// Reads appsettings from web.config + /// + public static string GetAppSetting(string settingKey) + { + return Environment.GetEnvironmentVariable(settingKey).Trim(); + } + } +} \ No newline at end of file diff --git a/Backend/Controllers/OSSController.cs b/Backend/Controllers/OSSController.cs new file mode 100644 index 0000000..340574c --- /dev/null +++ b/Backend/Controllers/OSSController.cs @@ -0,0 +1,150 @@ +using Autodesk.Forge; +using Autodesk.Forge.Model; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace forgeSample.Controllers +{ + [ApiController] + public class OSSController : ControllerBase + { + private IWebHostEnvironment _env; + public OSSController(IWebHostEnvironment env) { _env = env; } + public string ClientId { get { return OAuthController.GetAppSetting("FORGE_CLIENT_ID").ToLower(); } } + + /// + /// Return list of buckets (id=#) or list of objects (id=bucketKey) + /// + [HttpGet] + [Route("api/forge/oss/buckets")] + public async Task> GetOSSAsync(string id) + { + IList nodes = new List(); + dynamic oauth = await OAuthController.GetInternalAsync(); + + if (id == "#") // root + { + // in this case, let's return all buckets + BucketsApi appBckets = new BucketsApi(); + appBckets.Configuration.AccessToken = oauth.access_token; + + // to simplify, let's return only the first 100 buckets + dynamic buckets = await appBckets.GetBucketsAsync("US", 100); + foreach (KeyValuePair bucket in new DynamicDictionaryItems(buckets.items)) + { + nodes.Add(new TreeNode(bucket.Value.bucketKey, bucket.Value.bucketKey.Replace(ClientId + "-", string.Empty), "bucket", true)); + } + } + else + { + // as we have the id (bucketKey), let's return all + ObjectsApi objects = new ObjectsApi(); + objects.Configuration.AccessToken = oauth.access_token; + var objectsList = await objects.GetObjectsAsync(id, 100); + foreach (KeyValuePair objInfo in new DynamicDictionaryItems(objectsList.items)) + { + nodes.Add(new TreeNode(Base64Encode((string)objInfo.Value.objectId), + objInfo.Value.objectKey, "object", false)); + } + } + return nodes; + } + + /// + /// Model data for jsTree used on GetOSSAsync + /// + public class TreeNode + { + public TreeNode(string id, string text, string type, bool children) + { + this.id = id; + this.text = text; + this.type = type; + this.children = children; + } + + public string id { get; set; } + public string text { get; set; } + public string type { get; set; } + public bool children { get; set; } + } + + /// + /// Create a new bucket + /// + [HttpPost] + [Route("api/forge/oss/buckets")] + public async Task CreateBucket([FromBody] CreateBucketModel bucket) + { + BucketsApi buckets = new BucketsApi(); + dynamic token = await OAuthController.GetInternalAsync(); + buckets.Configuration.AccessToken = token.access_token; + PostBucketsPayload bucketPayload = new PostBucketsPayload(string.Format("{0}-{1}", ClientId, bucket.bucketKey.ToLower()), null, + PostBucketsPayload.PolicyKeyEnum.Transient); + return await buckets.CreateBucketAsync(bucketPayload, "US"); + } + + /// + /// Input model for CreateBucket method + /// + public class CreateBucketModel + { + public string bucketKey { get; set; } + } + + /// + /// Receive a file from the client and upload to the bucket + /// + /// + [HttpPost] + [Route("api/forge/oss/objects")] + public async Task UploadObject([FromForm] UploadFile input) + { + // save the file on the server + var fileSavePath = Path.Combine(_env.WebRootPath, Path.GetFileName(input.fileToUpload.FileName)); + using (var stream = new FileStream(fileSavePath, FileMode.Create)) + await input.fileToUpload.CopyToAsync(stream); + + + // get the bucket... + dynamic oauth = await OAuthController.GetInternalAsync(); + ObjectsApi objects = new ObjectsApi(); + objects.Configuration.AccessToken = oauth.access_token; + + // upload the file/object, which will create a new object + dynamic uploadedObj; + using (StreamReader streamReader = new StreamReader(fileSavePath)) + { + uploadedObj = await objects.UploadObjectAsync(input.bucketKey, + Path.GetFileName(input.fileToUpload.FileName), (int)streamReader.BaseStream.Length, streamReader.BaseStream, + "application/octet-stream"); + } + + // cleanup + System.IO.File.Delete(fileSavePath); + var a = Base64Encode((string)uploadedObj.objectId); + //var test = new TreeNode(Base64Encode((string)uploadedObj.objectId), objInfo.Value.objectKey, "object", false) + + return uploadedObj; + } + + public class UploadFile + { + public string bucketKey { get; set; } + public IFormFile fileToUpload { get; set; } + } + + /// + /// Base64 enconde a string + /// + public static string Base64Encode(string plainText) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + } +} \ No newline at end of file diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json index 0cf5fad..5fb22ee 100644 --- a/Backend/Properties/launchSettings.json +++ b/Backend/Properties/launchSettings.json @@ -13,7 +13,10 @@ "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation", + "FORGE_CALLBACK_URL": "http://localhost:3000/api/forge/callback/oauth", + "FORGE_CLIENT_ID": "TA3hqsFfzQbNOUXKpldKUKSew4SJ21w5", + "FORGE_CLIENT_SECRET": "D002f92d839144f8" } }, "Backend": { diff --git a/Backend/Views/BuildInfo/Index.cshtml b/Backend/Views/BuildInfo/Index.cshtml index 3ed27d0..c73ed0c 100644 --- a/Backend/Views/BuildInfo/Index.cshtml +++ b/Backend/Views/BuildInfo/Index.cshtml @@ -704,6 +704,25 @@ //#region 變更樓層平面圖 function changeImage(input) { $(`#map_file_preview_modal`).attr("data-src", window.URL.createObjectURL(input.files[0])); + + console.log("---- abc test ---"); + var file = input.files[0]; + + var formData = new FormData(); + formData.append('fileToUpload', file); + formData.append('bucketKey', "ta3hqsffzqbnouxkpldkuksew4sj21w5-bims_models"); + + $.ajax({ + url: '/api/forge/oss/objects', + data: formData, + processData: false, + contentType: false, + type: 'POST', + success: function (data) { + $('#appBuckets').jstree(true).refresh_node(node); + _this.value = ''; + } + }); } //#endregion diff --git a/Backend/Views/BuildInfo/_BuildInfo.cshtml b/Backend/Views/BuildInfo/_BuildInfo.cshtml index 231c234..f3fd29b 100644 --- a/Backend/Views/BuildInfo/_BuildInfo.cshtml +++ b/Backend/Views/BuildInfo/_BuildInfo.cshtml @@ -58,7 +58,7 @@
- +