feat: 解决冲突
commit
4647b829c3
|
|
@ -5,9 +5,9 @@ on:
|
|||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- ".gitignore"
|
||||
- "README.md"
|
||||
- ".vscode/**"
|
||||
- .gitignore
|
||||
- .vscode/**
|
||||
- README.md
|
||||
|
||||
env:
|
||||
docker_host: ${{ secrets.DOCKER_REGISTRY }}
|
||||
|
|
@ -32,16 +32,16 @@ jobs:
|
|||
|
||||
- name: 构建镜像
|
||||
run: |
|
||||
docker build -t ${{ env.docker_name }}:latest .
|
||||
|
||||
docker build -t ${{ env.docker_name }}:latest .
|
||||
|
||||
- name: 登陆镜像
|
||||
run: |
|
||||
docker login -u "${{env.docker_user}}" -p "${{env.docker_pass}}" ${{env.docker_host}}
|
||||
docker login -u "${{env.docker_user}}" -p "${{env.docker_pass}}" ${{env.docker_host}}
|
||||
|
||||
- name: 推送镜像
|
||||
shell: bash
|
||||
run: |
|
||||
docker push ${{ env.docker_name }}:latest
|
||||
docker push ${{ env.docker_name }}:latest
|
||||
|
||||
- name: 更新服务
|
||||
uses: http://git.dev.juetan.cn/mirror/ssh-action@v1.0.0
|
||||
|
|
@ -52,4 +52,4 @@ jobs:
|
|||
password: ${{ env.deploy_pass }}
|
||||
script: |
|
||||
docker service ls | grep -q ${{ env.deploy_name }} || exit 0
|
||||
docker service update --image ${{ env.docker_name }}:latest ${{ env.deploy_name }}
|
||||
docker service update --image ${{ env.docker_name }}:latest ${{ env.deploy_name }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
FROM node:18-alpine As dev
|
||||
RUN apk update && apk add sqlite
|
||||
RUN apk add --no-cache --virtual .build-deps g++ gcc libgcc libstdc++ linux-headers make python3
|
||||
WORKDIR /app
|
||||
COPY package*.json .
|
||||
RUN npm install
|
||||
|
|
@ -16,4 +18,3 @@ COPY --from=dev /app/package.json ./
|
|||
|
||||
EXPOSE 3030
|
||||
CMD [ "node", "./dist/main.js" ]
|
||||
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -26,4 +26,18 @@
|
|||
- 角色模块
|
||||
- 权限模块
|
||||
- 上传模块
|
||||
- 文章模块
|
||||
- 文章模块
|
||||
|
||||
## 部署
|
||||
|
||||
目前基于 Gitea 和 Gitea Actions 实现,大致流程是这样的:提交代码到 Gitea 仓库后,触发流水线任务进行构建并打包成 Docker 镜像,推送到 Gitea 自带的软件包仓库,然后登陆生产服务器执行更新命令。
|
||||
|
||||
使用 Github Actions 也是可以的,两者使用上是兼容的。本仓库有关部署的内容涉及三个地方,不需要的话可自行删除,如下:
|
||||
|
||||
- Dockerfile 构建镜像的配置文件
|
||||
- .dockerignore 配置哪些文件应该被忽略掉
|
||||
- .gitea/workflows/depoy.yaml 流水线任务的配置文件,语法上与 Github Actions 一致
|
||||
|
||||
## 最后
|
||||
|
||||
如果你在使用过程中遇到问题,欢迎在 Issue 中提问。
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -33,6 +33,7 @@
|
|||
"@nestjs/config": "^2.3.4",
|
||||
"@nestjs/jwt": "^10.1.1",
|
||||
"@nestjs/platform-express": "^9.4.3",
|
||||
"@nestjs/cache-manager": "^2.1.0",
|
||||
"axios": "^1.5.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
|
|
@ -45,10 +46,8 @@
|
|||
"typeorm-naming-strategies": "^4.1.0",
|
||||
"ua-parser-js": "^1.0.36",
|
||||
"uuid": "^9.0.1",
|
||||
"winston": "^3.10.0",
|
||||
"nodemailer": "^6.9.5",
|
||||
"mysql2": "^3.6.1",
|
||||
"@nestjs/cache-manager": "^2.1.0",
|
||||
"cache-manager": "^5.2.3",
|
||||
"cache-manager-redis-store": "^3.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
@ -57,6 +56,7 @@
|
|||
"multer": "1.4.5-lts.1",
|
||||
"redis": "^4.6.8",
|
||||
"sqlite3": "^5.1.6",
|
||||
"winston": "^3.10.0",
|
||||
"winston-daily-rotate-file": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -107,4 +107,4 @@
|
|||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PostModule } from '@/modules/post';
|
||||
import { PostModule } from '@/content/post';
|
||||
import { RoleModule } from '@/modules/role';
|
||||
import { UploadModule } from '@/modules/upload';
|
||||
import { UploadModule } from '@/storage/upload';
|
||||
import { PermissionModule } from '@/modules/permission';
|
||||
import { ConfigModule } from '@/config';
|
||||
import { LoggerModule } from '@/common/logger';
|
||||
|
|
@ -12,8 +12,9 @@ import { AuthModule } from '@/modules/auth';
|
|||
import { UserModule } from '@/modules/user';
|
||||
import { ResponseModule } from '@/common/response';
|
||||
import { SerializationModule } from '@/common/serialization';
|
||||
import { CacheModule } from './common/cache';
|
||||
import { ScanModule } from './utils/scan.module';
|
||||
import { CacheModule } from '@/storage/cache';
|
||||
import { ScanModule } from '@/utils/scan.module';
|
||||
import { ContentModule } from '@/content/content.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -61,6 +62,8 @@ import { ScanModule } from './utils/scan.module';
|
|||
* @description 用于连接数据库
|
||||
*/
|
||||
DatabaseModule,
|
||||
|
||||
|
||||
/**
|
||||
* 用户模块
|
||||
*/
|
||||
|
|
@ -77,6 +80,8 @@ import { ScanModule } from './utils/scan.module';
|
|||
* 权限模块
|
||||
*/
|
||||
PermissionModule,
|
||||
|
||||
|
||||
/**
|
||||
* 上传模块
|
||||
*/
|
||||
|
|
@ -85,6 +90,7 @@ import { ScanModule } from './utils/scan.module';
|
|||
* 文章模块
|
||||
*/
|
||||
PostModule,
|
||||
ContentModule
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class AllExecptionFilter implements ExceptionFilter {
|
|||
return response.status(exception.status).json(
|
||||
Response.create({
|
||||
code: ResponseCode.ERROR,
|
||||
message: '访问的路径不存在',
|
||||
message: '路径不存在',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ export class AllExecptionFilter implements ExceptionFilter {
|
|||
return response.status(HttpStatus.UNAUTHORIZED).json(
|
||||
Response.create({
|
||||
code: ResponseCode.TOKEN_EXPIRED,
|
||||
message: '登陆已过期'
|
||||
message: '登陆令牌已过期'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import { BaseController } from '@/common/base';
|
||||
import { Respond, RespondType } from '@/common/response';
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, ParseIntPipe } from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||
import { FindCategoryDto } from './dto/find-category.dto';
|
||||
import { UpdateCategoryDto } from './dto/update-category.dto';
|
||||
import { Category } from './entities/category.entity';
|
||||
import { CategoryService } from './category.service';
|
||||
|
||||
@ApiTags('category')
|
||||
@Controller('categories')
|
||||
export class CategoryController extends BaseController {
|
||||
constructor(private categoryService: CategoryService) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增分类
|
||||
*/
|
||||
@Post()
|
||||
addCategory(@Body() createCategoryDto: CreateCategoryDto) {
|
||||
return this.categoryService.create(createCategoryDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分页/过滤参数查询分类
|
||||
*/
|
||||
@Get()
|
||||
@Respond(RespondType.PAGINATION)
|
||||
@ApiOkResponse({ isArray: true, type: Category })
|
||||
getCategorys(@Query() query: FindCategoryDto) {
|
||||
return this.categoryService.findMany(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询分类
|
||||
*/
|
||||
@Get(':id')
|
||||
getCategory(@Param('id', ParseIntPipe) id: number): Promise<Category> {
|
||||
return this.categoryService.findOne(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID更新分类
|
||||
*/
|
||||
@Patch(':id')
|
||||
updateCategory(@Param('id', ParseIntPipe) id: number, @Body() updateCategoryDto: UpdateCategoryDto) {
|
||||
return this.categoryService.update(+id, updateCategoryDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID删除分类
|
||||
*/
|
||||
@Delete(':id')
|
||||
delCategory(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.categoryService.remove(+id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Category } from './entities/category.entity';
|
||||
import { CategoryController } from './category.controller';
|
||||
import { CategoryService } from './category.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Category])],
|
||||
controllers: [CategoryController],
|
||||
providers: [CategoryService],
|
||||
exports: [CategoryService],
|
||||
})
|
||||
export class CategoryModule {}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { BaseService } from '@/common/base';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Like, Repository } from 'typeorm';
|
||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||
import { FindCategoryDto } from './dto/find-category.dto';
|
||||
import { UpdateCategoryDto } from './dto/update-category.dto';
|
||||
import { Category } from './entities/category.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CategoryService extends BaseService {
|
||||
constructor(@InjectRepository(Category) private categoryRepository: Repository<Category>) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增分类
|
||||
*/
|
||||
async create(createCategoryDto: CreateCategoryDto) {
|
||||
const category = this.categoryRepository.create(createCategoryDto);
|
||||
await this.categoryRepository.save(category);
|
||||
return category.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件/分页查询
|
||||
*/
|
||||
async findMany(findCategorydto: FindCategoryDto) {
|
||||
const { page, size } = findCategorydto;
|
||||
const { skip, take } = this.formatPagination(page, size, true);
|
||||
return this.categoryRepository.findAndCount({ skip, take });
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询
|
||||
*/
|
||||
findOne(idOrOptions: number | Partial<Category>) {
|
||||
const where = typeof idOrOptions === 'number' ? { id: idOrOptions } : (idOrOptions as any);
|
||||
return this.categoryRepository.findOne({ where });
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID更新
|
||||
*/
|
||||
update(id: number, updateCategoryDto: UpdateCategoryDto) {
|
||||
return this.categoryRepository.update(id, updateCategoryDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID删除(软删除)
|
||||
*/
|
||||
remove(id: number) {
|
||||
return this.categoryRepository.softDelete(id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateCategoryDto {
|
||||
/**
|
||||
* 分类名称
|
||||
* @example '待分类'
|
||||
*/
|
||||
@IsString()
|
||||
title: string;
|
||||
/**
|
||||
* 分类别名
|
||||
* @example 'default'
|
||||
*/
|
||||
@IsString()
|
||||
slug: string;
|
||||
/**
|
||||
* 分类描述
|
||||
* @example '默认分类'
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
/**
|
||||
* 分类图标
|
||||
* @example 'default'
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
icon?: string;
|
||||
/**
|
||||
* 分类排序
|
||||
* @example 0
|
||||
*/
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
sort?: number;
|
||||
/**
|
||||
* 分类类型
|
||||
* @example 'category'
|
||||
*/
|
||||
@IsEnum(['category', 'tag'])
|
||||
@IsOptional()
|
||||
type: 'category' | 'tag';
|
||||
/**
|
||||
* 父级分类ID
|
||||
* @example 0
|
||||
*/
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
parentId?: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { PaginationDto } from '@/common/response';
|
||||
import { IntersectionType } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class FindCategoryDto extends IntersectionType(PaginationDto) {
|
||||
/**
|
||||
* 字段描述(Swagger用途)
|
||||
* @example '示例值'
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
demo?: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateCategoryDto } from './create-category.dto';
|
||||
|
||||
export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { BaseEntity } from '@/database';
|
||||
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
|
||||
|
||||
@Entity({ orderBy: { id: 'DESC' } })
|
||||
export class Category extends BaseEntity {
|
||||
/**
|
||||
* 分类名称
|
||||
* @example '待分类'
|
||||
*/
|
||||
@Column()
|
||||
title: string;
|
||||
/**
|
||||
* 分类别名
|
||||
* @example 'default'
|
||||
*/
|
||||
@Column()
|
||||
slug: string;
|
||||
/**
|
||||
* 分类描述
|
||||
* @example '默认分类'
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
/**
|
||||
* 分类图标
|
||||
* @example 'default'
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
icon?: string;
|
||||
/**
|
||||
* 分类排序
|
||||
* @example 0
|
||||
*/
|
||||
@Column({ default: 0 })
|
||||
sort?: number;
|
||||
/**
|
||||
* 分类类型
|
||||
* @example 'category'
|
||||
*/
|
||||
@Column({ default: 'category' })
|
||||
type?: 'category' | 'tag';
|
||||
/**
|
||||
* 父级分类ID
|
||||
* @example 0
|
||||
*/
|
||||
@Column({ default: 0, nullable: true })
|
||||
parentId?: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export * from './entities/category.entity';
|
||||
export * from './category.controller';
|
||||
export * from './category.module';
|
||||
export * from './category.service';
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { CategoryModule } from './category';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
/**
|
||||
* 分类模块
|
||||
*/
|
||||
CategoryModule
|
||||
],
|
||||
})
|
||||
export class ContentModule {}
|
||||
|
|
@ -27,7 +27,7 @@ export class EntitySubscripber implements EntitySubscriberInterface {
|
|||
}
|
||||
|
||||
beforeSoftRemove(event: SoftRemoveEvent<any>): void | Promise<any> {
|
||||
event.entity.deletedBy = this.getUser();
|
||||
event.entity && (event.entity.deletedBy = this.getUser());
|
||||
}
|
||||
|
||||
getUser() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseEntity } from '@/database';
|
||||
import { Post } from '@/modules/post';
|
||||
import { Post } from '@/content/post';
|
||||
import { Role } from '@/modules/role';
|
||||
import { ApiHideProperty } from '@nestjs/swagger';
|
||||
import { Exclude } from 'class-transformer';
|
||||
|
|
|
|||
Loading…
Reference in New Issue