Skip to content

Commit

Permalink
feat: category crud
Browse files Browse the repository at this point in the history
  • Loading branch information
SolidZORO committed May 22, 2020
1 parent 434b6a8 commit adbf2c4
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 211 deletions.
11 changes: 0 additions & 11 deletions packages/_leaa-common/src/dtos/article/update-article.input.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,30 @@
import { IsOptional } from 'class-validator';
import { Field, InputType, Int } from '@nestjs/graphql';

@InputType()
export class UpdateArticleInput {
@IsOptional()
@Field(() => String, { nullable: true })
title?: string;

@IsOptional()
@Field(() => String, { nullable: true })
slug?: string;

@IsOptional()
@Field(() => String, { nullable: true })
user_id?: string;

@IsOptional()
@Field(() => String, { nullable: true })
description?: string;

@IsOptional()
@Field(() => String, { nullable: true })
content?: string;

@IsOptional()
@Field(() => Int, { nullable: true })
status?: number;

@IsOptional()
@Field(() => Date, { nullable: true })
released_at?: Date;

@IsOptional()
@Field(() => [String], { nullable: true })
categoryIds?: string[] | null;

@IsOptional()
@Field(() => [String], { nullable: true })
tagIds?: string[] | null;
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { IsNotEmpty } from 'class-validator';
import { Field, InputType, Int } from '@nestjs/graphql';
import { Category } from '@leaa/common/src/entrys';

@InputType()
export class CreateCategoryInput {
@IsNotEmpty()
@Field(() => String)
name!: string;

@IsNotEmpty()
@Field(() => String)
slug!: string;

@Field(() => String, { nullable: true })
parent_id?: string;

@Field(() => String, { nullable: true })
description?: string;

parent?: Category | null;
}
69 changes: 42 additions & 27 deletions packages/leaa-api/src/modules/article/article.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import _ from 'lodash';
import moment from 'moment';
import { Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { CrudRequest } from '@nestjsx/crud';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { plainToClass } from 'class-transformer';

import { Article, Tag, Category } from '@leaa/common/src/entrys';
import { Repository } from 'typeorm';
import { CrudRequest } from '@nestjsx/crud';
import { UpdateArticleInput } from '@leaa/common/src/dtos/article';
import { TagService } from '@leaa/api/src/modules/tag/tag.service';
import { plainToClass } from 'class-transformer';
import moment from 'moment';
import { formatHtmlToText, cutTags } from '@leaa/api/src/utils';

export interface ITransIdsToEntrys {
dto: any;
toSave: any;
idName: any;
sName: string;
idField: any;
saveField: string;
repo: any;
}

Expand All @@ -30,24 +31,6 @@ export class ArticleService extends TypeOrmCrudService<Article> {
super(articleRepo);
}

async transIdsToEntrys({ dto, toSave, idName, sName, repo }: ITransIdsToEntrys) {
const dtoIds: any = dto[idName];

/* eslint-disable no-param-reassign */

if (dtoIds === null) toSave[sName] = [];
if (dtoIds && dtoIds.length) {
const items = await repo.findByIds(dtoIds);

if (!_.isEmpty(items) && !_.isEqual(items, toSave[sName])) {
toSave[sName] = items;
toSave.updated_at = moment().toDate();
}
}

/* eslint-enable */
}

async updateOne(req: CrudRequest, dto: UpdateArticleInput): Promise<Article> {
const { allowParamsOverride, returnShallow } = req.options.routes?.updateOneBase || {};

Expand All @@ -57,15 +40,24 @@ export class ArticleService extends TypeOrmCrudService<Article> {
? { ...found, ...dto, ...paramsFilters, ...req.parsed.authPersist }
: { ...found, ...dto, ...req.parsed.authPersist };

await this.transIdsToEntrys({ dto, toSave, idName: 'categoryIds', sName: 'categories', repo: this.categoryRepo });
await this.transIdsToEntrys({ dto, toSave, idName: 'tagIds', sName: 'tags', repo: this.tagRepo });
await this.formatRelationIdsToSave({
dto,
toSave,
idField: 'categoryIds',
saveField: 'categories',
repo: this.categoryRepo,
});
await this.formatRelationIdsToSave({ dto, toSave, idField: 'tagIds', saveField: 'tags', repo: this.tagRepo });

// // auto add tag from article content (by jieba)
// if (dto.content && (!dto.tagIds || (dto.tagIds && dto.tagIds.length === 0))) {
// const allText = formatHtmlToText(dto.content, dto.title);
//
//
// console.log(cutTags(allText));
//
// // batch create tags
// relationArgs.tags = await this.tagService.createTags(gqlCtx, cutTags(allText));
// toSave.tags = await this.tagService.createTags(cutTags(allText));
//
// // ⚠️ sync tags
// // execute only once when the article has no tag, reducing server pressure
Expand All @@ -83,4 +75,27 @@ export class ArticleService extends TypeOrmCrudService<Article> {

return this.getOneOrFail(req);
}

//
//

async formatRelationIdsToSave({ dto, toSave, idField, saveField, repo }: ITransIdsToEntrys) {
const dtoIds: any = dto[idField];

/* eslint-disable no-param-reassign */

if (dtoIds === null) toSave[saveField] = [];

if (dtoIds && dtoIds.length) {
const items = await repo.findByIds(dtoIds);

// 如果 relation 有更新,item 的时间也会更新
if (!_.isEmpty(items) && !_.isEqual(items, toSave[saveField])) {
toSave[saveField] = items;
toSave.updated_at = moment().toDate();
}
}

/* eslint-enable */
}
}
16 changes: 14 additions & 2 deletions packages/leaa-api/src/modules/category/category.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, UseGuards, Get, Req, Query } from '@nestjs/common';
import { Crud, CrudController, CrudRequest } from '@nestjsx/crud';
import { Crud, CrudController, CrudRequest, Override, ParsedRequest, ParsedBody } from '@nestjsx/crud';

import { Permissions } from '@leaa/api/src/decorators';
import { CreateCategoryInput, UpdateCategoryInput } from '@leaa/common/src/dtos/category';
Expand Down Expand Up @@ -37,8 +37,20 @@ import { CategoryService } from './category.service';
export class CategoryController implements CrudController<Category> {
constructor(public service: CategoryService) {}

@Override('createOneBase')
createOne(@ParsedRequest() req: CrudRequest, @ParsedBody() dto: Category & CreateCategoryInput): Promise<Category> {
return this.service.createOne(req, dto);
}

@Override('updateOneBase')
updateOne(@ParsedRequest() req: CrudRequest, @ParsedBody() dto: Category & UpdateCategoryInput): Promise<Category> {
return this.service.updateOne(req, dto);
}

//
//

@Get('tree')
// async tree(@ParsedRequest() req: CrudRequest) {
async tree(@Req() req: CrudRequest, @Query() query: ICategoriesQuery) {
return this.service.tree(query);
}
Expand Down
39 changes: 34 additions & 5 deletions packages/leaa-api/src/modules/category/category.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
import _ from 'lodash';
import moment from 'moment';
import { TreeRepository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { CrudRequest } from '@nestjsx/crud';

import { Category } from '@leaa/common/src/entrys';
import { TreeRepository } from 'typeorm';
import moment from 'moment';
import { ICategoriesQuery, ICategoryArgs } from '@leaa/api/src/interfaces';
import { CategoryTreeObject } from '@leaa/common/src/dtos/category';
import { ICategoriesQuery } from '@leaa/api/src/interfaces';
import { CategoryTreeObject, CreateCategoryInput, UpdateCategoryInput } from '@leaa/common/src/dtos/category';

@Injectable()
export class CategoryService extends TypeOrmCrudService<Category> {
constructor(@InjectRepository(Category) private readonly categoryRepo: TreeRepository<Category>) {
super(categoryRepo as TreeRepository<Category>);
super(categoryRepo);
}

async createOne(req: CrudRequest, dto: Category & CreateCategoryInput): Promise<Category> {
const nextDto = {
...dto,
parent: await this.formatParentId(dto.parent_id),
};

return super.createOne(req, nextDto);
}

async updateOne(req: CrudRequest, dto: Category & UpdateCategoryInput): Promise<Category> {
const nextDto = {
...dto,
parent: await this.formatParentId(dto.parent_id),
};

return super.updateOne(req, nextDto);
}

//
//

async formatParentId(parentId?: string | null) {
if (parentId === '----' || null) return null;
if (parentId) return this.categoryRepo.findOneOrFail(parentId);

return null;
}

async tree(options?: ICategoriesQuery) {
Expand Down
2 changes: 1 addition & 1 deletion packages/leaa-api/src/modules/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class UserController implements CrudController<User> {
constructor(public service: UserService) {}

@Override('createOneBase')
createOne(@ParsedRequest() req: CrudRequest, @ParsedBody() dto: CreateUserInput): Promise<User> {
createOne(@ParsedRequest() req: CrudRequest, @ParsedBody() dto: User & CreateUserInput): Promise<User> {
return this.service.createOne(req, dto);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/leaa-api/src/modules/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class UserService extends TypeOrmCrudService<User> {
super(userRepo);
}

async createOne(req: CrudRequest, dto: CreateUserInput | User): Promise<User> {
async createOne(req: CrudRequest, dto: User & CreateUserInput): Promise<User> {
const nextDto = dto;
if (dto.password) nextDto.password = await this.createPassword(dto.password);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ interface IProps {
loading?: boolean;
}

/**
* 关于 categoryIds 为空的处理方式
*
* @ideaNotes
* categoryIds 如果没有值,请设置为 null,以强表示为清空,API 看到这个值会做清空处理,
* 如果为 '' 字符或 undefined,API 都不会做处理,因为意图不够明确。
*
* 所以有两个地方需要注意,
* - Input | `setFieldsValue`,
* - Output | Form.Item `normalize={(e) => e || null}`
*/
export const ArticleInfoForm = forwardRef((props: IProps, ref: React.Ref<any>) => {
const { t } = useTranslation();
const [form] = Form.useForm();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from 'antd';

import { Category } from '@leaa/common/src/entrys';
import { UPDATE_BUTTON_ICON } from '@leaa/dashboard/src/constants';
import { UpdateCategoryInput } from '@leaa/common/src/dtos/category';
import { IPage, ICommenFormRef, ISubmitData, IHttpRes, IHttpError } from '@leaa/dashboard/src/interfaces';
import { msg, errorMsg, ajax } from '@leaa/dashboard/src/utils';

import { envConfig } from '@leaa/dashboard/src/configs';
import { PageCard, HtmlMeta, Rcon, SubmitBar } from '@leaa/dashboard/src/components';

import { CategoryInfoForm } from '../_components/CategoryInfoForm/CategoryInfoForm';

import style from './style.module.less';

const API_PATH = 'categories';

export default (props: IPage) => {
const { t } = useTranslation();

const infoFormRef = useRef<ICommenFormRef<UpdateCategoryInput>>(null);

const [submitLoading, setSubmitLoading] = useState(false);

const onCreateItem = async () => {
const infoData: ISubmitData<UpdateCategoryInput> = await infoFormRef.current?.onValidateForm();

if (!infoData) return;

const data: ISubmitData<UpdateCategoryInput> = {
...infoData,
};

setSubmitLoading(true);

ajax
.post(`${envConfig.API_URL}/${API_PATH}`, data)
.then((res: IHttpRes<Category>) => {
msg(t('_lang:createdSuccessfully'));

props.history.push(`/${API_PATH}/${res.data.data?.id}`);
})
.catch((err: IHttpError) => errorMsg(err.response?.data?.message || err.message))
.finally(() => setSubmitLoading(false));
};

return (
<PageCard route={props.route} title="@CREATE" className={style['wapper']} loading={submitLoading}>
<HtmlMeta title={t(`${props.route.namei18n}`)} />

<CategoryInfoForm ref={infoFormRef} />

<SubmitBar full>
<Button
type="primary"
size="large"
icon={<Rcon type={UPDATE_BUTTON_ICON} />}
className="g-submit-bar-button"
loading={submitLoading}
onClick={onCreateItem}
>
{t('_lang:update')}
</Button>
</SubmitBar>
</PageCard>
);
};

0 comments on commit adbf2c4

Please sign in to comment.