mirror of https://github.com/abpframework/abp.git
40 changed files with 7018 additions and 33 deletions
@ -1,3 +1,222 @@ |
|||
# 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