diff --git a/Content/icons/web_services/ms-word-icon-64x64.png b/Content/icons/web_services/ms-word-icon-64x64.png new file mode 100644 index 0000000000..bc2ab64f8f Binary files /dev/null and b/Content/icons/web_services/ms-word-icon-64x64.png differ diff --git a/Fr8Infrastructure.NET/Data/Control/Control.cs b/Fr8Infrastructure.NET/Data/Control/Control.cs index e1857d70e2..61f00db5cf 100644 --- a/Fr8Infrastructure.NET/Data/Control/Control.cs +++ b/Fr8Infrastructure.NET/Data/Control/Control.cs @@ -399,6 +399,9 @@ public TextBlock() public class FilePicker : ControlDefinitionDTO { + [JsonProperty("fileextensions")] + public string FileExtensions { get; set; } + public FilePicker() { Type = ControlTypes.FilePicker; diff --git a/Tests/terminalGoogleTests/Integration/Terminal_Discover_v1_Tests.cs b/Tests/terminalGoogleTests/Integration/Terminal_Discover_v1_Tests.cs index dc3e68f3e5..57760c4223 100644 --- a/Tests/terminalGoogleTests/Integration/Terminal_Discover_v1_Tests.cs +++ b/Tests/terminalGoogleTests/Integration/Terminal_Discover_v1_Tests.cs @@ -31,18 +31,20 @@ public async Task Terminal_Google_Discover() Assert.IsNotNull(googleTerminalDiscoveryResponse, "Terminal Google discovery did not happen."); Assert.IsNotNull(googleTerminalDiscoveryResponse.Activities, "Google terminal does not have actions."); - Assert.AreEqual(4, googleTerminalDiscoveryResponse.Activities.Count, "Google terminal expected 4 actions."); + Assert.AreEqual(5, googleTerminalDiscoveryResponse.Activities.Count, "Google terminal expected 5 actions."); Assert.AreEqual("terminalGoogle", googleTerminalDiscoveryResponse.Definition.Name); Assert.AreEqual("Google", googleTerminalDiscoveryResponse.Definition.Label); Assert.AreEqual(googleTerminalDiscoveryResponse.Activities.Any(a => a.Name == "Get_Google_Sheet_Data"), true, "Action Get_Google_Sheet_Data was not loaded"); Assert.AreEqual(googleTerminalDiscoveryResponse.Activities.Any(a => a.Name == "Monitor_Form_Responses"), true, "Action Monitor_Form_Responses was not loaded"); Assert.AreEqual(googleTerminalDiscoveryResponse.Activities.Any(a => a.Name == "Save_To_Google_Sheet"), true, "Action Save_To_Google_Sheet was not loaded"); Assert.AreEqual(googleTerminalDiscoveryResponse.Activities.Any(a => a.Name == "Monitor_Gmail_Inbox"), true, "Action Monitor_Gmail_Inbox was not loaded"); + Assert.AreEqual(googleTerminalDiscoveryResponse.Activities.Any(a => a.Name == "Create_Google_Doc"), true, "Action Create_Google_Doc was not loaded"); //Check Activities Categories Assert.True(googleTerminalDiscoveryResponse.Activities.Single(a => a.Name == "Get_Google_Sheet_Data").Categories.Any(x => x.Id == ActivityCategories.ReceiveId), "Activity Get_Google_Sheet_Data is not of Category Receivers"); Assert.True(googleTerminalDiscoveryResponse.Activities.Single(a => a.Name == "Monitor_Form_Responses").Categories.Any(x => x.Id == ActivityCategories.MonitorId), "Activity Monitor_Form_Responses is not of Category Monitors"); Assert.True(googleTerminalDiscoveryResponse.Activities.Single(a => a.Name == "Save_To_Google_Sheet").Categories.Any(x => x.Id == ActivityCategories.ForwardId), "Activity Save_To_Google_Sheet is not of Category Forwarders"); Assert.True(googleTerminalDiscoveryResponse.Activities.Single(a => a.Name == "Monitor_Gmail_Inbox").Categories.Any(x => x.Id == ActivityCategories.MonitorId), "Activity Monitor_Gmail_Inbox is not of Category Monitors"); + Assert.True(googleTerminalDiscoveryResponse.Activities.Single(a => a.Name == "Create_Google_Doc").Categories.Any(x => x.Id == ActivityCategories.ForwardId), "Activity Create_Google_Doc is not of Category Forwarders"); } } } diff --git a/Views/AngularTemplate/FilePicker.cshtml b/Views/AngularTemplate/FilePicker.cshtml index 191172ad97..a023a8b1c5 100644 --- a/Views/AngularTemplate/FilePicker.cshtml +++ b/Views/AngularTemplate/FilePicker.cshtml @@ -1,5 +1,5 @@ 
Select File + accept="{{field.fileextensions}}" ngf-max-size='20MB' ngf-min-height='100'>Select File {{field.value == null ? 'No file selected' : field.value|formatInput}}
diff --git a/terminalExcel/Activities/Load_Excel_File_v1.cs b/terminalExcel/Activities/Load_Excel_File_v1.cs index 975f2c37bf..5898ef1ef6 100644 --- a/terminalExcel/Activities/Load_Excel_File_v1.cs +++ b/terminalExcel/Activities/Load_Excel_File_v1.cs @@ -33,6 +33,7 @@ public ActivityUi() { FilePicker = new FilePicker { + FileExtensions = ".xls,.xlsx", Label = "Select an Excel file", Name = nameof(FilePicker), Required = true, diff --git a/terminalGoogle/Activities/Create_Google_Doc_v1.cs b/terminalGoogle/Activities/Create_Google_Doc_v1.cs new file mode 100644 index 0000000000..f2614c2dda --- /dev/null +++ b/terminalGoogle/Activities/Create_Google_Doc_v1.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Fr8.Infrastructure.Data.Control; +using Fr8.Infrastructure.Data.Crates; +using Fr8.Infrastructure.Data.DataTransferObjects; +using Fr8.Infrastructure.Data.Managers; +using Fr8.Infrastructure.Data.Manifests; +using Fr8.Infrastructure.Data.Manifests.Helpers; +using Fr8.Infrastructure.Data.States; +using Fr8.TerminalBase.Errors; +using Fr8.TerminalBase.Infrastructure; +using Google.GData.Client; +using Newtonsoft.Json; +using terminalGoogle.Actions; +using terminalGoogle.DataTransferObjects; +using terminalGoogle.Interfaces; +using Fr8.TerminalBase.Services; +using System.Globalization; +using System.Text; +using Fr8.Infrastructure.Data.Constants; +using System.IO; + +namespace terminalGoogle.Activities +{ + public class Create_Google_Doc_v1 : BaseGoogleTerminalActivity + { + public static ActivityTemplateDTO ActivityTemplateDTO = new ActivityTemplateDTO + { + Id = new Guid("DAFD6823-CF54-4DD4-A618-D89A967EB885"), + Name = "Create_Google_Doc", + Label = "Create Google Doc", + Version = "1", + Terminal = TerminalData.TerminalDTO, + NeedsAuthentication = true, + MinPaneWidth = 300, + Categories = new[] + { + ActivityCategories.Forward, + TerminalData.GooogleActivityCategoryDTO + } + }; + protected override ActivityTemplateDTO MyTemplate => ActivityTemplateDTO; + + public class ActivityUi : StandardConfigurationControlsCM + { + public CrateChooser UpstreamCrateChooser { get; set; } + + public DropDownList ExistingFilesList { get; set; } + + public RadioButtonOption UseIncomingDataOption { get; set; } + + public RadioButtonOption UseStoredFileOption { get; set; } + + public RadioButtonGroup ContentSelectionGroup { get; set; } + + public TextSource NewFileName { get; set; } + + public ActivityUi(UiBuilder uiBuilder) + { + NewFileName = uiBuilder.CreateSpecificOrUpstreamValueChooser("Title", nameof(NewFileName), addRequestConfigEvent: true, requestUpstream: true, availability: AvailabilityType.RunTime); + NewFileName.IsCollapsed = false; + Controls.Add(NewFileName); + + UpstreamCrateChooser = new CrateChooser + { + Label = "Crate to store", + Name = nameof(UpstreamCrateChooser), + Required = true, + RequestUpstream = true, + SingleManifestOnly = true, + }; + + ExistingFilesList = new DropDownList + { + Name = nameof(ExistingFilesList), + Events = new List { ControlEvent.RequestConfig } + }; + + UseIncomingDataOption = new RadioButtonOption + { + Selected = true, + Name = nameof(UseIncomingDataOption), + Value = "Use Incoming Data", + Controls = new List { UpstreamCrateChooser } + }; + + UseStoredFileOption = new RadioButtonOption() + { + Selected = false, + Name = nameof(UseStoredFileOption), + Value = "Use Excisting File", + Controls = new List { ExistingFilesList } + }; + + ContentSelectionGroup = new RadioButtonGroup + { + GroupName = nameof(ContentSelectionGroup), + Name = nameof(ContentSelectionGroup), + Events = new List { ControlEvent.RequestConfig }, + Radios = new List + { + UseIncomingDataOption, + UseStoredFileOption + } + }; + + Controls.Add(ContentSelectionGroup); + } + } + + private const string SelectedDocCrateLabel = "Selected Doc"; + + private readonly IGoogleDrive _googleDrive; + + public Create_Google_Doc_v1(ICrateManager crateManager, IGoogleIntegration googleIntegration, IGoogleDrive googleDrive) + : base(crateManager, googleIntegration) + { + _googleDrive = googleDrive; + } + + private GoogleAuthDTO GetGoogleAuthToken() + { + return JsonConvert.DeserializeObject(AuthorizationToken.Token); + } + + public override async Task Initialize() + { + ActivityUI.ExistingFilesList.ListItems = await GetCurrentUsersFiles(".doc",".docx"); + } + + public override async Task FollowUp() + { + } + + protected override Task Validate() + { + ValidationManager.ValidateTextSourceNotEmpty(ActivityUI.NewFileName, "Title for Document must be specified"); + + if(ActivityUI.UseIncomingDataOption.Selected == true) + { + ValidationManager.ValidateCrateChooserNotEmpty(ActivityUI.UpstreamCrateChooser, "File must be specified"); + } + + if(ActivityUI.UseStoredFileOption.Selected == true) + { + ValidationManager.ValidateDropDownListNotEmpty(ActivityUI.ExistingFilesList, "File must be specified"); + } + + return Task.FromResult(0); + } + + public override async Task Run() + { + byte[] body = null; + string fileName = null; + + if(ActivityUI.UseIncomingDataOption.Selected == true) + { + var crateToProcess = FindCrateToProcess(); + if (crateToProcess == null) + { + throw new ActivityExecutionException($"Failed to run {ActivityPayload.Name} because specified upstream crate was not found in payload"); + } + + var content = crateToProcess.Get(); + body = Convert.FromBase64String(content.TextRepresentation); + fileName = content.Filename; + } + else if(ActivityUI.UseStoredFileOption.Selected == true) + { + var fileSelector = ActivityUI.ExistingFilesList; + if (string.IsNullOrEmpty(fileSelector.Value)) + { + RaiseError("No File was selected on design time", ActivityErrorCode.DESIGN_TIME_DATA_MISSING); + return; + } + //let's download this file + var file = await HubCommunicator.DownloadFile(int.Parse(fileSelector.Value)); + if (file == null || file.Length < 1) + { + RaiseError("Unable to download file from Hub"); + return; + } + fileName = fileSelector.selectedKey; + + using (var reader = new MemoryStream()) + { + file.CopyTo(reader); + body = reader.ToArray(); + } + } + + try + { + var file = await _googleDrive.CreateFile(ActivityUI.NewFileName.TextValue, + body, GetMimeType(fileName), GetGoogleAuthToken()); + + } + catch (GDataRequestException ex) + { + if (ex.InnerException.Message.IndexOf("(401) Unauthorized") > -1) + { + throw new AuthorizationTokenExpiredOrInvalidException(); + } + + throw; + } + } + + private static string GetMimeType(string fileName) + { + string mimeType = "application/unknown"; + string ext = System.IO.Path.GetExtension(fileName).ToLower(); + Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext); + if (regKey != null && regKey.GetValue("Content Type") != null) + mimeType = regKey.GetValue("Content Type").ToString(); + return mimeType; + } + + private async Task> GetCurrentUsersFiles(params string[] extensions) + { + var curAccountFileList = await HubCommunicator.GetFiles(); + if(extensions != null) + { + curAccountFileList = curAccountFileList.Where(f => + { + bool result = true; + foreach (var ext in extensions) + { + if (f.OriginalFileName.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)) + { + result = true; + } + else + { + result = false; + } + } + return result; + }); + } + + return curAccountFileList.Select(c => new ListItem() { Key = c.OriginalFileName, Value = c.Id.ToString(CultureInfo.InvariantCulture) }).ToList(); + } + + private Crate FindCrateToProcess() + { + var desiredCrateDescription = ActivityUI.UpstreamCrateChooser.CrateDescriptions.Single(x => x.Selected); + return Payload.FirstOrDefault(x => x.Label == desiredCrateDescription.Label && x.ManifestType.Type == desiredCrateDescription.ManifestType); + } + } +} \ No newline at end of file diff --git a/terminalGoogle/Interfaces/IGoogleDrive.cs b/terminalGoogle/Interfaces/IGoogleDrive.cs index 7c67dcea57..f83248e5a0 100644 --- a/terminalGoogle/Interfaces/IGoogleDrive.cs +++ b/terminalGoogle/Interfaces/IGoogleDrive.cs @@ -10,5 +10,7 @@ public interface IGoogleDrive Task CreateDriveService(GoogleAuthDTO authDTO); bool FileExist(DriveService driveService, string filename, out string link); Task> GetGoogleForms(GoogleAuthDTO authDTO); - } + Task CreateFile(string title, byte[] body, string mimeType, GoogleAuthDTO authDTO); + + } } \ No newline at end of file diff --git a/terminalGoogle/Services/GoogleDrive.cs b/terminalGoogle/Services/GoogleDrive.cs index 945e8ca88a..92df274426 100644 --- a/terminalGoogle/Services/GoogleDrive.cs +++ b/terminalGoogle/Services/GoogleDrive.cs @@ -77,6 +77,20 @@ public async Task DownloadFile(string fileId, GoogleAuthDTO authDTO) return fileContent; } + public async Task CreateFile(string title, byte[] body, string mimeType, GoogleAuthDTO authDTO) + { + var driveService = await CreateDriveService(authDTO); + var doc = new Google.Apis.Drive.v2.Data.File(); + doc.Title = title; + doc.MimeType = mimeType; + var stream = new System.IO.MemoryStream(body); + + var request = driveService.Files.Insert(doc, stream, mimeType); + await request.UploadAsync(); + return request.ResponseBody; + } + + public async Task> GetGoogleForms(GoogleAuthDTO authDTO) { var driveService = await CreateDriveService(authDTO); diff --git a/terminalGoogle/Startup.cs b/terminalGoogle/Startup.cs index ad7036469d..129e3c8ead 100644 --- a/terminalGoogle/Startup.cs +++ b/terminalGoogle/Startup.cs @@ -56,6 +56,7 @@ protected override void RegisterActivities() ActivityStore.RegisterActivity(Monitor_Form_Responses_v1.ActivityTemplateDTO); ActivityStore.RegisterActivity(Save_To_Google_Sheet_v1.ActivityTemplateDTO); ActivityStore.RegisterActivity(Monitor_Gmail_Inbox_v1.ActivityTemplateDTO); + ActivityStore.RegisterActivity(Create_Google_Doc_v1.ActivityTemplateDTO); } } } diff --git a/terminalGoogle/terminalGoogle.csproj b/terminalGoogle/terminalGoogle.csproj index 29df308c03..cfadc36ffc 100644 --- a/terminalGoogle/terminalGoogle.csproj +++ b/terminalGoogle/terminalGoogle.csproj @@ -304,6 +304,7 @@ + diff --git a/terminalUtilities/Excel/ExcelUtils.cs b/terminalUtilities/Excel/ExcelUtils.cs index 6e911c7a39..85e5a47515 100644 --- a/terminalUtilities/Excel/ExcelUtils.cs +++ b/terminalUtilities/Excel/ExcelUtils.cs @@ -15,6 +15,7 @@ using OfficeOpenXml; using StructureMap; using RestSharp.Extensions; +using terminalUtilities.Files; namespace terminalUtilities.Excel { @@ -22,11 +23,13 @@ public class ExcelUtils { private readonly IRestfulServiceClient _restfulServiceClient; private readonly ICrateManager _crateManager; + private readonly FileUtils _fileUtils; - public ExcelUtils(IRestfulServiceClient restfulServiceClient, ICrateManager crateManager) + public ExcelUtils(IRestfulServiceClient restfulServiceClient, ICrateManager crateManager, FileUtils fileUtils) { _restfulServiceClient = restfulServiceClient; _crateManager = crateManager; + _fileUtils = fileUtils; } public static void ConvertToCsv(string pathToExcel, string pathToCsv) @@ -263,9 +266,7 @@ public static Dictionary>> GetTabularData(byt public async Task GetExcelFileAsByteArray(string selectedFilePath) { - var fileAsByteArray = await RetrieveFile(selectedFilePath); - fileAsByteArray.Position = 0; - return fileAsByteArray.ReadAsBytes(); + return await _fileUtils.GetFileAsByteArray(selectedFilePath, ".xls", ".xlsx"); } public async Task GetExcelFile(string selectedFilePath, bool isFirstRowAsColumnNames = true) @@ -334,12 +335,7 @@ public StandardTableDataCM GetExcelFile(byte[] fileAsByteArray, string selectedF private async Task RetrieveFile(string filePath) { - var ext = Path.GetExtension(filePath); - if (ext != ".xls" && ext != ".xlsx") - { - throw new ArgumentException("Expected '.xls' or '.xlsx'", "selectedFile"); - } - return await _restfulServiceClient.DownloadAsync(new Uri(filePath)); + return await _fileUtils.RetrieveFile(filePath, ".xls", ".xlsx"); } public static List CreateTableCellPayloadObjects(Dictionary>> rowsDictionary, string[] headersArray = null, bool includeHeadersAsFirstRow = false) diff --git a/terminalUtilities/Files/FileUtils.cs b/terminalUtilities/Files/FileUtils.cs new file mode 100644 index 0000000000..6ed4ed3d48 --- /dev/null +++ b/terminalUtilities/Files/FileUtils.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Excel; +using Fr8.Infrastructure.Data.Crates; +using Fr8.Infrastructure.Data.DataTransferObjects; +using Fr8.Infrastructure.Data.Managers; +using Fr8.Infrastructure.Data.Manifests; +using Fr8.Infrastructure.Data.States; +using Fr8.Infrastructure.Interfaces; +using OfficeOpenXml; +using StructureMap; +using RestSharp.Extensions; + +namespace terminalUtilities.Files +{ + public class FileUtils + { + private readonly IRestfulServiceClient _restfulServiceClient; + + public FileUtils(IRestfulServiceClient restfulServiceClient, ICrateManager crateManager) + { + _restfulServiceClient = restfulServiceClient; + } + + + public async Task GetFileAsByteArray(string selectedFilePath, params string[] extensions) + { + var fileAsByteArray = await RetrieveFile(selectedFilePath, extensions); + fileAsByteArray.Position = 0; + return fileAsByteArray.ReadAsBytes(); + } + public async Task RetrieveFile(string filePath, params string[] extensions) + { + var ext = System.IO.Path.GetExtension(filePath); + string exception = "Expected "; + bool hasException = false; + foreach (var extension in extensions) + { + if(ext == extension) + { + hasException = false; + break; + } + if (ext != extension) + { + hasException = true; + if (extensions.First() == extension) + { + exception += extension; + } + else + { + exception += " or " + extension; + } + } + } + if (hasException == true) + { + throw new ArgumentException(exception, "selectedFile"); + } + + return await _restfulServiceClient.DownloadAsync(new Uri(filePath)); + } + } +} + diff --git a/terminalUtilities/terminalUtilities.csproj b/terminalUtilities/terminalUtilities.csproj index f9d222c36e..5a57909666 100644 --- a/terminalUtilities/terminalUtilities.csproj +++ b/terminalUtilities/terminalUtilities.csproj @@ -155,6 +155,7 @@ + diff --git a/terminalWord/Activities/Load_Word_File_v1.cs b/terminalWord/Activities/Load_Word_File_v1.cs new file mode 100644 index 0000000000..da4c38a8f1 --- /dev/null +++ b/terminalWord/Activities/Load_Word_File_v1.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Fr8.Infrastructure.Data.Constants; +using Fr8.Infrastructure.Data.Control; +using Fr8.Infrastructure.Data.Crates; +using Fr8.Infrastructure.Data.DataTransferObjects; +using Fr8.Infrastructure.Data.Managers; +using Fr8.Infrastructure.Data.Manifests; +using Fr8.Infrastructure.Data.States; +using Fr8.TerminalBase.BaseClasses; +using Fr8.TerminalBase.Errors; +using RestSharp.Extensions; +using terminalUtilities.Files; + +namespace terminalWord.Activities +{ + public class Load_Word_File_v1 : TerminalActivity + { + public static ActivityTemplateDTO ActivityTemplateDTO = new ActivityTemplateDTO + { + Id = new Guid("8BBB1FE4-58F0-4342-B0E2-65CECB21F5DC"), + Name = "Load_Word_File", + Label = "Load Word File", + Version = "1", + MinPaneWidth = 300, + Terminal = TerminalData.TerminalDTO, + Categories = new[] + { + ActivityCategories.Receive, + TerminalData.ActivityCategoryDTO + } + }; + protected override ActivityTemplateDTO MyTemplate => ActivityTemplateDTO; + + private const string FileCrateLabel = "File uploaded by Load Word"; + + private const string ConfigurationCrateLabel = "Selected File"; + + private const string ExternalObjectHandlesLabel = "External Object Handles"; + + public class ActivityUi : StandardConfigurationControlsCM + { + public FilePicker FilePicker { get; set; } + + public TextBlock UploadedFileDescription { get; set; } + + public TextBlock ActivityDescription { get; set; } + + public ActivityUi() + { + FilePicker = new FilePicker + { + FileExtensions= ".doc,.docx", + Label = "Select a Word file", + Name = nameof(FilePicker), + Required = true, + Events = new List { ControlEvent.RequestConfig }, + }; + Controls.Add(FilePicker); + UploadedFileDescription = new TextBlock + { + Name = nameof(UploadedFileDescription), + IsHidden = true + }; + Controls.Add(UploadedFileDescription); + } + public void MarkFileAsUploaded(string fileName, string filePath) + { + FilePicker.Value = filePath; + UploadedFileDescription.Value = $"Uploaded file: {fileName}"; + UploadedFileDescription.IsHidden = false; + } + + public void ClearFileDescription() + { + UploadedFileDescription.Value = string.Empty; + UploadedFileDescription.IsHidden = true; + } + } + + private readonly FileUtils _fileUtils; + public Load_Word_File_v1(ICrateManager crateManager, FileUtils fileUtils) + : base(crateManager) + { + _fileUtils = fileUtils; + } + + public override async Task Initialize() + { + CrateSignaller.MarkAvailableAtRuntime(FileCrateLabel); + } + + public override Task FollowUp() + { + return Task.FromResult(0); + } + + protected override Task Validate() + { + if(string.IsNullOrEmpty(ActivityUI.FilePicker.Value)) + { + ValidationManager.SetError("File must be specified", ActivityUI.FilePicker); + } + return Task.FromResult(0); + } + + public override async Task Run() + { + var byteArray = await _fileUtils.GetFileAsByteArray(ActivityUI.FilePicker.Value, ".doc", ".docx"); + + var fileDescription = new StandardFileDescriptionCM + { + + TextRepresentation = Convert.ToBase64String(byteArray), + Filetype = System.IO.Path.GetExtension(ActivityUI.FilePicker.Value), + Filename = System.IO.Path.GetFileName(ActivityUI.FilePicker.Value) + }; + + Payload.Add(Crate.FromContent(FileCrateLabel, fileDescription)); + } + } +} \ No newline at end of file diff --git a/terminalWord/App_Start/RouteConfig.cs b/terminalWord/App_Start/RouteConfig.cs new file mode 100644 index 0000000000..564cf9e5c9 --- /dev/null +++ b/terminalWord/App_Start/RouteConfig.cs @@ -0,0 +1,13 @@ +using System.Web.Http; +using Fr8.TerminalBase.BaseClasses; + +namespace terminalWord +{ + public static class RoutesConfig + { + public static void Register(HttpConfiguration config) + { + BaseTerminalWebApiConfig.Register("terminalWord", config); + } + } +} \ No newline at end of file diff --git a/terminalWord/App_Start/SwaggerConfig.cs b/terminalWord/App_Start/SwaggerConfig.cs new file mode 100644 index 0000000000..7d4576f45c --- /dev/null +++ b/terminalWord/App_Start/SwaggerConfig.cs @@ -0,0 +1,246 @@ +using System.Web.Http; +using WebActivatorEx; +using terminalWord; +using Swashbuckle.Application; + +//[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")] + +namespace terminalWord +{ + public class SwaggerConfig + { + public static void Register(HttpConfiguration configuration) + { + var thisAssembly = typeof(SwaggerConfig).Assembly; + + GlobalConfiguration.Configuration + .EnableSwagger(c => + { + // By default, the service root url is inferred from the request used to access the docs. + // However, there may be situations (e.g. proxy and load-balanced environments) where this does not + // resolve correctly. You can workaround this by providing your own code to determine the root URL. + // + //c.RootUrl(req => GetRootUrlFromAppConfig()); + + // If schemes are not explicitly provided in a Swagger 2.0 document, then the scheme used to access + // the docs is taken as the default. If your API supports multiple schemes and you want to be explicit + // about them, you can use the "Schemes" option as shown below. + // + //c.Schemes(new[] { "http", "https" }); + + // Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to + // hold additional metadata for an API. Version and title are required but you can also provide + // additional fields by chaining methods off SingleApiVersion. + // + c.SingleApiVersion("v1", "terminalWord"); + + // If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion". + // In this case, you must provide a lambda that tells Swashbuckle which actions should be + // included in the docs for a given API version. Like "SingleApiVersion", each call to "Version" + // returns an "Info" builder so you can provide additional metadata per API version. + // + //c.MultipleApiVersions( + // (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion), + // (vc) => + // { + // vc.Version("v2", "Swashbuckle Dummy API V2"); + // vc.Version("v1", "Swashbuckle Dummy API V1"); + // }); + + // You can use "BasicAuth", "ApiKey" or "OAuth2" options to describe security schemes for the API. + // See https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md for more details. + // NOTE: These only define the schemes and need to be coupled with a corresponding "security" property + // at the document or operation level to indicate which schemes are required for an operation. To do this, + // you'll need to implement a custom IDocumentFilter and/or IOperationFilter to set these properties + // according to your specific authorization implementation + // + //c.BasicAuth("basic") + // .Description("Basic HTTP Authentication"); + // + // NOTE: You must also configure 'EnableApiKeySupport' below in the SwaggerUI section + //c.ApiKey("apiKey") + // .Description("API Key Authentication") + // .Name("apiKey") + // .In("header"); + // + //c.OAuth2("oauth2") + // .Description("OAuth2 Implicit Grant") + // .Flow("implicit") + // .AuthorizationUrl("http://petstore.swagger.wordnik.com/api/oauth/dialog") + // //.TokenUrl("https://tempuri.org/token") + // .Scopes(scopes => + // { + // scopes.Add("read", "Read access to protected resources"); + // scopes.Add("write", "Write access to protected resources"); + // }); + + // Set this flag to omit descriptions for any actions decorated with the Obsolete attribute + //c.IgnoreObsoleteActions(); + + // Each operation be assigned one or more tags which are then used by consumers for various reasons. + // For example, the swagger-ui groups operations according to the first tag of each operation. + // By default, this will be controller name but you can use the "GroupActionsBy" option to + // override with any value. + // + //c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString()); + + // You can also specify a custom sort order for groups (as defined by "GroupActionsBy") to dictate + // the order in which operations are listed. For example, if the default grouping is in place + // (controller name) and you specify a descending alphabetic sort order, then actions from a + // ProductsController will be listed before those from a CustomersController. This is typically + // used to customize the order of groupings in the swagger-ui. + // + //c.OrderActionGroupsBy(new DescendingAlphabeticComparer()); + + // If you annotate Controllers and API Types with + // Xml comments (http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx), you can incorporate + // those comments into the generated docs and UI. You can enable this by providing the path to one or + // more Xml comment files. + // + //c.IncludeXmlComments(GetXmlCommentsPath()); + + // Swashbuckle makes a best attempt at generating Swagger compliant JSON schemas for the various types + // exposed in your API. However, there may be occasions when more control of the output is needed. + // This is supported through the "MapType" and "SchemaFilter" options: + // + // Use the "MapType" option to override the Schema generation for a specific type. + // It should be noted that the resulting Schema will be placed "inline" for any applicable Operations. + // While Swagger 2.0 supports inline definitions for "all" Schema types, the swagger-ui tool does not. + // It expects "complex" Schemas to be defined separately and referenced. For this reason, you should only + // use the "MapType" option when the resulting Schema is a primitive or array type. If you need to alter a + // complex Schema, use a Schema filter. + // + //c.MapType(() => new Schema { type = "integer", format = "int32" }); + + // If you want to post-modify "complex" Schemas once they've been generated, across the board or for a + // specific type, you can wire up one or more Schema filters. + // + //c.SchemaFilter(); + + // In a Swagger 2.0 document, complex types are typically declared globally and referenced by unique + // Schema Id. By default, Swashbuckle does NOT use the full type name in Schema Ids. In most cases, this + // works well because it prevents the "implementation detail" of type namespaces from leaking into your + // Swagger docs and UI. However, if you have multiple types in your API with the same class name, you'll + // need to opt out of this behavior to avoid Schema Id conflicts. + // + //c.UseFullTypeNameInSchemaIds(); + + // Alternatively, you can provide your own custom strategy for inferring SchemaId's for + // describing "complex" types in your API. + // + //c.SchemaId(t => t.FullName.Contains('`') ? t.FullName.Substring(0, t.FullName.IndexOf('`')) : t.FullName); + + // Set this flag to omit schema property descriptions for any type properties decorated with the + // Obsolete attribute + //c.IgnoreObsoleteProperties(); + + // In accordance with the built in JsonSerializer, Swashbuckle will, by default, describe enums as integers. + // You can change the serializer behavior by configuring the StringToEnumConverter globally or for a given + // enum type. Swashbuckle will honor this change out-of-the-box. However, if you use a different + // approach to serialize enums as strings, you can also force Swashbuckle to describe them as strings. + // + //c.DescribeAllEnumsAsStrings(); + + // Similar to Schema filters, Swashbuckle also supports Operation and Document filters: + // + // Post-modify Operation descriptions once they've been generated by wiring up one or more + // Operation filters. + // + //c.OperationFilter(); + // + // If you've defined an OAuth2 flow as described above, you could use a custom filter + // to inspect some attribute on each action and infer which (if any) OAuth2 scopes are required + // to execute the operation + // + //c.OperationFilter(); + + // Post-modify the entire Swagger document by wiring up one or more Document filters. + // This gives full control to modify the final SwaggerDocument. You should have a good understanding of + // the Swagger 2.0 spec. - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md + // before using this option. + // + //c.DocumentFilter(); + + // In contrast to WebApi, Swagger 2.0 does not include the query string component when mapping a URL + // to an action. As a result, Swashbuckle will raise an exception if it encounters multiple actions + // with the same path (sans query string) and HTTP method. You can workaround this by providing a + // custom strategy to pick a winner or merge the descriptions for the purposes of the Swagger docs + // + //c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); + + // Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an + // alternative implementation for ISwaggerProvider with the CustomProvider option. + // + //c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider)); + }) + .EnableSwaggerUi(c => + { + // Use the "InjectStylesheet" option to enrich the UI with one or more additional CSS stylesheets. + // The file must be included in your project as an "Embedded Resource", and then the resource's + // "Logical Name" is passed to the method as shown below. + // + //c.InjectStylesheet(containingAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testStyles1.css"); + + // Use the "InjectJavaScript" option to invoke one or more custom JavaScripts after the swagger-ui + // has loaded. The file must be included in your project as an "Embedded Resource", and then the resource's + // "Logical Name" is passed to the method as shown above. + // + //c.InjectJavaScript(thisAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testScript1.js"); + + // The swagger-ui renders boolean data types as a dropdown. By default, it provides "true" and "false" + // strings as the possible choices. You can use this option to change these to something else, + // for example 0 and 1. + // + //c.BooleanValues(new[] { "0", "1" }); + + // By default, swagger-ui will validate specs against swagger.io's online validator and display the result + // in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the + // feature entirely. + //c.SetValidatorUrl("http://localhost/validator"); + //c.DisableValidator(); + + // Use this option to control how the Operation listing is displayed. + // It can be set to "None" (default), "List" (shows operations for each resource), + // or "Full" (fully expanded: shows operations and their details). + // + //c.DocExpansion(DocExpansion.List); + + // Specify which HTTP operations will have the 'Try it out!' option. An empty paramter list disables + // it for all operations. + // + //c.SupportedSubmitMethods("GET", "HEAD"); + + // Use the CustomAsset option to provide your own version of assets used in the swagger-ui. + // It's typically used to instruct Swashbuckle to return your version instead of the default + // when a request is made for "index.html". As with all custom content, the file must be included + // in your project as an "Embedded Resource", and then the resource's "Logical Name" is passed to + // the method as shown below. + // + //c.CustomAsset("index", containingAssembly, "YourWebApiProject.SwaggerExtensions.index.html"); + + // If your API has multiple versions and you've applied the MultipleApiVersions setting + // as described above, you can also enable a select box in the swagger-ui, that displays + // a discovery URL for each version. This provides a convenient way for users to browse documentation + // for different API versions. + // + //c.EnableDiscoveryUrlSelector(); + + // If your API supports the OAuth2 Implicit flow, and you've described it correctly, according to + // the Swagger 2.0 specification, you can enable UI support as shown below. + // + //c.EnableOAuth2Support( + // clientId: "test-client-id", + // clientSecret: null, + // realm: "test-realm", + // appName: "Swagger UI" + // //additionalQueryStringParams: new Dictionary() { { "foo", "bar" } } + //); + + // If your API supports ApiKey, you can override the default values. + // "apiKeyIn" can either be "query" or "header" + // + //c.EnableApiKeySupport("apiKey", "header"); + }); + } + } +} diff --git a/terminalWord/Controllers/ActivityController.cs b/terminalWord/Controllers/ActivityController.cs new file mode 100644 index 0000000000..28b64796ae --- /dev/null +++ b/terminalWord/Controllers/ActivityController.cs @@ -0,0 +1,13 @@ +using Fr8.TerminalBase.BaseClasses; +using Fr8.TerminalBase.Services; + +namespace terminalWord.Controllers +{ + public class ActivityController : DefaultActivityController + { + public ActivityController(IActivityExecutor activityExecutor) + : base(activityExecutor) + { + } + } +} \ No newline at end of file diff --git a/terminalWord/Controllers/TerminalController.cs b/terminalWord/Controllers/TerminalController.cs new file mode 100644 index 0000000000..beed4b4b67 --- /dev/null +++ b/terminalWord/Controllers/TerminalController.cs @@ -0,0 +1,13 @@ +using Fr8.TerminalBase.BaseClasses; +using Fr8.TerminalBase.Services; + +namespace terminalWord.Controllers +{ + public class TerminalController : DefaultTerminalController + { + public TerminalController(IActivityStore activityStore, IHubDiscoveryService hubDiscovery) + : base(activityStore, hubDiscovery) + { + } + } +} \ No newline at end of file diff --git a/terminalWord/Global.asax b/terminalWord/Global.asax new file mode 100644 index 0000000000..2ae86aee55 --- /dev/null +++ b/terminalWord/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="terminalWord.WebApiApplication" Language="C#" %> diff --git a/terminalWord/Global.asax.cs b/terminalWord/Global.asax.cs new file mode 100644 index 0000000000..b8b830c341 --- /dev/null +++ b/terminalWord/Global.asax.cs @@ -0,0 +1,7 @@ +namespace terminalWord +{ + public class WebApiApplication : System.Web.HttpApplication + { + + } +} diff --git a/terminalWord/Properties/AssemblyInfo.cs b/terminalWord/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..855bddfe47 --- /dev/null +++ b/terminalWord/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("$projectsafename$")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("$projectsafename")] +[assembly: AssemblyCopyright("Copyright © terminalWord 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3b6ebeb4-7cd4-4008-aaab-a3c4cdc77655")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/terminalWord/Startup.cs b/terminalWord/Startup.cs new file mode 100644 index 0000000000..c6ec36b2ef --- /dev/null +++ b/terminalWord/Startup.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Web.Http.Dispatcher; +using Fr8.TerminalBase.BaseClasses; +using Microsoft.Owin; +using Owin; +using terminalWord.Controllers; +using terminalWord; + +[assembly: OwinStartup(typeof(Startup))] + +namespace terminalWord +{ + public class Startup : BaseConfiguration + { + public Startup() + : base(TerminalData.TerminalDTO) + { + } + + public void Configuration(IAppBuilder app) + { + Configuration(app, false); + } + + public void Configuration(IAppBuilder app, bool selfHost) + { + ConfigureProject(selfHost, null); + SwaggerConfig.Register(_configuration); + RoutesConfig.Register(_configuration); + ConfigureFormatters(); + app.UseWebApi(_configuration); + if (!selfHost) + { + StartHosting(); + } + } + + protected override void RegisterActivities() + { + ActivityStore.RegisterActivity(Activities.Load_Word_File_v1.ActivityTemplateDTO); + } + + public override ICollection GetControllerTypes(IAssembliesResolver assembliesResolver) + { + return new Type[] { + typeof(ActivityController), + typeof(TerminalController), + }; + } + } +} diff --git a/terminalWord/TerminalData.cs b/terminalWord/TerminalData.cs new file mode 100644 index 0000000000..e35c8eeabd --- /dev/null +++ b/terminalWord/TerminalData.cs @@ -0,0 +1,25 @@ +using Fr8.Infrastructure.Data.DataTransferObjects; +using Fr8.Infrastructure.Data.States; +using Fr8.Infrastructure.Utilities.Configuration; + +namespace terminalWord +{ + public static class TerminalData + { + public static ActivityCategoryDTO ActivityCategoryDTO = new ActivityCategoryDTO + { + Name = "Word", + IconPath = "/Content/icons/web_services/ms-word-icon-64x64.png", + Type = "WebService" + }; + + public static TerminalDTO TerminalDTO = new TerminalDTO + { + Endpoint = CloudConfigurationManager.GetSetting("terminalWord.TerminalEndpoint"), + TerminalStatus = TerminalStatus.Active, + Name = "terminalWord", + Label = "Word", + Version = "1" + }; + } +} \ No newline at end of file diff --git a/terminalWord/Web.Debug.config b/terminalWord/Web.Debug.config new file mode 100644 index 0000000000..680849f611 --- /dev/null +++ b/terminalWord/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/terminalWord/Web.Release.config b/terminalWord/Web.Release.config new file mode 100644 index 0000000000..943c9c0de7 --- /dev/null +++ b/terminalWord/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/terminalWord/Web.config b/terminalWord/Web.config new file mode 100644 index 0000000000..0600d234ee --- /dev/null +++ b/terminalWord/Web.config @@ -0,0 +1,97 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/terminalWord/favicon.ico b/terminalWord/favicon.ico new file mode 100644 index 0000000000..a3a799985c Binary files /dev/null and b/terminalWord/favicon.ico differ diff --git a/terminalWord/index.html b/terminalWord/index.html new file mode 100644 index 0000000000..dd07692281 --- /dev/null +++ b/terminalWord/index.html @@ -0,0 +1,11 @@ + + + + + + + This is the home page of the Fr8Demo Terminal Service, one of the many terminals being developed. + + + + diff --git a/terminalWord/packages.config b/terminalWord/packages.config new file mode 100644 index 0000000000..f5502e330d --- /dev/null +++ b/terminalWord/packages.config @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/terminalWord/terminalWord.csproj b/terminalWord/terminalWord.csproj new file mode 100644 index 0000000000..52b60a66f6 --- /dev/null +++ b/terminalWord/terminalWord.csproj @@ -0,0 +1,242 @@ + + + + + + Debug + AnyCPU + + + 2.0 + {C808A1BB-26EC-4EF9-8592-10176BF1E558} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + terminalWord + terminalWord + v4.5 + false + true + + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\packages\AutoMapper.4.0.4\lib\net45\AutoMapper.dll + True + + + ..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll + True + + + ..\packages\log4net.2.0.5\lib\net45-full\log4net.dll + True + + + ..\packages\Microsoft.ApplicationInsights.2.0.0\lib\net45\Microsoft.ApplicationInsights.dll + True + + + + ..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll + True + + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\libphonenumber-csharp.7.2.5\lib\PhoneNumbers.dll + True + + + ..\packages\PusherServer.3.0.0\lib\net35\PusherServer.dll + True + + + ..\packages\RestSharp.105.0.1\lib\net4\RestSharp.dll + True + + + ..\packages\structuremap.3.1.6.186\lib\net40\StructureMap.dll + True + + + ..\packages\structuremap.3.1.6.186\lib\net40\StructureMap.Net4.dll + True + + + ..\packages\Swashbuckle.Core.5.4.0\lib\net40\Swashbuckle.Core.dll + True + + + + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll + True + + + + + + + + + + + + + + True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + + + + ..\packages\WebActivatorEx.2.0\lib\net40\WebActivatorEx.dll + True + + + ..\packages\YamlDotNet.3.8.0\lib\net35\YamlDotNet.dll + True + + + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll + + + + + + + + + + Global.asax + + + + + + + + + + + Designer + + + Web.config + + + Web.config + + + + + + + + + + + {bba91af2-7636-41b6-87c4-c1575ae8b04b} + Fr8Infrastructure.NET + + + {bf96675e-6baf-4ac9-b247-1551d62e8c44} + Fr8TerminalBase.NET + + + {E46B602A-287B-450F-B455-5EED20C93EB7} + terminalUtilities + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + + + + True + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + \ No newline at end of file