mirror of https://github.com/abpframework/abp.git
40 changed files with 7018 additions and 33 deletions
@ -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,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
Loading…
Reference in new issue