diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index a616d29409..97b57a0436 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -33,6 +33,7 @@ namespace Volo.Abp.Cli options.Commands["add-module"] = typeof(AddModuleCommand); options.Commands["login"] = typeof(LoginCommand); options.Commands["logout"] = typeof(LogoutCommand); + options.Commands["generate-proxy"] = typeof(GenerateProxyCommand); }); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs new file mode 100644 index 0000000000..368295b4c5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs @@ -0,0 +1,393 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using ICSharpCode.SharpZipLib.Core; +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectBuilding; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; +using Microsoft.CSharp; +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Cli.Commands +{ + public class GenerateProxyCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected TemplateProjectBuilder TemplateProjectBuilder { get; } + + public GenerateProxyCommand(TemplateProjectBuilder templateProjectBuilder) + { + TemplateProjectBuilder = templateProjectBuilder; + + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + try + { + var apiUrl = commandLineArgs.Options.GetOrNull(Options.ApiUrl.Short, Options.ApiUrl.Long); + var uiFramework = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long); + + //WebClient client = new WebClient(); + //string json = client.DownloadString(apiUrl); + StreamReader sr = File.OpenText("api-definition.json"); + string json = sr.ReadToEnd(); + + Logger.LogInformation("Downloading api definition..."); + Logger.LogInformation("Api Url: " + apiUrl); + + JObject data = JObject.Parse(json); + + string rootPath = (string)data["modules"]["app"]["rootPath"]; + + System.IO.Directory.CreateDirectory(string.Format("src/app/{0}/shared/models", rootPath)); + System.IO.Directory.CreateDirectory(string.Format("src/app/{0}/shared/services", rootPath)); + + var serviceIndexList = new List(); + + foreach (var module in data["modules"]) + { + foreach (var item in module.First["controllers"]) + { + var controller = item.First; + + var controllerName = (string)controller["controllerName"]; + var controllerServiceName = controllerName.PascalToKebabCase() + ".service.ts"; + + StringBuilder serviceFileText = new StringBuilder(); + + serviceFileText.AppendLine("firstTypeList"); + serviceFileText.AppendLine("import { Injectable } from '@angular/core';"); + serviceFileText.AppendLine("import { Observable } from 'rxjs';"); + serviceFileText.AppendLine("secondTypeList"); + serviceFileText.AppendLine(""); + serviceFileText.AppendLine("@Injectable()"); + serviceFileText.AppendLine(string.Format("export class {0}Service ", controllerName) + "{"); + serviceFileText.AppendLine(" constructor(private restService: RestService) {}"); + serviceFileText.AppendLine(""); + + var firstTypeList = new List(); + var secondTypeList = new List(); + + foreach (var controllerItem in controller["actions"]) + { + var action = controllerItem.First; + var actionName = (string)action["name"]; + + actionName = (char.ToLower(actionName[0]) + actionName.Substring(1)).Replace("Async", "").Replace("Controller", ""); + + var returnValueType = (string)action["returnValue"]["type"]; + + var parameters = action["parameters"]; + StringBuilder parametersText = new StringBuilder(); + var parametersIndex = 0; + var bodyExtra = ""; + var modelBindingExtra = ""; + var modelBindingExtraList = new List(); + + foreach (var parameter in parameters) + { + parametersIndex++; + + if (parametersIndex > 1) + parametersText.Append(", "); + + var bindingSourceId = (string)parameter["bindingSourceId"]; + bindingSourceId = char.ToLower(bindingSourceId[0]) + bindingSourceId.Substring(1); + + if (bindingSourceId == "body") + { + bodyExtra = ", body"; + var typeArray = ((string)parameter["type"]).Split("."); + var type = typeArray[typeArray.Length - 1]; + + parametersText.Append(bindingSourceId + ": " + type); + secondTypeList.Add(type); + } + else if (bindingSourceId == "path") + { + parametersText.Append((string)parameter["name"] + ": " + (string)parameter["typeSimple"]); + } + else if (bindingSourceId == "modelBinding") + { + var typeSimple = ""; + var type = ""; + + var parameterNameOnMethod = (string)parameter["nameOnMethod"]; + + var parametersOnMethod = action["parametersOnMethod"]; + foreach (var parameterOnMethod in parametersOnMethod) + { + var parametersOnMethodName = (string)parameterOnMethod["name"]; + if (parametersOnMethodName == parameterNameOnMethod) + { + typeSimple = (string)parameterOnMethod["typeSimple"]; + + var typeArray = ((string)parameterOnMethod["type"]).Split("."); + type = typeArray[typeArray.Length - 1]; + } + } + + if (typeSimple == "string" || typeSimple == "boolean" || typeSimple == "number") + { + parametersText.Append((string)parameter["name"] + ": " + (string)parameter["typeSimple"]); + modelBindingExtraList.Add((string)parameter["name"]); + } + else + { + parametersText.Append(string.Format("params = {{}} as {0}", type)); + modelBindingExtra = ", params"; + secondTypeList.Add(type); + break; + } + } + } + + if (returnValueType != null) + { + if (returnValueType.IndexOf('<') > -1) + { + var firstTypeArray = returnValueType.Split("<")[0].Split("."); + var firstType = firstTypeArray[firstTypeArray.Length - 1]; + + var secondTypeArray = returnValueType.Split("<")[1].Split("."); + var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>'); + + serviceFileText.AppendLine(string.Format(" {0}({1}): Observable<{2}<{3}>> {{", actionName, parametersText.ToString(), firstType, secondType)); + + firstTypeList.Add(firstType); + secondTypeList.Add(secondType); + } + else + { + var typeArray = returnValueType.Split("."); + var type = typeArray[typeArray.Length - 1].TrimEnd('>'); + + if (type == "Void") + type = "void"; + + serviceFileText.AppendLine(string.Format(" {0}({1}): Observable<{2}> {{", actionName, parametersText.ToString(), type)); + secondTypeList.Add(type); + } + } + + if (modelBindingExtraList != null && modelBindingExtraList.Count > 0) + { + modelBindingExtra = ", params: { " + String.Join(", ", modelBindingExtraList.ToArray()) + " }"; + } + + var url = ((string)action["url"]).Replace("/{", "/${"); + var httpMethod = (string)action["httpMethod"]; + serviceFileText.AppendLine(string.Format(" return this.restService.request({{ url: '/{0}', method: '{1}'{2}{3} }});", url, httpMethod, bodyExtra, modelBindingExtra)); + + serviceFileText.AppendLine(" }"); + } + + serviceFileText.AppendLine("}"); + + if (firstTypeList != null && firstTypeList.Count > 0) + { + var firstTypeListDistinct = ", " + String.Join(", ", firstTypeList.Where(p => p != "void").Distinct().ToArray()); + serviceFileText.Replace("firstTypeList", string.Format("import {{ RestService {0}}} from '@abp/ng.core';", firstTypeListDistinct)); + } + else + { + serviceFileText.Replace("firstTypeList", ""); + } + + if (secondTypeList != null && secondTypeList.Count > 0) + { + var secondTypeListDistinct = String.Join(", ", secondTypeList.Where(p => p != "void").Distinct().ToArray()); + serviceFileText.Replace("secondTypeList", string.Format("import {{{0}}} from '../models';", secondTypeListDistinct)); + } + else + { + serviceFileText.Replace("secondTypeList", ""); + } + + System.IO.File.WriteAllText(string.Format("src/app/{0}/shared/services/{1}", rootPath, controllerServiceName), serviceFileText.ToString()); + + serviceIndexList.Add(controllerServiceName.Replace(".ts", "")); + } + } + + StringBuilder serviceIndexFileText = new StringBuilder(); + + foreach (var serviceIndexItem in serviceIndexList) + { + serviceIndexFileText.AppendLine(string.Format("export * from './{0}';", serviceIndexItem)); + } + + System.IO.File.WriteAllText(string.Format("src/app/{0}/shared/services/index.ts", rootPath), serviceIndexFileText.ToString()); + + var modelIndexList = new List(); + + foreach (var type in data["types"]) + { + var typeFullName = ((JProperty)type.First.Parent).Name; + + var typeNameSplit = typeFullName.Split("."); + var typeName = typeNameSplit[typeNameSplit.Length - 1]; + + var typeModelName = typeName.Replace("<","").Replace(">", "").PascalToKebabCase() + ".ts"; + + StringBuilder modelFileText = new StringBuilder(); + + + var baseType = (string)type.First["baseType"]; + var extends = ""; + + if (!string.IsNullOrWhiteSpace(baseType)) + { + var baseTypeSplit = baseType.Split("."); + var baseTypeName = baseTypeSplit[baseTypeSplit.Length - 1]; + var baseTypeKebabCase = baseTypeName.PascalToKebabCase(); + + modelFileText.AppendLine(string.Format("import {{ {0} }} from '@abp/ng.core';", baseTypeName, baseTypeKebabCase)); + extends = "extends " + baseTypeName; + } + + modelFileText.AppendLine(string.Format("export class {0} {1} {{", typeName, extends)); + + foreach (var property in type.First["properties"]) + { + var propertyName = (string)property["name"]; + propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1)); + + var typeSimple = (string)property["typeSimple"]; + + modelFileText.AppendLine(string.Format(" {0}: {1};", propertyName, typeSimple)); + } + + modelFileText.AppendLine(""); + + modelFileText.AppendLine(string.Format(" constructor(initialValues: Partial<{0}> = {{}}) {{", typeName)); + + if (!string.IsNullOrWhiteSpace(baseType)) + { + modelFileText.AppendLine(" super(initialValues);"); + } + else + { + modelFileText.AppendLine(" if (initialValues) {"); + modelFileText.AppendLine(" for (const key in initialValues) {"); + modelFileText.AppendLine(" if (initialValues.hasOwnProperty(key)) {"); + modelFileText.AppendLine(" this[key] = initialValues[key];"); + + modelFileText.AppendLine(" }"); + modelFileText.AppendLine(" }"); + modelFileText.AppendLine(" }"); + modelFileText.AppendLine(" }"); + } + + modelFileText.AppendLine("}"); + + System.IO.File.WriteAllText(string.Format("src/app/{0}/shared/models/{1}", rootPath, typeModelName), modelFileText.ToString()); + + modelIndexList.Add(typeModelName.Replace(".ts", "")); + } + + StringBuilder modelIndexFileText = new StringBuilder(); + + foreach (var modelIndexItem in modelIndexList) + { + modelIndexFileText.AppendLine(string.Format("export * from './{0}';", modelIndexItem)); + } + + System.IO.File.WriteAllText(string.Format("src/app/{0}/shared/models/index.ts", rootPath), modelIndexFileText.ToString()); + } + catch (Exception ex) + { + throw; + } + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp generate-proxy --apiUrl [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine("-u|--ui (default: angular)"); + sb.AppendLine(""); + sb.AppendLine("Examples:"); + sb.AppendLine(""); + sb.AppendLine(" abp new --apiUrl https://www.abp.io/api/abp/api-definition?types=true"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Generates a ..."; + } + + private UiFramework GetUiFramework(CommandLineArgs commandLineArgs) + { + var optionValue = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long); + switch (optionValue) + { + case "none": + return UiFramework.None; + case "mvc": + return UiFramework.Mvc; + case "angular": + return UiFramework.Angular; + default: + return UiFramework.NotSpecified; + } + } + + public static class Options + { + public static class ApiUrl + { + public const string Short = "au"; + public const string Long = "apiUrl"; + } + + public static class UiFramework + { + public const string Short = "u"; + public const string Long = "ui"; + } + } + } + + public static class StringExtensions + { + public static string PascalToKebabCase(this string value) + { + if (string.IsNullOrEmpty(value)) + return value; + + return Regex.Replace( + value, + "(? +