feat: 解决冲突
commit
4647b829c3
|
|
@ -5,9 +5,9 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".gitignore"
|
- .gitignore
|
||||||
- "README.md"
|
- .vscode/**
|
||||||
- ".vscode/**"
|
- README.md
|
||||||
|
|
||||||
env:
|
env:
|
||||||
docker_host: ${{ secrets.DOCKER_REGISTRY }}
|
docker_host: ${{ secrets.DOCKER_REGISTRY }}
|
||||||
|
|
@ -32,16 +32,16 @@ jobs:
|
||||||
|
|
||||||
- name: 构建镜像
|
- name: 构建镜像
|
||||||
run: |
|
run: |
|
||||||
docker build -t ${{ env.docker_name }}:latest .
|
docker build -t ${{ env.docker_name }}:latest .
|
||||||
|
|
||||||
- name: 登陆镜像
|
- name: 登陆镜像
|
||||||
run: |
|
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: 推送镜像
|
- name: 推送镜像
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
docker push ${{ env.docker_name }}:latest
|
docker push ${{ env.docker_name }}:latest
|
||||||
|
|
||||||
- name: 更新服务
|
- name: 更新服务
|
||||||
uses: http://git.dev.juetan.cn/mirror/ssh-action@v1.0.0
|
uses: http://git.dev.juetan.cn/mirror/ssh-action@v1.0.0
|
||||||
|
|
@ -52,4 +52,4 @@ jobs:
|
||||||
password: ${{ env.deploy_pass }}
|
password: ${{ env.deploy_pass }}
|
||||||
script: |
|
script: |
|
||||||
docker service ls | grep -q ${{ env.deploy_name }} || exit 0
|
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
|
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
|
WORKDIR /app
|
||||||
COPY package*.json .
|
COPY package*.json .
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
@ -16,4 +18,3 @@ COPY --from=dev /app/package.json ./
|
||||||
|
|
||||||
EXPOSE 3030
|
EXPOSE 3030
|
||||||
CMD [ "node", "./dist/main.js" ]
|
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/config": "^2.3.4",
|
||||||
"@nestjs/jwt": "^10.1.1",
|
"@nestjs/jwt": "^10.1.1",
|
||||||
"@nestjs/platform-express": "^9.4.3",
|
"@nestjs/platform-express": "^9.4.3",
|
||||||
|
"@nestjs/cache-manager": "^2.1.0",
|
||||||
"axios": "^1.5.0",
|
"axios": "^1.5.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
|
|
@ -45,10 +46,8 @@
|
||||||
"typeorm-naming-strategies": "^4.1.0",
|
"typeorm-naming-strategies": "^4.1.0",
|
||||||
"ua-parser-js": "^1.0.36",
|
"ua-parser-js": "^1.0.36",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"winston": "^3.10.0",
|
|
||||||
"nodemailer": "^6.9.5",
|
"nodemailer": "^6.9.5",
|
||||||
"mysql2": "^3.6.1",
|
"mysql2": "^3.6.1",
|
||||||
"@nestjs/cache-manager": "^2.1.0",
|
|
||||||
"cache-manager": "^5.2.3",
|
"cache-manager": "^5.2.3",
|
||||||
"cache-manager-redis-store": "^3.0.1",
|
"cache-manager-redis-store": "^3.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|
@ -57,6 +56,7 @@
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"redis": "^4.6.8",
|
"redis": "^4.6.8",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
|
"winston": "^3.10.0",
|
||||||
"winston-daily-rotate-file": "^4.7.1"
|
"winston-daily-rotate-file": "^4.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -107,4 +107,4 @@
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { PostModule } from '@/modules/post';
|
import { PostModule } from '@/content/post';
|
||||||
import { RoleModule } from '@/modules/role';
|
import { RoleModule } from '@/modules/role';
|
||||||
import { UploadModule } from '@/modules/upload';
|
import { UploadModule } from '@/storage/upload';
|
||||||
import { PermissionModule } from '@/modules/permission';
|
import { PermissionModule } from '@/modules/permission';
|
||||||
import { ConfigModule } from '@/config';
|
import { ConfigModule } from '@/config';
|
||||||
import { LoggerModule } from '@/common/logger';
|
import { LoggerModule } from '@/common/logger';
|
||||||
|
|
@ -12,8 +12,9 @@ import { AuthModule } from '@/modules/auth';
|
||||||
import { UserModule } from '@/modules/user';
|
import { UserModule } from '@/modules/user';
|
||||||
import { ResponseModule } from '@/common/response';
|
import { ResponseModule } from '@/common/response';
|
||||||
import { SerializationModule } from '@/common/serialization';
|
import { SerializationModule } from '@/common/serialization';
|
||||||
import { CacheModule } from './common/cache';
|
import { CacheModule } from '@/storage/cache';
|
||||||
import { ScanModule } from './utils/scan.module';
|
import { ScanModule } from '@/utils/scan.module';
|
||||||
|
import { ContentModule } from '@/content/content.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -61,6 +62,8 @@ import { ScanModule } from './utils/scan.module';
|
||||||
* @description 用于连接数据库
|
* @description 用于连接数据库
|
||||||
*/
|
*/
|
||||||
DatabaseModule,
|
DatabaseModule,
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户模块
|
* 用户模块
|
||||||
*/
|
*/
|
||||||
|
|
@ -77,6 +80,8 @@ import { ScanModule } from './utils/scan.module';
|
||||||
* 权限模块
|
* 权限模块
|
||||||
*/
|
*/
|
||||||
PermissionModule,
|
PermissionModule,
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传模块
|
* 上传模块
|
||||||
*/
|
*/
|
||||||
|
|
@ -85,6 +90,7 @@ import { ScanModule } from './utils/scan.module';
|
||||||
* 文章模块
|
* 文章模块
|
||||||
*/
|
*/
|
||||||
PostModule,
|
PostModule,
|
||||||
|
ContentModule
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export class AllExecptionFilter implements ExceptionFilter {
|
||||||
return response.status(exception.status).json(
|
return response.status(exception.status).json(
|
||||||
Response.create({
|
Response.create({
|
||||||
code: ResponseCode.ERROR,
|
code: ResponseCode.ERROR,
|
||||||
message: '访问的路径不存在',
|
message: '路径不存在',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ export class AllExecptionFilter implements ExceptionFilter {
|
||||||
return response.status(HttpStatus.UNAUTHORIZED).json(
|
return response.status(HttpStatus.UNAUTHORIZED).json(
|
||||||
Response.create({
|
Response.create({
|
||||||
code: ResponseCode.TOKEN_EXPIRED,
|
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> {
|
beforeSoftRemove(event: SoftRemoveEvent<any>): void | Promise<any> {
|
||||||
event.entity.deletedBy = this.getUser();
|
event.entity && (event.entity.deletedBy = this.getUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser() {
|
getUser() {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { BaseEntity } from '@/database';
|
import { BaseEntity } from '@/database';
|
||||||
import { Post } from '@/modules/post';
|
import { Post } from '@/content/post';
|
||||||
import { Role } from '@/modules/role';
|
import { Role } from '@/modules/role';
|
||||||
import { ApiHideProperty } from '@nestjs/swagger';
|
import { ApiHideProperty } from '@nestjs/swagger';
|
||||||
import { Exclude } from 'class-transformer';
|
import { Exclude } from 'class-transformer';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue