mirror of https://github.com/abpframework/abp.git
89 changed files with 7498 additions and 383 deletions
@ -0,0 +1,24 @@ |
|||||
|
name: 'Main' |
||||
|
on: |
||||
|
pull_request: |
||||
|
paths: |
||||
|
- 'framework/**' |
||||
|
- 'modules/**' |
||||
|
branches: |
||||
|
- dev |
||||
|
jobs: |
||||
|
build-test: |
||||
|
runs-on: windows-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v1 |
||||
|
- uses: actions/setup-dotnet@master |
||||
|
with: |
||||
|
dotnet-version: '3.0.100' |
||||
|
|
||||
|
- name: Build All |
||||
|
run: .\build-all.ps1 |
||||
|
shell: pwsh |
||||
|
|
||||
|
- name: Test All |
||||
|
run: .\test-all.ps1 |
||||
|
shell: pwsh |
||||
@ -1,3 +1,222 @@ |
|||||
# Data Filtering |
# Data Filtering |
||||
|
|
||||
TODO |
[Volo.Abp.Data](https://www.nuget.org/packages/Volo.Abp.Data) package defines services to automatically filter data on querying from a database. |
||||
|
|
||||
|
## Pre-Defined Filters |
||||
|
|
||||
|
ABP defines some filters out of the box. |
||||
|
|
||||
|
### ISoftDelete |
||||
|
|
||||
|
Used to mark an [entity](Entities.md) as deleted instead of actually deleting it. Implement the `ISoftDelete` interface to make your entity "soft delete". |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public class Book : AggregateRoot<Guid>, ISoftDelete |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public bool IsDeleted { get; set; } //Defined by ISoftDelete |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`ISoftDelete` defines the `IsDeleted` property. When you delete a book using [repositories](Repositories.md), ABP automatically sets `IsDeleted` to true and protects it from actual deletion (you can also manually set the `IsDeleted` property to true if you need). In addition, it **automatically filters deleted entities** when you query the database. |
||||
|
|
||||
|
> `ISoftDelete` filter is enabled by default and you can not get deleted entities from database unless you explicitly disable it. See the `IDataFilter` service below. |
||||
|
|
||||
|
### IMultiTenant |
||||
|
|
||||
|
[Multi-tenancy](Multi-Tenancy.md) is an efficient way of creating SaaS applications. Once you create a multi-tenant application, you typically want to isolate data between tenants. Implement `IMultiTenant` interface to make your entity "multi-tenant aware". |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public class Book : AggregateRoot<Guid>, ISoftDelete, IMultiTenant |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public bool IsDeleted { get; set; } //Defined by ISoftDelete |
||||
|
|
||||
|
public Guid? TenantId { get; set; } //Defined by IMultiTenant |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`IMultiTenant` interface defines the `TenantId` property which is then used to automatically filter the entities for the current tenant. See the [Multi-tenancy](Multi-Tenancy.md) document for more. |
||||
|
|
||||
|
## IDataFilter Service: Enable/Disable Data Filters |
||||
|
|
||||
|
You can control the filters using `IDataFilter` service. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public class MyBookService : ITransientDependency |
||||
|
{ |
||||
|
private readonly IDataFilter _dataFilter; |
||||
|
private readonly IRepository<Book, Guid> _bookRepository; |
||||
|
|
||||
|
public MyBookService( |
||||
|
IDataFilter dataFilter, |
||||
|
IRepository<Book, Guid> bookRepository) |
||||
|
{ |
||||
|
_dataFilter = dataFilter; |
||||
|
_bookRepository = bookRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task<List<Book>> GetAllBooksIncludingDeletedAsync() |
||||
|
{ |
||||
|
//Temporary disable the ISoftDelete filter |
||||
|
using (_dataFilter.Disable<ISoftDelete>()) |
||||
|
{ |
||||
|
return await _bookRepository.GetListAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* [Inject](Dependency-Injection.md) the `IDataFilter` service to your class. |
||||
|
* Use the `Disable` method within a `using` statement to create a code block where the `ISoftDelete` filter is disabled inside it (Always use it inside a `using` block to guarantee that the filter is reset to its previous state). |
||||
|
|
||||
|
`IDataFilter.Enable` method can be used to enable a filter. `Enable` and `Disable` methods can be used in a nested way to define inner scopes. |
||||
|
|
||||
|
## AbpDataFilterOptions |
||||
|
|
||||
|
`AbpDataFilterOptions` can be used to [set options](Options.md) for the data filter system. |
||||
|
|
||||
|
The example code below disables the `ISoftDelete` filter by default which will cause to include deleted entities when you query the database unless you explicitly enable the filter: |
||||
|
|
||||
|
````csharp |
||||
|
Configure<AbpDataFilterOptions>(options => |
||||
|
{ |
||||
|
options.DefaultStates[typeof(ISoftDelete)] = new DataFilterState(isEnabled: false); |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
> Carefully change defaults for global filters, especially if you are using a pre-built module which might be developed assuming the soft delete filter is turned on by default. But you can do it for your own defined filters safely. |
||||
|
|
||||
|
## Defining Custom Filters |
||||
|
|
||||
|
Defining and implementing a new filter highly depends on the database provider. ABP implements all pre-defined filters for all database providers. |
||||
|
|
||||
|
When you need it, start by defining an interface (like `ISoftDelete` and `IMultiTenant`) for your filter and implement it for your entities. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````csharp |
||||
|
public interface IIsActive |
||||
|
{ |
||||
|
bool IsActive { get; } |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
Such an `IIsActive` interface can be used to filter active/passive data and can be easily implemented by any [entity](Entities.md): |
||||
|
|
||||
|
````csharp |
||||
|
public class Book : AggregateRoot<Guid>, IIsActive |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public bool IsActive { get; set; } //Defined by IIsActive |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
### EntityFramework Core |
||||
|
|
||||
|
ABP uses [EF Core's Global Query Filters](https://docs.microsoft.com/en-us/ef/core/querying/filters) system for the [EF Core Integration](Entity-Framework-Core.md). So, it is well integrated to EF Core and works as expected even if you directly work with `DbContext`. |
||||
|
|
||||
|
Best way to implement a custom filter is to override `CreateFilterExpression` method for your `DbContext`. Example: |
||||
|
|
||||
|
````csharp |
||||
|
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false; |
||||
|
|
||||
|
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>() |
||||
|
{ |
||||
|
var expression = base.CreateFilterExpression<TEntity>(); |
||||
|
|
||||
|
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) |
||||
|
{ |
||||
|
Expression<Func<TEntity, bool>> isActiveFilter = |
||||
|
e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive"); |
||||
|
expression = expression == null |
||||
|
? isActiveFilter |
||||
|
: CombineExpressions(expression, isActiveFilter); |
||||
|
} |
||||
|
|
||||
|
return expression; |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* Added a `IsActiveFilterEnabled` property to check if `IIsActive` is enabled or not. It internally uses the `IDataFilter` service introduced before. |
||||
|
* Overrided the `CreateFilterExpression` method, checked if given entity implements the `IIsActive` interface and combines the expressions if necessary. |
||||
|
|
||||
|
### MongoDB |
||||
|
|
||||
|
ABP implements data filters directly in the [repository](Repositories.md) level for the [MongoDB Integration](MongoDB.md). So, it works only if you use the repositories properly. Otherwise, you should manually filter the data. |
||||
|
|
||||
|
Currently, the best way to implement a data filter for the MongoDB integration is to override the `AddGlobalFilters` in the repository derived from the `MongoDbRepository` class. Example: |
||||
|
|
||||
|
````csharp |
||||
|
public class BookRepository : MongoDbRepository<BookStoreMongoDbContext, Book, Guid> |
||||
|
{ |
||||
|
protected override void AddGlobalFilters(List<FilterDefinition<Book>> filters) |
||||
|
{ |
||||
|
if (DataFilter.IsEnabled<IIsActive>()) |
||||
|
{ |
||||
|
filters.Add(Builders<Book>.Filter.Eq(e => ((IIsActive)e).IsActive, true)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
This example implements it only for the `Book` entity. If you want to implement for all entities (those implement the `IIsActive` interface), create your own custom MongoDB repository base class and override the `AddGlobalFilters` as shown below: |
||||
|
|
||||
|
````csharp |
||||
|
public abstract class MyMongoRepository<TMongoDbContext, TEntity, TKey> : MongoDbRepository<TMongoDbContext, TEntity, TKey> |
||||
|
where TMongoDbContext : IAbpMongoDbContext |
||||
|
where TEntity : class, IEntity<TKey> |
||||
|
{ |
||||
|
protected MyMongoRepository(IMongoDbContextProvider<TMongoDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected override void AddGlobalFilters(List<FilterDefinition<TEntity>> filters) |
||||
|
{ |
||||
|
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)) |
||||
|
&& DataFilter.IsEnabled<IIsActive>()) |
||||
|
{ |
||||
|
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IIsActive)e).IsActive, true)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> See "Set Default Repository Classes" section of the [MongoDb Integration document](MongoDB.md) to learn how to replace default repository base with your custom class. |
||||
@ -0,0 +1,21 @@ |
|||||
|
namespace Volo.Abp.Caching |
||||
|
{ |
||||
|
public class DistributedCacheKeyNormalizeArgs |
||||
|
{ |
||||
|
public string Key { get; } |
||||
|
|
||||
|
public string CacheName { get; } |
||||
|
|
||||
|
public bool IgnoreMultiTenancy { get; } |
||||
|
|
||||
|
public DistributedCacheKeyNormalizeArgs( |
||||
|
string key, |
||||
|
string cacheName, |
||||
|
bool ignoreMultiTenancy) |
||||
|
{ |
||||
|
Key = key; |
||||
|
CacheName = cacheName; |
||||
|
IgnoreMultiTenancy = ignoreMultiTenancy; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.Caching |
||||
|
{ |
||||
|
public class DistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency |
||||
|
{ |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
|
||||
|
protected AbpDistributedCacheOptions DistributedCacheOptions { get; } |
||||
|
|
||||
|
public DistributedCacheKeyNormalizer( |
||||
|
ICurrentTenant currentTenant, |
||||
|
IOptions<AbpDistributedCacheOptions> distributedCacheOptions) |
||||
|
{ |
||||
|
CurrentTenant = currentTenant; |
||||
|
DistributedCacheOptions = distributedCacheOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args) |
||||
|
{ |
||||
|
var normalizedKey = $"c:{args.CacheName},k:{DistributedCacheOptions.KeyPrefix}{args.Key}"; |
||||
|
|
||||
|
if (!args.IgnoreMultiTenancy && CurrentTenant.Id.HasValue) |
||||
|
{ |
||||
|
normalizedKey = $"t:{CurrentTenant.Id.Value},{normalizedKey}"; |
||||
|
} |
||||
|
|
||||
|
return normalizedKey; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Caching |
||||
|
{ |
||||
|
public interface IDistributedCacheKeyNormalizer |
||||
|
{ |
||||
|
string NormalizeKey(DistributedCacheKeyNormalizeArgs args); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.Text.RegularExpressions; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Utils |
||||
|
{ |
||||
|
public static class NamespaceHelper |
||||
|
{ |
||||
|
public static string NormalizeNamespace([CanBeNull] string value) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(value)) |
||||
|
{ |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
value = value.Trim(); |
||||
|
value = Regex.Replace(value, @"(((?<=\.)|^)((?=\d)|\.)|[^\w\.])|(\.$)", "_"); |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Volo.IdentityServer:DuplicateIdentityResourceName": "Identity资源名称已存在: {Name}", |
||||
|
"Volo.IdentityServer:DuplicateApiResourceName": "Api资源名称已存在: {Name}", |
||||
|
"Volo.IdentityServer:DuplicateClientId": "ClientId已经存在: {ClientId}" |
||||
|
} |
||||
|
} |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"trailingComma": "all", |
||||
|
"printWidth": 120, |
||||
|
"singleQuote": true |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
import { APIDefination } from './types/api-defination'; |
||||
|
export declare function angular(data: APIDefination.Response, selectedModules: string[]): Promise<void>; |
||||
@ -0,0 +1,57 @@ |
|||||
|
"use strict"; |
||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
||||
|
return new (P || (P = Promise))(function (resolve, reject) { |
||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
||||
|
}); |
||||
|
}; |
||||
|
var __generator = (this && this.__generator) || function (thisArg, body) { |
||||
|
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; |
||||
|
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; |
||||
|
function verb(n) { return function (v) { return step([n, v]); }; } |
||||
|
function step(op) { |
||||
|
if (f) throw new TypeError("Generator is already executing."); |
||||
|
while (_) try { |
||||
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; |
||||
|
if (y = 0, t) op = [op[0] & 2, t.value]; |
||||
|
switch (op[0]) { |
||||
|
case 0: case 1: t = op; break; |
||||
|
case 4: _.label++; return { value: op[1], done: false }; |
||||
|
case 5: _.label++; y = op[1]; op = [0]; continue; |
||||
|
case 7: op = _.ops.pop(); _.trys.pop(); continue; |
||||
|
default: |
||||
|
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } |
||||
|
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } |
||||
|
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } |
||||
|
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } |
||||
|
if (t[2]) _.ops.pop(); |
||||
|
_.trys.pop(); continue; |
||||
|
} |
||||
|
op = body.call(thisArg, _); |
||||
|
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } |
||||
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; |
||||
|
} |
||||
|
}; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
var service_templates_1 = require("./templates/angular/service-templates"); |
||||
|
function angular(data, selectedModules) { |
||||
|
return __awaiter(this, void 0, void 0, function () { |
||||
|
return __generator(this, function (_a) { |
||||
|
selectedModules.forEach(function (module) { |
||||
|
var element = data.modules[module]; |
||||
|
var contents = []; |
||||
|
(Object.keys(element.controllers) || []).forEach(function (key) { |
||||
|
console.warn(element.controllers[key].controllerName); |
||||
|
contents.push(''); |
||||
|
}); |
||||
|
var service = service_templates_1.ServiceTemplates.classTemplate(element.rootPath, contents); |
||||
|
console.log(service); |
||||
|
}); |
||||
|
return [2 /*return*/]; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
exports.angular = angular; |
||||
@ -0,0 +1 @@ |
|||||
|
export declare function cli(program: any): Promise<void>; |
||||
@ -0,0 +1,102 @@ |
|||||
|
"use strict"; |
||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
||||
|
return new (P || (P = Promise))(function (resolve, reject) { |
||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
||||
|
}); |
||||
|
}; |
||||
|
var __generator = (this && this.__generator) || function (thisArg, body) { |
||||
|
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; |
||||
|
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; |
||||
|
function verb(n) { return function (v) { return step([n, v]); }; } |
||||
|
function step(op) { |
||||
|
if (f) throw new TypeError("Generator is already executing."); |
||||
|
while (_) try { |
||||
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; |
||||
|
if (y = 0, t) op = [op[0] & 2, t.value]; |
||||
|
switch (op[0]) { |
||||
|
case 0: case 1: t = op; break; |
||||
|
case 4: _.label++; return { value: op[1], done: false }; |
||||
|
case 5: _.label++; y = op[1]; op = [0]; continue; |
||||
|
case 7: op = _.ops.pop(); _.trys.pop(); continue; |
||||
|
default: |
||||
|
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } |
||||
|
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } |
||||
|
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } |
||||
|
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } |
||||
|
if (t[2]) _.ops.pop(); |
||||
|
_.trys.pop(); continue; |
||||
|
} |
||||
|
op = body.call(thisArg, _); |
||||
|
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } |
||||
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; |
||||
|
} |
||||
|
}; |
||||
|
var __importDefault = (this && this.__importDefault) || function (mod) { |
||||
|
return (mod && mod.__esModule) ? mod : { "default": mod }; |
||||
|
}; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
var prompt_1 = require("./utils/prompt"); |
||||
|
var axios_1 = require("./utils/axios"); |
||||
|
var ora = require("ora"); |
||||
|
var angular_1 = require("./angular"); |
||||
|
var chalk_1 = __importDefault(require("chalk")); |
||||
|
function cli(program) { |
||||
|
return __awaiter(this, void 0, void 0, function () { |
||||
|
var _a, loading, data, selection, modules, _b; |
||||
|
var _this = this; |
||||
|
return __generator(this, function (_c) { |
||||
|
switch (_c.label) { |
||||
|
case 0: |
||||
|
if (!(program.ui !== 'angular')) return [3 /*break*/, 2]; |
||||
|
_a = program; |
||||
|
return [4 /*yield*/, prompt_1.uiSelection(['Angular'])]; |
||||
|
case 1: |
||||
|
_a.ui = (_c.sent()).toLowerCase(); |
||||
|
_c.label = 2; |
||||
|
case 2: |
||||
|
loading = ora('Waiting for the API response... \n'); |
||||
|
loading.start(); |
||||
|
return [4 /*yield*/, axios_1.axiosInstance.get('a')]; |
||||
|
case 3: |
||||
|
data = (_c.sent()); |
||||
|
loading.stop(); |
||||
|
selection = function (modules) { return __awaiter(_this, void 0, void 0, function () { |
||||
|
var selectedModules; |
||||
|
return __generator(this, function (_a) { |
||||
|
switch (_a.label) { |
||||
|
case 0: return [4 /*yield*/, prompt_1.moduleSelection(modules)]; |
||||
|
case 1: |
||||
|
selectedModules = (_a.sent()); |
||||
|
if (!!selectedModules.length) return [3 /*break*/, 3]; |
||||
|
console.log(chalk_1.default.red('Please select module(s)')); |
||||
|
return [4 /*yield*/, selection(modules)]; |
||||
|
case 2: return [2 /*return*/, _a.sent()]; |
||||
|
case 3: return [2 /*return*/, selectedModules]; |
||||
|
} |
||||
|
}); |
||||
|
}); }; |
||||
|
return [4 /*yield*/, selection(Object.keys(data.modules))]; |
||||
|
case 4: |
||||
|
modules = _c.sent(); |
||||
|
_b = program.ui; |
||||
|
switch (_b) { |
||||
|
case 'angular': return [3 /*break*/, 5]; |
||||
|
} |
||||
|
return [3 /*break*/, 7]; |
||||
|
case 5: return [4 /*yield*/, angular_1.angular(data, modules)]; |
||||
|
case 6: |
||||
|
_c.sent(); |
||||
|
return [3 /*break*/, 8]; |
||||
|
case 7: |
||||
|
process.exit(1); |
||||
|
_c.label = 8; |
||||
|
case 8: return [2 /*return*/]; |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
exports.cli = cli; |
||||
@ -0,0 +1,2 @@ |
|||||
|
#!/usr/bin/env node |
||||
|
export {}; |
||||
@ -0,0 +1,24 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
"use strict"; |
||||
|
var __importDefault = (this && this.__importDefault) || function (mod) { |
||||
|
return (mod && mod.__esModule) ? mod : { "default": mod }; |
||||
|
}; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
var chalk_1 = __importDefault(require("chalk")); |
||||
|
var commander_1 = __importDefault(require("commander")); |
||||
|
var cli_1 = require("./cli"); |
||||
|
var clear = require('clear'); |
||||
|
var figlet = require('figlet'); |
||||
|
clear(); |
||||
|
console.log(chalk_1.default.red(figlet.textSync('ABP', { horizontalLayout: 'full' }))); |
||||
|
commander_1.default |
||||
|
.version('0.0.1') |
||||
|
.description('ABP Client Generator') |
||||
|
.option('-u, --ui <type>', 'UI option (Angular)') |
||||
|
.parse(process.argv); |
||||
|
if (!process.argv.slice(2).length || !commander_1.default.ui || typeof commander_1.default.ui !== 'string') { |
||||
|
commander_1.default.outputHelp(); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
commander_1.default.ui = commander_1.default.ui.toLowerCase(); |
||||
|
cli_1.cli(commander_1.default); |
||||
@ -0,0 +1,3 @@ |
|||||
|
export declare namespace ServiceTemplates { |
||||
|
function classTemplate(name: string, content: string[]): string; |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
"use strict"; |
||||
|
var __importDefault = (this && this.__importDefault) || function (mod) { |
||||
|
return (mod && mod.__esModule) ? mod : { "default": mod }; |
||||
|
}; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
var change_case_1 = __importDefault(require("change-case")); |
||||
|
var ServiceTemplates; |
||||
|
(function (ServiceTemplates) { |
||||
|
function classTemplate(name, content) { |
||||
|
return "import { RestService } from '@abp/ng.core';\nimport { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class " + change_case_1.default.pascalCase(name) + "Service {\n\n constructor(private restService: RestService) { }\n\n " + content.forEach(function (element) { |
||||
|
element + '\n\n'; |
||||
|
}) + "\n}"; |
||||
|
} |
||||
|
ServiceTemplates.classTemplate = classTemplate; |
||||
|
})(ServiceTemplates = exports.ServiceTemplates || (exports.ServiceTemplates = {})); |
||||
@ -0,0 +1,14 @@ |
|||||
|
export declare namespace APIDefination { |
||||
|
interface Response { |
||||
|
modules: Modules; |
||||
|
} |
||||
|
interface Modules { |
||||
|
[key: string]: Module; |
||||
|
} |
||||
|
interface Module { |
||||
|
rootPath: string; |
||||
|
controllers: { |
||||
|
[key: string]: any; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
"use strict"; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
@ -0,0 +1 @@ |
|||||
|
export declare const axiosInstance: import("axios").AxiosInstance; |
||||
File diff suppressed because it is too large
@ -0,0 +1,2 @@ |
|||||
|
export declare const uiSelection: (uiArray: string[]) => Promise<any>; |
||||
|
export declare const moduleSelection: (modules: string[]) => Promise<any>; |
||||
@ -0,0 +1,56 @@ |
|||||
|
"use strict"; |
||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
||||
|
return new (P || (P = Promise))(function (resolve, reject) { |
||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
||||
|
}); |
||||
|
}; |
||||
|
var __generator = (this && this.__generator) || function (thisArg, body) { |
||||
|
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; |
||||
|
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; |
||||
|
function verb(n) { return function (v) { return step([n, v]); }; } |
||||
|
function step(op) { |
||||
|
if (f) throw new TypeError("Generator is already executing."); |
||||
|
while (_) try { |
||||
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; |
||||
|
if (y = 0, t) op = [op[0] & 2, t.value]; |
||||
|
switch (op[0]) { |
||||
|
case 0: case 1: t = op; break; |
||||
|
case 4: _.label++; return { value: op[1], done: false }; |
||||
|
case 5: _.label++; y = op[1]; op = [0]; continue; |
||||
|
case 7: op = _.ops.pop(); _.trys.pop(); continue; |
||||
|
default: |
||||
|
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } |
||||
|
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } |
||||
|
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } |
||||
|
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } |
||||
|
if (t[2]) _.ops.pop(); |
||||
|
_.trys.pop(); continue; |
||||
|
} |
||||
|
op = body.call(thisArg, _); |
||||
|
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } |
||||
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; |
||||
|
} |
||||
|
}; |
||||
|
var __importDefault = (this && this.__importDefault) || function (mod) { |
||||
|
return (mod && mod.__esModule) ? mod : { "default": mod }; |
||||
|
}; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
var inquirer_1 = __importDefault(require("inquirer")); |
||||
|
exports.uiSelection = function (uiArray) { return __awaiter(void 0, void 0, void 0, function () { |
||||
|
return __generator(this, function (_a) { |
||||
|
return [2 /*return*/, inquirer_1.default |
||||
|
.prompt({ type: 'list', name: 'Please select an UI', choices: uiArray }) |
||||
|
.then(function (res) { return res['Please select an UI']; })]; |
||||
|
}); |
||||
|
}); }; |
||||
|
exports.moduleSelection = function (modules) { return __awaiter(void 0, void 0, void 0, function () { |
||||
|
return __generator(this, function (_a) { |
||||
|
return [2 /*return*/, inquirer_1.default |
||||
|
.prompt({ type: 'checkbox', name: 'Please select module(s)', choices: modules }) |
||||
|
.then(function (res) { return res['Please select module(s)']; })]; |
||||
|
}); |
||||
|
}); }; |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"watch": ["src"], |
||||
|
"ext": "ts", |
||||
|
"ignore": ["src/**/*.spec.ts"], |
||||
|
"exec": "ts-node ./src/index.ts" |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
{ |
||||
|
"name": "@abp/client-generator", |
||||
|
"version": "0.0.1", |
||||
|
"description": "", |
||||
|
"main": "./lib/index.js", |
||||
|
"bin": { |
||||
|
"abp-type": "./lib/index.js" |
||||
|
}, |
||||
|
"scripts": { |
||||
|
"start": "ts-node src/index.ts", |
||||
|
"start:watch": "nodemon", |
||||
|
"create": "npm run build && npm run test", |
||||
|
"build": "tsc -p .", |
||||
|
"test": "jest", |
||||
|
"prepublish": "yarn build" |
||||
|
}, |
||||
|
"author": "", |
||||
|
"license": "ISC", |
||||
|
"dependencies": { |
||||
|
"axios": "^0.19.0", |
||||
|
"chalk": "^3.0.0", |
||||
|
"change-case": "^3.1.0", |
||||
|
"clear": "^0.1.0", |
||||
|
"commander": "^4.0.1", |
||||
|
"figlet": "^1.2.4", |
||||
|
"fs-extra": "^8.1.0", |
||||
|
"inquirer": "^7.0.0", |
||||
|
"ora": "^4.0.3", |
||||
|
"path": "^0.12.7" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@types/fs-extra": "^8.0.1", |
||||
|
"@types/inquirer": "^6.5.0", |
||||
|
"@types/node": "^12.12.8", |
||||
|
"nodemon": "^1.19.4", |
||||
|
"ts-node": "^8.5.2", |
||||
|
"typescript": "^3.7.2" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
import { APIDefination } from './types/api-defination'; |
||||
|
import { ServiceTemplates } from './templates/angular/service-templates'; |
||||
|
import changeCase from 'change-case'; |
||||
|
import fse from 'fs-extra'; |
||||
|
|
||||
|
export async function angular(data: APIDefination.Response, selectedModules: string[]) { |
||||
|
selectedModules.forEach(async module => { |
||||
|
const element = data.modules[module]; |
||||
|
|
||||
|
let contents = [] as string[]; |
||||
|
(Object.keys(element.controllers) || []).forEach(key => { |
||||
|
console.warn(element.controllers[key].controllerName); |
||||
|
contents.push(' '); |
||||
|
}); |
||||
|
|
||||
|
const service = ServiceTemplates.classTemplate(element.rootPath, contents); |
||||
|
await fse.writeFile(`${changeCase.kebabCase(element.rootPath + '-service')}.ts`, service); |
||||
|
console.log(service); |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
import { uiSelection, moduleSelection } from './utils/prompt'; |
||||
|
import { axiosInstance } from './utils/axios'; |
||||
|
import ora = require('ora'); |
||||
|
import { angular } from './angular'; |
||||
|
import chalk from 'chalk'; |
||||
|
|
||||
|
export async function cli(program: any) { |
||||
|
if (program.ui !== 'angular') { |
||||
|
program.ui = ((await uiSelection(['Angular'])) as string).toLowerCase(); |
||||
|
} |
||||
|
|
||||
|
const loading = ora('Waiting for the API response... \n'); |
||||
|
loading.start(); |
||||
|
const data = (await axiosInstance.get('a')) as any; |
||||
|
loading.stop(); |
||||
|
|
||||
|
const selection = async (modules: string[]): Promise<string[]> => { |
||||
|
const selectedModules = (await moduleSelection(modules)) as string[]; |
||||
|
|
||||
|
if (!selectedModules.length) { |
||||
|
console.log(chalk.red('Please select module(s)')); |
||||
|
return await selection(modules); |
||||
|
} |
||||
|
|
||||
|
return selectedModules; |
||||
|
}; |
||||
|
|
||||
|
// const modules = await selection(Object.keys(data.modules));
|
||||
|
const modules = ['multi-tenancy']; |
||||
|
|
||||
|
switch (program.ui) { |
||||
|
case 'angular': |
||||
|
await angular(data, modules); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
process.exit(1); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
#!/usr/bin/env node |
||||
|
import chalk from 'chalk'; |
||||
|
import program from 'commander'; |
||||
|
import ora from 'ora'; |
||||
|
import { axiosInstance } from './utils/axios'; |
||||
|
import { moduleSelection, uiSelection } from './utils/prompt'; |
||||
|
import { cli } from './cli'; |
||||
|
const clear = require('clear'); |
||||
|
const figlet = require('figlet'); |
||||
|
|
||||
|
clear(); |
||||
|
|
||||
|
console.log(chalk.red(figlet.textSync('ABP', { horizontalLayout: 'full' }))); |
||||
|
|
||||
|
program |
||||
|
.version('0.0.1') |
||||
|
.description('ABP Client Generator') |
||||
|
.option('-u, --ui <type>', 'UI option (Angular)') |
||||
|
.parse(process.argv); |
||||
|
|
||||
|
if (!process.argv.slice(2).length || !program.ui || typeof program.ui !== 'string') { |
||||
|
program.outputHelp(); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
|
||||
|
program.ui = program.ui.toLowerCase(); |
||||
|
|
||||
|
cli(program); |
||||
@ -0,0 +1,18 @@ |
|||||
|
import changeCase from 'change-case'; |
||||
|
|
||||
|
export namespace ServiceTemplates { |
||||
|
export function classTemplate(name: string, contents: string[]) { |
||||
|
return `import { RestService } from '@abp/ng.core';
|
||||
|
import { Injectable } from '@angular/core'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class ${changeCase.pascalCase(name)}Service { |
||||
|
|
||||
|
constructor(private restService: RestService) { } |
||||
|
|
||||
|
${contents[0]} |
||||
|
}`;
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
export namespace APIDefination { |
||||
|
export interface Response { |
||||
|
modules: Modules; |
||||
|
} |
||||
|
|
||||
|
export interface Modules { |
||||
|
[key: string]: Module; |
||||
|
} |
||||
|
|
||||
|
export interface Module { |
||||
|
rootPath: string; |
||||
|
controllers: { [key: string]: any }; |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,13 @@ |
|||||
|
import inquirer from 'inquirer'; |
||||
|
|
||||
|
export const uiSelection = async (uiArray: string[]) => { |
||||
|
return inquirer |
||||
|
.prompt({ type: 'list', name: 'Please select an UI', choices: uiArray }) |
||||
|
.then(res => res['Please select an UI']); |
||||
|
}; |
||||
|
|
||||
|
export const moduleSelection = async (modules: string[]) => { |
||||
|
return inquirer |
||||
|
.prompt({ type: 'checkbox', name: 'Please select module(s)', choices: modules }) |
||||
|
.then(res => res['Please select module(s)']); |
||||
|
}; |
||||
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"target": "es5", |
||||
|
"module": "commonjs", |
||||
|
"lib": ["es6", "es2015", "dom"], |
||||
|
"declaration": true, |
||||
|
"outDir": "lib", |
||||
|
"rootDir": "src", |
||||
|
"strict": true, |
||||
|
"types": ["node"], |
||||
|
"esModuleInterop": true, |
||||
|
"resolveJsonModule": true, |
||||
|
}, |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,39 @@ |
|||||
|
# COMMON PATHS |
||||
|
|
||||
|
$rootFolder = (Get-Item -Path "./" -Verbose).FullName |
||||
|
|
||||
|
# List of solutions |
||||
|
|
||||
|
$solutionPaths = ( |
||||
|
"framework", |
||||
|
"modules/users", |
||||
|
"modules/permission-management", |
||||
|
"modules/setting-management", |
||||
|
"modules/feature-management", |
||||
|
"modules/identity", |
||||
|
"modules/identityserver", |
||||
|
"modules/tenant-management", |
||||
|
"modules/account", |
||||
|
"modules/docs", |
||||
|
"modules/blogging", |
||||
|
"modules/audit-logging", |
||||
|
"modules/background-jobs", |
||||
|
"modules/client-simulation", |
||||
|
"templates/module/aspnet-core", |
||||
|
"templates/app/aspnet-core" |
||||
|
) |
||||
|
|
||||
|
# Test all solutions |
||||
|
|
||||
|
foreach ($solutionPath in $solutionPaths) { |
||||
|
$solutionAbsPath = (Join-Path $rootFolder $solutionPath) |
||||
|
Set-Location $solutionAbsPath |
||||
|
dotnet test --no-build --no-restore |
||||
|
if (-Not $?) { |
||||
|
Write-Host ("Test failed for the solution: " + $solutionPath) |
||||
|
Set-Location $rootFolder |
||||
|
exit $LASTEXITCODE |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Set-Location $rootFolder |
||||
Loading…
Reference in new issue