@shys/crud 1.0.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ import { Controller, Inject } from '@midwayjs/core';
2
+ import { CrudPiped } from '@shys/crud-piped';
3
+ import { {{ Pascal }} } from '../entity/{{ Camel }}.entity';
4
+ import { {{ ServiceName }} } from '../service/{{ Camel }}.service';
5
+ import {
6
+ {{ CreateName }},
7
+ {{ UpdateName }},
8
+ {{ ReplaceName }},
9
+ {{ QueryName }},
10
+ } from '../dto/{{ Camel }}';
11
+ import { {{ VOName }}, {{ ListVOName }} } from '../vo/{{ Camel }}';
12
+
13
+ @Controller({{ ('/api/' + pluralKebab) | json }}, {
14
+ tagName: {{ tagName | json }},
15
+ description: {{ desc | json }},
16
+ })
17
+ @CrudPiped({
18
+ model: {{ Pascal }},
19
+ service: {{ ServiceName }},
20
+ id: {{ idField | json }},
21
+ dto: {
22
+ create: {{ CreateName }},
23
+ update: {{ UpdateName }},
24
+ replace: {{ ReplaceName }},
25
+ query: {{ QueryName }},
26
+ },
27
+ serialize: {
28
+ get: {{ VOName }},
29
+ list: {{ ListVOName }},
30
+ create: {{ VOName }},
31
+ update: {{ VOName }},
32
+ },
33
+ routes: {
34
+ only: ['list', 'detail', 'create', 'update', 'replace', 'delete'],
35
+ },
36
+ query: {
37
+ maxLimit: 100,
38
+ defaultLimit: 10,
39
+ sortable: [{{ sortable }}],
40
+ filterable: [{{ filterable }}],
41
+ searchable: [{{ searchable }}],
42
+ defaultSort: {{ defaultSort }},
43
+ },
44
+ piped: {
45
+ create: {
46
+ enabled: true,
47
+ stages: ['deserialize', 'sanitize', 'mapFields', 'transform', 'validate'],
48
+ validate: { enabled: true, errorStatus: 422 },
49
+ },
50
+ update: {
51
+ enabled: true,
52
+ stages: ['deserialize', 'sanitize', 'mapFields', 'transform', 'validate'],
53
+ validate: { enabled: true, errorStatus: 422 },
54
+ },
55
+ replace: {
56
+ enabled: true,
57
+ stages: ['deserialize', 'sanitize', 'mapFields', 'transform', 'validate'],
58
+ validate: { enabled: true, errorStatus: 422 },
59
+ },
60
+ },
61
+ })
62
+ export class {{ CtrlName }} {
63
+ @Inject()
64
+ crudService: {{ ServiceName }};
65
+ }
@@ -0,0 +1,59 @@
1
+ import { ApiProperty } from '@midwayjs/swagger';
2
+ import { Exclude, Expose, Type } from 'class-transformer';
3
+
4
+ @Exclude()
5
+ export class CrudPageMetaVO {
6
+ @ApiProperty({ type: 'integer', description: '当前页码,从 1 开始', example: 1 })
7
+ @Expose()
8
+ page: number;
9
+
10
+ @ApiProperty({ type: 'integer', description: '每页条数', example: 10 })
11
+ @Expose()
12
+ limit: number;
13
+
14
+ @ApiProperty({ type: 'integer', description: '总记录数', example: 1024 })
15
+ @Expose()
16
+ total: number;
17
+
18
+ @ApiProperty({ type: 'integer', description: '总页数', example: 103 })
19
+ @Expose()
20
+ pageCount: number;
21
+
22
+ @ApiProperty({ type: 'boolean', description: '是否存在下一页', example: true })
23
+ @Expose()
24
+ hasNext: boolean;
25
+
26
+ @ApiProperty({ type: 'boolean', description: '是否存在上一页', example: false })
27
+ @Expose()
28
+ hasPrev: boolean;
29
+ }
30
+
31
+ export interface CrudPageResult<T> {
32
+ data: T[];
33
+ meta: CrudPageMetaVO;
34
+ }
35
+
36
+ type ClassLike<T = any> = new (...args: any[]) => T;
37
+
38
+ export function createCrudPageListVO<T extends ClassLike>(
39
+ ItemVOClass: T,
40
+ options?: { className?: string }
41
+ ): ClassLike<CrudPageResult<InstanceType<T>>> {
42
+ const baseName = options?.className || \`\${ItemVOClass.name}ListVO\`;
43
+
44
+ @Exclude()
45
+ class ListVO implements CrudPageResult<InstanceType<T>> {
46
+ @ApiProperty({ type: () => [ItemVOClass], description: '当前页数据列表' })
47
+ @Expose()
48
+ @Type(() => ItemVOClass)
49
+ data: InstanceType<T>[];
50
+
51
+ @ApiProperty({ type: () => CrudPageMetaVO, description: '分页元信息' })
52
+ @Expose()
53
+ @Type(() => CrudPageMetaVO)
54
+ meta: CrudPageMetaVO;
55
+ }
56
+
57
+ Object.defineProperty(ListVO, 'name', { value: baseName, writable: false });
58
+ return ListVO as ClassLike<CrudPageResult<InstanceType<T>>>;
59
+ }
@@ -0,0 +1,18 @@
1
+ import { Rule } from '@midwayjs/validation';
2
+ import { ApiPropertyOptional } from '@midwayjs/swagger';
3
+ import * as Joi from 'joi';
4
+
5
+ export class {{ CreateName }} {
6
+ {% for f in createFields -%}
7
+ @ApiPropertyOptional({
8
+ type: {{ f.swagType | json }},
9
+ {% if f.len %} maxLength: {{ f.len }},
10
+ {% endif %}{% if f.nullable %} nullable: true,
11
+ {% endif %} description: {{ (f.comment or f.camel) | json }},
12
+ {% if f.defaultVal != null %} example: {{ f.defaultVal | json }},
13
+ {% endif %} })
14
+ @Rule({{ f.joiRule }}.description({{ (f.comment or f.camel) | json }}))
15
+ {{ f.camel }}?: {{ f.createDtoType }};
16
+
17
+ {% endfor -%}
18
+ }
@@ -0,0 +1,4 @@
1
+ export * from './create.dto';
2
+ export * from './update.dto';
3
+ export * from './replace.dto';
4
+ export * from './query.dto';
@@ -0,0 +1,71 @@
1
+ import { Rule, PickDto } from '@midwayjs/validation';
2
+ import { ApiPropertyOptional } from '@midwayjs/swagger';
3
+ import * as Joi from 'joi';
4
+ import { {{ CreateName }} } from './create.dto';
5
+
6
+ type _QueryFilterFields = Pick<{{ CreateName }}, {{ typeArgs }}>;
7
+
8
+ const _QueryFilterPicked = PickDto({{ CreateName }}, [
9
+ {{ picks }}
10
+ ]) as unknown as new () => Partial<_QueryFilterFields>;
11
+
12
+ export class {{ QueryName }} extends _QueryFilterPicked {
13
+ {% for f in filterable -%}
14
+ @ApiPropertyOptional({
15
+ type: {{ f.swagType | json }},
16
+ {% if f.len %} maxLength: {{ f.len }},
17
+ {% endif %} description: {{ ((f.comment or f.camel) + '(列表筛选条件,可选)') | json }},
18
+ })
19
+ override {{ f.camel }}?: {{ f.jsType }};
20
+
21
+ {% endfor -%}
22
+ {% if filterable.length %}
23
+
24
+ {% endif %} @ApiPropertyOptional({
25
+ type: 'integer',
26
+ minimum: 1,
27
+ default: 1,
28
+ description: '当前页码,从1开始',
29
+ example: 1,
30
+ })
31
+ @Rule(Joi.number().integer().min(1).default(1).description('当前页码,从1开始,默认1'))
32
+ page?: number;
33
+
34
+ @ApiPropertyOptional({
35
+ type: 'integer',
36
+ minimum: 1,
37
+ maximum: 100,
38
+ default: 10,
39
+ description: '每页条数,范围 1-100',
40
+ example: 10,
41
+ })
42
+ @Rule(Joi.number().integer().min(1).max(100).default(10).description('每页条数,1-100,默认10'))
43
+ limit?: number;
44
+
45
+ @ApiPropertyOptional({
46
+ type: 'string',
47
+ description: {{ sortDescription | json }},
48
+ example: 'id:DESC',
49
+ })
50
+ @Rule(Joi.string().description('排序字段,例如 id:ASC 或 name:DESC,id:DESC'))
51
+ sort?: string;
52
+
53
+ @ApiPropertyOptional({
54
+ type: 'string',
55
+ description:
56
+ '过滤条件(兼容两种写法)。官方标准:field||op||value,多条件多次传或逗号分隔。简写:field:op:value 逗号分隔。' +
57
+ '操作符 eq/ne/gt/gte/lt/lte/in/like,别名 ==/!=/>/>=/</<=/contains。' +
58
+ '示例:age||gte||18,name||like||张 或 age:gte:18,name:like:张',
59
+ example: {{ filterExample | json }},
60
+ })
61
+ @Rule(Joi.string().description('过滤条件,支持 field||op||value 或 field:op:value,逗号分隔多条件'))
62
+ filter?: string;
63
+
64
+ @ApiPropertyOptional({
65
+ type: 'string',
66
+ description: {{ searchDescription | json }},
67
+ example: 'zhang',
68
+ })
69
+ @Rule(Joi.string().description('搜索关键字,用于模糊搜索可搜索字段'))
70
+ search?: string;
71
+ }
@@ -0,0 +1,21 @@
1
+ import { Rule, PickDto } from '@midwayjs/validation';
2
+ import { ApiProperty } from '@midwayjs/swagger';
3
+ import * as Joi from 'joi';
4
+ import { {{ CreateName }} } from './create.dto';
5
+
6
+ export class {{ ReplaceName }} extends PickDto({{ CreateName }}, [
7
+ {{ picks }}
8
+ ]) {
9
+ {% for f in writable -%}
10
+ @ApiProperty({
11
+ type: {{ f.swagType | json }},
12
+ {% if f.len %} maxLength: {{ f.len }},
13
+ {% endif %}{% if f.nullable %} nullable: true,
14
+ {% endif %} required: true,
15
+ description: {{ ((f.comment or f.camel) + '(PUT:必须完整传入)') | json }},
16
+ })
17
+ @Rule({{ f.joiRuleRequired }}.description({{ (f.comment or f.camel) | json }}))
18
+ override {{ f.camel }}: {{ f.createDtoType }};
19
+
20
+ {% endfor -%}
21
+ }
@@ -0,0 +1,22 @@
1
+ import { ApiPropertyOptional } from '@midwayjs/swagger';
2
+ import { PickDto } from '@midwayjs/validation';
3
+ import { {{ CreateName }} } from './create.dto';
4
+
5
+ type _UpdateFields = Pick<{{ CreateName }}, {{ typeArgs }}>;
6
+
7
+ const _UpdatePicked = PickDto({{ CreateName }}, [
8
+ {{ picks }}
9
+ ]) as unknown as new () => Partial<_UpdateFields>;
10
+
11
+ export class {{ UpdateName }} extends _UpdatePicked {
12
+ {% for f in writable -%}
13
+ @ApiPropertyOptional({
14
+ type: {{ f.swagType | json }},
15
+ {% if f.len %} maxLength: {{ f.len }},
16
+ {% endif %}{% if f.nullable %} nullable: true,
17
+ {% endif %} description: {{ ((f.comment or f.camel) + '(PATCH:不传则不修改)') | json }},
18
+ })
19
+ override {{ f.camel }}?: {{ f.createDtoType }};
20
+
21
+ {% endfor -%}
22
+ }
@@ -0,0 +1,60 @@
1
+ {% if orm !== 'typeorm' -%}
2
+ // {{ orm }} Entity 模板占位(TypeORM 是当前唯一完整生成的 ORM)
3
+ export class {{ Pascal }} {}
4
+ {%- else -%}
5
+ import { Entity, Column{% if hasPk %}, PrimaryGeneratedColumn{% endif %}{% if idxCount %}, Index{% endif %} } from 'typeorm';
6
+ import { Expose{% if hasDateField %}, Transform{% endif %} } from 'class-transformer';
7
+ import { ApiProperty, ApiPropertyOptional } from '@midwayjs/swagger';
8
+ import { Rule } from '@midwayjs/validation';
9
+ import { z } from 'zod';
10
+ import { BaseEntity } from '../../../framework/entity/base.entity';
11
+
12
+ @Entity({{ tableName | json }}, {
13
+ comment: {{ (tableName + ' 表') | json }},
14
+ })
15
+ export class {{ Pascal }} extends BaseEntity {
16
+ {% if hasPk and pkInfo -%}
17
+ @PrimaryGeneratedColumn({
18
+ type: {{ pkInfo.type | json }},
19
+ name: {{ pkInfo.name | json }},
20
+ comment: {{ (pkInfo.comment or '主键') | json }},
21
+ })
22
+ @ApiProperty({
23
+ type: 'integer',
24
+ description: {{ (pkInfo.comment or '主键ID') | json }},
25
+ })
26
+ @Expose()
27
+ @Rule(z.number().int().describe({{ (pkInfo.comment or '主键ID') | json }}))
28
+ {{ pkInfo.camel }}: number;
29
+
30
+ {% endif -%}
31
+ {% for f in fields -%}
32
+ {% if f.idx -%} @Index()
33
+ {% endif -%} @Column({
34
+ type: {{ f.ormType | json }},
35
+ {% if f.supportsLength and f.len %} length: {{ f.len }},
36
+ {% endif %} name: {{ f.name | json }},
37
+ nullable: {% if f.nullable %}true{% else %}false{% endif %},
38
+ {% if f.defaultVal != null %} default: {{ f.defaultVal | json }},
39
+ {% endif %}{% if f.comment %} comment: {{ f.comment | json }},
40
+ {% endif %} })
41
+ @ApiPropertyOptional({
42
+ type: {{ f.swagType | json }},
43
+ {% if f.supportsLength and f.len %} maxLength: {{ f.len }},
44
+ {% endif %}{% if f.nullable %} nullable: true,
45
+ {% endif %} description: {{ (f.comment or f.camel) | json }},
46
+ {% if f.defaultVal != null %} example: {{ f.defaultVal | json }},
47
+ {% endif %} })
48
+ @Expose()
49
+ @Rule({{ f.zodRule }}.nullable().optional())
50
+ {% if f.isDate %} @Transform(({ value }) => {
51
+ if (value == null) return null;
52
+ const d = value instanceof Date ? value : new Date(value);
53
+ if (isNaN(d.getTime())) return value == null ? null : String(value);
54
+ return d.toISOString();
55
+ })
56
+ {% endif %} {{ f.camel }}?: {{ f.jsType }};
57
+
58
+ {% endfor -%}
59
+ }
60
+ {%- endif %}
@@ -0,0 +1,20 @@
1
+ {% if ormInfo.orm === 'typeorm' -%}
2
+ import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
3
+ import { InjectEntityModel } from '@midwayjs/typeorm';
4
+ import { Repository } from 'typeorm';
5
+ import { {{ ormInfo.serviceParent }} } from '@midwayjs/crud/typeorm';
6
+ import { {{ Pascal }} } from '../entity/{{ Camel }}.entity';
7
+
8
+ @Provide()
9
+ @Scope(ScopeEnum.Request, { allowDowngrade: true })
10
+ export class {{ ServiceName }} extends {{ ormInfo.serviceParent }}<{{ Pascal }}> {
11
+ @InjectEntityModel({{ Pascal }})
12
+ repo: Repository<{{ Pascal }}>;
13
+ }
14
+ {%- else -%}
15
+ // {{ ormInfo.orm }} Service 模板(请按项目注入方式调整)
16
+ import { Provide } from '@midwayjs/core';
17
+
18
+ @Provide()
19
+ export class {{ ServiceName }} {}
20
+ {%- endif %}
@@ -0,0 +1,2 @@
1
+ export * from './{{ Camel }}.vo';
2
+ export * from './{{ Camel }}List.vo';
@@ -0,0 +1,15 @@
1
+ import {
2
+ CrudPageMetaVO,
3
+ createCrudPageListVO,
4
+ type CrudPageResult,
5
+ } from '../../../../framework/vo/crudPage.vo';
6
+ import { {{ VOName }} } from './{{ Camel }}.vo';
7
+
8
+ export { CrudPageMetaVO };
9
+ export type { CrudPageResult };
10
+
11
+ export const {{ ListVOName }} = createCrudPageListVO({{ VOName }}, {
12
+ className: {{ ListVOName | json }},
13
+ });
14
+
15
+ export type {{ ListVOName }}Type = CrudPageResult<{{ VOName }}>;
@@ -0,0 +1,24 @@
1
+ import { Exclude, Expose{% if hasTransform %}, Transform{% endif %} } from 'class-transformer';
2
+ import { ApiProperty } from '@midwayjs/swagger';
3
+
4
+ @Exclude()
5
+ export class {{ VOName }} {
6
+ {% for f in fields -%}
7
+ @ApiProperty({
8
+ type: {{ f.swagType | json }},
9
+ {% if f.len %} maxLength: {{ f.len }},
10
+ {% endif %}{% if f.nullable %} nullable: true,
11
+ {% endif %} description: {{ (f.comment or f.camel) | json }},
12
+ })
13
+ @Expose()
14
+ {% if f.isDate %} @Transform(({ value }) => {
15
+ if (value == null) return null;
16
+ const d = value instanceof Date ? value : new Date(value);
17
+ if (isNaN(d.getTime())) return value == null ? null : String(value);
18
+ return d.toISOString();
19
+ })
20
+ {% endif %}{% if f.isSensitive %} @Transform(() => undefined, { toPlainOnly: true })
21
+ {% endif %} {{ f.camel }}: {{ f.voType }}{% if f.nullable %} | null{% endif %};
22
+
23
+ {% endfor -%}
24
+ }