From 26d8ba4b3cf8f0780ab1745a54597bf335f1eaa1 Mon Sep 17 00:00:00 2001 From: Dani Date: Wed, 25 Oct 2023 13:34:56 +0700 Subject: [PATCH 1/9] add generic crud --- package.json | 3 + src/admin/admin.module.ts | 13 ++++ src/admin/admin.roles.ts | 19 +++++ src/admin/roles/dto/create-role.dto.ts | 14 ++++ src/admin/roles/dto/update-role.dto.ts | 4 ++ src/admin/roles/roles.controller.spec.ts | 20 ++++++ src/admin/roles/roles.controller.ts | 15 ++++ src/admin/roles/roles.module.ts | 14 ++++ src/admin/roles/roles.service.spec.ts | 18 +++++ src/admin/roles/roles.service.ts | 19 +++++ src/admin/users/dto/create-user.dto.ts | 21 ++++++ src/admin/users/dto/update-user.dto.ts | 6 ++ src/admin/users/users.controller.ts | 37 ++++++++++ src/admin/users/users.module.ts | 14 ++++ src/admin/users/users.service.ts | 46 ++++++++++++ src/apps/app.module.ts | 6 +- src/apps/user/user.controller.ts | 1 + src/common/decorators/validation.decorator.ts | 24 +++++++ src/entities/role.entity.ts | 1 - src/i18n/base-crud/base-crud.controller.ts | 33 +++++++++ src/i18n/base-crud/base-crud.service.ts | 35 +++++++++ src/i18n/base-crud/base-service.interface.ts | 7 ++ src/main.ts | 5 ++ yarn.lock | 71 ++++++++++++++++++- 24 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 src/admin/admin.module.ts create mode 100644 src/admin/admin.roles.ts create mode 100644 src/admin/roles/dto/create-role.dto.ts create mode 100644 src/admin/roles/dto/update-role.dto.ts create mode 100644 src/admin/roles/roles.controller.spec.ts create mode 100644 src/admin/roles/roles.controller.ts create mode 100644 src/admin/roles/roles.module.ts create mode 100644 src/admin/roles/roles.service.spec.ts create mode 100644 src/admin/roles/roles.service.ts create mode 100644 src/admin/users/dto/create-user.dto.ts create mode 100644 src/admin/users/dto/update-user.dto.ts create mode 100644 src/admin/users/users.controller.ts create mode 100644 src/admin/users/users.module.ts create mode 100644 src/admin/users/users.service.ts create mode 100644 src/common/decorators/validation.decorator.ts create mode 100644 src/i18n/base-crud/base-crud.controller.ts create mode 100644 src/i18n/base-crud/base-crud.service.ts create mode 100644 src/i18n/base-crud/base-service.interface.ts diff --git a/package.json b/package.json index 2d85aa3..cee3b94 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "@nestjs/platform-express": "^10.0.2", "@nestjs/swagger": "^7.1.13", "@nestjs/typeorm": "^10.0.0", + "@nestjsx/crud": "^5.0.0-alpha.3", + "@nestjsx/crud-typeorm": "^5.0.0-alpha.3", "@svtslv/nestjs-ioredis": "^1.0.2", "bcrypt": "^5.1.0", "cache-manager-redis-store": "^3.0.1", @@ -44,6 +46,7 @@ "ejs": "^3.1.8", "ioredis": "^5.3.2", "jsonwebtoken": "^9.0.1", + "nest-access-control": "^3.1.0", "nestjs-i18n": "^10.3.6", "passport": "^0.6.0", "passport-jwt": "^4.0.1", diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts new file mode 100644 index 0000000..005bafc --- /dev/null +++ b/src/admin/admin.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { SwaggerModule } from "@nestjs/swagger"; +import { UserModule } from "src/apps/user/user.module"; +import { UsersModule } from "./users/users.module"; +import { RolesModule } from './roles/roles.module'; + +@Module({ + imports: [ + UsersModule, + RolesModule, + ] +}) +export class AdminModule {} \ No newline at end of file diff --git a/src/admin/admin.roles.ts b/src/admin/admin.roles.ts new file mode 100644 index 0000000..62594da --- /dev/null +++ b/src/admin/admin.roles.ts @@ -0,0 +1,19 @@ +import { RolesBuilder } from 'nest-access-control'; + +export enum AppRoles { + ADMIN = 'ADMIN', + EDITOR = 'EDITOR', +} + +export const roles: RolesBuilder = new RolesBuilder(); + +roles + .grant(AppRoles.EDITOR) + .create('jobs') + .update('jobs') + // admin + .grant(AppRoles.ADMIN) + .extend(AppRoles.EDITOR) + .create(['companies']) + .update(['companies']) + .delete(['companies', 'jobs']); \ No newline at end of file diff --git a/src/admin/roles/dto/create-role.dto.ts b/src/admin/roles/dto/create-role.dto.ts new file mode 100644 index 0000000..0d6d172 --- /dev/null +++ b/src/admin/roles/dto/create-role.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from "@nestjsx/crud/lib/crud"; +import { Exclude } from "class-transformer"; + +export class CreateRoleDto { + + @ApiProperty({example: 'Admin'}) + name: string; + + @ApiProperty({example: 'ADMIN'}) + key: string; + + @ApiProperty() + description: string; +} diff --git a/src/admin/roles/dto/update-role.dto.ts b/src/admin/roles/dto/update-role.dto.ts new file mode 100644 index 0000000..450134d --- /dev/null +++ b/src/admin/roles/dto/update-role.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateRoleDto } from './create-role.dto'; + +export class UpdateRoleDto extends PartialType(CreateRoleDto) {} diff --git a/src/admin/roles/roles.controller.spec.ts b/src/admin/roles/roles.controller.spec.ts new file mode 100644 index 0000000..3dc6dac --- /dev/null +++ b/src/admin/roles/roles.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RolesController } from './roles.controller'; +import { RolesService } from './roles.service'; + +describe('RolesController', () => { + let controller: RolesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [RolesController], + providers: [RolesService], + }).compile(); + + controller = module.get(RolesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/admin/roles/roles.controller.ts b/src/admin/roles/roles.controller.ts new file mode 100644 index 0000000..3503d25 --- /dev/null +++ b/src/admin/roles/roles.controller.ts @@ -0,0 +1,15 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { RolesService } from './roles.service'; +import { CreateRoleDto } from './dto/create-role.dto'; +import { UpdateRoleDto } from './dto/update-role.dto'; +import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { BaseCrudController } from 'src/i18n/base-crud/base-crud.controller'; + +@Controller('admin/roles') +@ApiTags("Admin > Role") +@ApiSecurity('api-key') +export class RolesController extends BaseCrudController { + constructor(public readonly service: RolesService) { + super(service); + } +} diff --git a/src/admin/roles/roles.module.ts b/src/admin/roles/roles.module.ts new file mode 100644 index 0000000..6ae706f --- /dev/null +++ b/src/admin/roles/roles.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { RolesService } from './roles.service'; +import { RolesController } from './roles.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Role } from 'src/entities/role.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Role]) + ], + controllers: [RolesController], + providers: [RolesService], +}) +export class RolesModule {} diff --git a/src/admin/roles/roles.service.spec.ts b/src/admin/roles/roles.service.spec.ts new file mode 100644 index 0000000..058d35f --- /dev/null +++ b/src/admin/roles/roles.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RolesService } from './roles.service'; + +describe('RolesService', () => { + let service: RolesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RolesService], + }).compile(); + + service = module.get(RolesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/admin/roles/roles.service.ts b/src/admin/roles/roles.service.ts new file mode 100644 index 0000000..e4c1857 --- /dev/null +++ b/src/admin/roles/roles.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { CreateRoleDto } from './dto/create-role.dto'; +import { UpdateRoleDto } from './dto/update-role.dto'; +import { Role } from 'src/entities/role.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { IBaseService } from 'src/i18n/base-crud/base-service.interface'; +import { BaseCrudService } from 'src/i18n/base-crud/base-crud.service'; + +@Injectable() +// export class RolesService implements IBaseService { +export class RolesService extends BaseCrudService implements IBaseService { + constructor( + @InjectRepository(Role) + public roleRepository: Repository + ) { + super(roleRepository) + } +} diff --git a/src/admin/users/dto/create-user.dto.ts b/src/admin/users/dto/create-user.dto.ts new file mode 100644 index 0000000..856ddf2 --- /dev/null +++ b/src/admin/users/dto/create-user.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from "@nestjsx/crud/lib/crud"; +import { IsString, IsEmail, MinLength, Matches, IsNotEmpty } from 'class-validator'; +import { Match } from "src/common/decorators/validation.decorator"; + +export class CreateUserDto { + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsEmail() + email: string; + + @ApiProperty() + @MinLength(8) + password: string; + + @ApiProperty() + @Match('password', { message: 'Confirm password must match the password'}) + confirmPassword: string; +} diff --git a/src/admin/users/dto/update-user.dto.ts b/src/admin/users/dto/update-user.dto.ts new file mode 100644 index 0000000..0e35289 --- /dev/null +++ b/src/admin/users/dto/update-user.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateUserDto } from './create-user.dto'; + +export class UpdateUserDto extends PartialType(CreateUserDto) { + +} diff --git a/src/admin/users/users.controller.ts b/src/admin/users/users.controller.ts new file mode 100644 index 0000000..92e73d7 --- /dev/null +++ b/src/admin/users/users.controller.ts @@ -0,0 +1,37 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { ApiSecurity, ApiTags } from '@nestjs/swagger'; + +@Controller('admin/users') +@ApiSecurity('api-key') +@ApiTags("Admin > Users") +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Post() + create(@Body() createUserDto: CreateUserDto) { + return this.usersService.create(createUserDto); + } + + @Get() + findAll() { + return this.usersService.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.usersService.findOne(+id); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { + return this.usersService.update(+id, updateUserDto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.usersService.remove(+id); + } +} diff --git a/src/admin/users/users.module.ts b/src/admin/users/users.module.ts new file mode 100644 index 0000000..c0f0bc7 --- /dev/null +++ b/src/admin/users/users.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { UsersController } from './users.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from 'src/entities/user.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([User]), + ], + controllers: [UsersController], + providers: [UsersService], +}) +export class UsersModule {} diff --git a/src/admin/users/users.service.ts b/src/admin/users/users.service.ts new file mode 100644 index 0000000..21a489b --- /dev/null +++ b/src/admin/users/users.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { User } from 'src/entities/user.entity'; +import { Repository } from 'typeorm'; +import { hash } from 'bcrypt'; + +@Injectable() +export class UsersService { + + constructor( + @InjectRepository(User) + private userRepository: Repository + ) {} + + + get repo() { return this.userRepository; } + + async create(createUserDto: CreateUserDto) { + const { password, ...rest } = createUserDto; + const hashedPassword = await hash(password, 10); + const newUser = this.repo.create({ password: hashedPassword, ...rest }); + const createdUser = await this.repo.save(newUser); + return createdUser; + } + + findAll() { + + return this.repo.find(); + } + + findOne(id: number) { + return this.repo.findOne({where: {id}}); + } + + async update(id: number, updateUserDto: UpdateUserDto) { + + return await this.repo.update(id, updateUserDto); + } + + remove(id: number) { + // delete with typeorm + return this.repo.delete(id); + } +} diff --git a/src/apps/app.module.ts b/src/apps/app.module.ts index 3e278f6..758798d 100644 --- a/src/apps/app.module.ts +++ b/src/apps/app.module.ts @@ -10,6 +10,9 @@ import { FileModule } from './file/file.module'; import { I18nModule, QueryResolver, HeaderResolver, AcceptLanguageResolver } from 'nestjs-i18n' import { join } from 'path'; import { ConfigModule as AppConfigModule } from './config/config.module'; +import { UsersModule } from 'src/admin/users/users.module'; +import { AdminModule } from 'src/admin/admin.module'; +import { UserModule } from './user/user.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -38,7 +41,8 @@ import { ConfigModule as AppConfigModule } from './config/config.module'; }), AuthModule, FileModule, - AppConfigModule + AppConfigModule, + AdminModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/apps/user/user.controller.ts b/src/apps/user/user.controller.ts index 277d6bf..fc6e0e2 100644 --- a/src/apps/user/user.controller.ts +++ b/src/apps/user/user.controller.ts @@ -4,6 +4,7 @@ import { UseJwtGuard } from 'src/common/guards/jwt.guard'; import { Response } from 'src/common/utils'; import { UpdateUserDTO } from './update-user.dto'; import { UserService } from './user.service'; +import { ApiNotAcceptableResponse, ApiTags } from '@nestjs/swagger'; @Controller('user') @UseJwtGuard() diff --git a/src/common/decorators/validation.decorator.ts b/src/common/decorators/validation.decorator.ts new file mode 100644 index 0000000..b6d3883 --- /dev/null +++ b/src/common/decorators/validation.decorator.ts @@ -0,0 +1,24 @@ +import {registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator'; + +export function Match(property: string, validationOptions?: ValidationOptions) { + return (object: any, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [property], + validator: MatchConstraint, + }); + }; +} + +@ValidatorConstraint({name: 'Match'}) +export class MatchConstraint implements ValidatorConstraintInterface { + + validate(value: any, args: ValidationArguments) { + const [relatedPropertyName] = args.constraints; + const relatedValue = (args.object as any)[relatedPropertyName]; + return value === relatedValue; + } + +} \ No newline at end of file diff --git a/src/entities/role.entity.ts b/src/entities/role.entity.ts index 5e4111e..f5575b6 100644 --- a/src/entities/role.entity.ts +++ b/src/entities/role.entity.ts @@ -26,4 +26,3 @@ export class Role { @OneToMany(type => RoleToPermission, roleToPermission => roleToPermission.role) permissions: RoleToPermission[]; } - diff --git a/src/i18n/base-crud/base-crud.controller.ts b/src/i18n/base-crud/base-crud.controller.ts new file mode 100644 index 0000000..2f5fc53 --- /dev/null +++ b/src/i18n/base-crud/base-crud.controller.ts @@ -0,0 +1,33 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { IBaseService } from './base-service.interface'; + + +export abstract class BaseCrudController { + constructor(public readonly service: IBaseService) {} + + @Post() + create(@Body() createRoleDto: CreateDTO) { + return this.service.create(createRoleDto); + } + + @Get() + findAll() { + return this.service.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.service.findOne(+id); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() updateRoleDto: UpdateDTO) { + return this.service.update(+id, updateRoleDto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.service.remove(+id); + } +} diff --git a/src/i18n/base-crud/base-crud.service.ts b/src/i18n/base-crud/base-crud.service.ts new file mode 100644 index 0000000..86fea72 --- /dev/null +++ b/src/i18n/base-crud/base-crud.service.ts @@ -0,0 +1,35 @@ +import { Repository } from "typeorm"; +import { IBaseService } from "./base-service.interface"; +import { InjectRepository } from "@nestjs/typeorm"; + +export abstract class BaseCrudService implements IBaseService { + constructor( + public roleRepository: Repository + ) {} + + get repo() { return this.roleRepository; } + + create(createRoleDto: A|any) { + const newRole = this.repo.create(createRoleDto); + return this.repo.save(newRole); + } + + findAll() { + return this.repo.find(); + } + + findOne(id: number) { + return this.repo.findOne({where: { + id, + }}); + } + + update(id: number, updateRoleDto: B | any ) { + return this.repo.update(id, updateRoleDto); + } + + remove(id: number) { + return this.repo.delete(id); + } + } + \ No newline at end of file diff --git a/src/i18n/base-crud/base-service.interface.ts b/src/i18n/base-crud/base-service.interface.ts new file mode 100644 index 0000000..627cd22 --- /dev/null +++ b/src/i18n/base-crud/base-service.interface.ts @@ -0,0 +1,7 @@ +export interface IBaseService { + create(data: CreateDTO): any; + findAll(): any; + findOne(id: number): any; + update(id: number, data:UpdateDTO ): any; + remove(id: number): any; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 1a3b12d..06b0033 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import { ApiKeyGuard } from './common/guards/api-key.guard'; import { AppModule } from './apps/app.module'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { ValidationPipe } from '@nestjs/common'; const APP_PORT = 3000; @@ -19,6 +20,9 @@ async function bootstrap() { app.useGlobalGuards(new ApiKeyGuard()); app.useGlobalFilters(new HttpExceptionFilter()) + app.useGlobalPipes(new ValidationPipe()); + + app.setViewEngine('ejs'); @@ -35,6 +39,7 @@ async function bootstrap() { .addBearerAuth({name: "AuthorizationToken", type: "http"}) .addBearerAuth({name: "RefreshToken", type: "http"}) .addTag('Auth', "All about authentication") + .addTag('Admin > Users', "User CRUD") .build(); const document = SwaggerModule.createDocument(app, config); diff --git a/yarn.lock b/yarn.lock index 92c7cef..191336b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -951,6 +951,36 @@ dependencies: uuid "9.0.0" +"@nestjsx/crud-request@^5.0.0-alpha.3": + version "5.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/@nestjsx/crud-request/-/crud-request-5.0.0-alpha.3.tgz#ee62f5ff454daef4973990faf9d1568e2a4b9bc8" + integrity sha512-EFeGgh/sSHhD3px9aj4hyjvOqBepMcRFAi5lrYuSUDmnj/eQOa4dg4orehU8gKrSAmgKNe9r0dx0kel7Zgivzg== + dependencies: + "@nestjsx/util" "^5.0.0-alpha.3" + qs "^6.8.0" + +"@nestjsx/crud-typeorm@^5.0.0-alpha.3": + version "5.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/@nestjsx/crud-typeorm/-/crud-typeorm-5.0.0-alpha.3.tgz#069dedb5cc82fd4749082594b5f6952c160fcd51" + integrity sha512-9FN4iBELmhhSoVJj8457kaNGFKhm7IA7zJbLJRGuIb0+12WwCOhY0/bhMg1pWgZ1qUYtkYoELyMiMDroPPtARw== + dependencies: + "@zmotivat0r/o0" "^1.0.2" + +"@nestjsx/crud@^5.0.0-alpha.3": + version "5.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/@nestjsx/crud/-/crud-5.0.0-alpha.3.tgz#84d494ceb5b10135afe53c62ed2a0e8015522f8b" + integrity sha512-LPFtrrE4nGT1vvzM+IpA4CNazO67Tz8gc4lAfSjwujIeOHPGfj3vJKUlcU/ESRUW/t+bLOF3S1tY/N0GQ/SRyg== + dependencies: + "@nestjsx/crud-request" "^5.0.0-alpha.3" + "@nestjsx/util" "^5.0.0-alpha.3" + deepmerge "^3.2.0" + pluralize "^8.0.0" + +"@nestjsx/util@^5.0.0-alpha.3": + version "5.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/@nestjsx/util/-/util-5.0.0-alpha.3.tgz#d1182280e9254f0d335efa369da17fa25d260135" + integrity sha512-UTYIxtyx80M42gOZ0VIInSiOLn78P6k1BCziiN8gE3FglzivQ1RGvPpmsRwioYEWG27gOgnMwOQnXv8Zbq8dzQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -1575,6 +1605,11 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@zmotivat0r/o0@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@zmotivat0r/o0/-/o0-1.0.2.tgz#5eb069a631678736234d4c34b07c3a2bae8f8e4b" + integrity sha512-wQ/sHqJEtYb+QtgxtWlBpGASFZ2avpoRWbvpginEuy7howZHTZTy3zYMRMaBKok4bZwIGU5bjmzE59+m1IWqKQ== + abbrev@1, abbrev@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1600,6 +1635,13 @@ accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +accesscontrol@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/accesscontrol/-/accesscontrol-2.2.1.tgz#c942c48e330841c619682309a8c3aeec6bec66eb" + integrity sha512-52EvFk/J9EF+w4mYQoKnOTkEMj01R1U5n2fc1dai6x1xkgOks3DGkx01qQL2cKFxGmE4Tn1krAU3jJA9L1NMkg== + dependencies: + notation "^1.3.6" + acorn-import-assertions@^1.9.0: version "1.9.0" resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz" @@ -2581,6 +2623,11 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.3.0.tgz#d3c47fd6f3a93d517b14426b0628a17b0125f5f7" + integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA== + deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" @@ -5530,6 +5577,14 @@ neo-async@^2.6.0, neo-async@^2.6.2: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +nest-access-control@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/nest-access-control/-/nest-access-control-3.1.0.tgz#0e35b2503f205ea80caf55bd5d0c53dc1df04a1b" + integrity sha512-rg8OWIcvA2gGiSjnl141RDg2B+R7YgyYgCl3D67NcK7/1TXoTJCdqSyFysYXzkiHiXZOvANKYkvke//p6Yzl8g== + dependencies: + accesscontrol "^2.2.1" + tslib "^2.6.2" + nestjs-i18n@^10.3.6: version "10.3.6" resolved "https://registry.yarnpkg.com/nestjs-i18n/-/nestjs-i18n-10.3.6.tgz#c666d0ce3ff89ed49e9944afbfa8200529843a7c" @@ -5627,6 +5682,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +notation@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/notation/-/notation-1.3.6.tgz#bc87b88d1f1159e2931e7f9317a3020313790321" + integrity sha512-DIuJmrP/Gg1DcXKaApsqcjsJD6jEccqKSfmU3BUx/f1GHsMiTJh70cERwYc64tOmTRTARCeMwkqNNzjh3AHhiw== + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -6073,7 +6133,7 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pluralize@8.0.0: +pluralize@8.0.0, pluralize@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== @@ -6328,6 +6388,13 @@ qs@6.11.0, qs@^6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.8.0: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -7234,7 +7301,7 @@ tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.6.2: +tslib@2.6.2, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== From 2ebaa66b249ece6102c6f9a5310c894c3bb52a22 Mon Sep 17 00:00:00 2001 From: Dani Date: Wed, 25 Oct 2023 13:36:02 +0700 Subject: [PATCH 2/9] remove unused --- src/admin/roles/roles.controller.spec.ts | 20 -------------------- src/admin/roles/roles.service.spec.ts | 18 ------------------ src/admin/roles/roles.service.ts | 1 - 3 files changed, 39 deletions(-) delete mode 100644 src/admin/roles/roles.controller.spec.ts delete mode 100644 src/admin/roles/roles.service.spec.ts diff --git a/src/admin/roles/roles.controller.spec.ts b/src/admin/roles/roles.controller.spec.ts deleted file mode 100644 index 3dc6dac..0000000 --- a/src/admin/roles/roles.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { RolesController } from './roles.controller'; -import { RolesService } from './roles.service'; - -describe('RolesController', () => { - let controller: RolesController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [RolesController], - providers: [RolesService], - }).compile(); - - controller = module.get(RolesController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/admin/roles/roles.service.spec.ts b/src/admin/roles/roles.service.spec.ts deleted file mode 100644 index 058d35f..0000000 --- a/src/admin/roles/roles.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { RolesService } from './roles.service'; - -describe('RolesService', () => { - let service: RolesService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [RolesService], - }).compile(); - - service = module.get(RolesService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/admin/roles/roles.service.ts b/src/admin/roles/roles.service.ts index e4c1857..64a6340 100644 --- a/src/admin/roles/roles.service.ts +++ b/src/admin/roles/roles.service.ts @@ -8,7 +8,6 @@ import { IBaseService } from 'src/i18n/base-crud/base-service.interface'; import { BaseCrudService } from 'src/i18n/base-crud/base-crud.service'; @Injectable() -// export class RolesService implements IBaseService { export class RolesService extends BaseCrudService implements IBaseService { constructor( @InjectRepository(Role) From 89b6922fb37ec951f640fa6dd2c9ca08c67ab72d Mon Sep 17 00:00:00 2001 From: Dani Date: Wed, 25 Oct 2023 17:20:08 +0700 Subject: [PATCH 3/9] fix swagger property --- .rc | 3 ++ .../1697207588388-CreatePermissionsTable.ts | 4 +- nest-cli.json | 1 + package.json | 2 - src/admin/admin.module.ts | 2 + .../permissions/dto/create-permission.dto.ts | 13 +++++ .../permissions/dto/update-permission.dto.ts | 4 ++ .../permissions/permissions.controller.ts | 26 ++++++++++ src/admin/permissions/permissions.module.ts | 14 ++++++ src/admin/permissions/permissions.service.ts | 18 +++++++ src/admin/roles/dto/create-role.dto.ts | 6 +-- src/admin/roles/roles.controller.ts | 14 +++++- src/admin/roles/roles.service.ts | 6 +-- src/admin/users/dto/create-user.dto.ts | 3 +- src/entities/permission.entity.ts | 2 +- src/i18n/base-crud/base-crud.controller.ts | 33 ------------- src/i18n/base-crud/base-service.interface.ts | 7 --- src/main.ts | 2 + src/shared/base-crud/base-crud.controller.ts | 38 ++++++++++++++ .../base-crud/base-crud.service.ts | 2 +- .../base-crud/base-service.interface.ts | 7 +++ yarn.lock | 49 +------------------ 22 files changed, 154 insertions(+), 102 deletions(-) create mode 100644 .rc create mode 100644 src/admin/permissions/dto/create-permission.dto.ts create mode 100644 src/admin/permissions/dto/update-permission.dto.ts create mode 100644 src/admin/permissions/permissions.controller.ts create mode 100644 src/admin/permissions/permissions.module.ts create mode 100644 src/admin/permissions/permissions.service.ts delete mode 100644 src/i18n/base-crud/base-crud.controller.ts delete mode 100644 src/i18n/base-crud/base-service.interface.ts create mode 100644 src/shared/base-crud/base-crud.controller.ts rename src/{i18n => shared}/base-crud/base-crud.service.ts (98%) create mode 100644 src/shared/base-crud/base-service.interface.ts diff --git a/.rc b/.rc new file mode 100644 index 0000000..b1253d0 --- /dev/null +++ b/.rc @@ -0,0 +1,3 @@ +alias dyarn="docker compose exec app yarn" +alias dreset="docker compose restart app" +alias dlog="docker compose logs app -f" \ No newline at end of file diff --git a/migrations/1697207588388-CreatePermissionsTable.ts b/migrations/1697207588388-CreatePermissionsTable.ts index bf699c3..e6f75ae 100644 --- a/migrations/1697207588388-CreatePermissionsTable.ts +++ b/migrations/1697207588388-CreatePermissionsTable.ts @@ -9,7 +9,9 @@ export class CreatePermissionsTable1697207588388 implements MigrationInterface { { name: "id", type: "int8", isPrimary: true, isGenerated: true, generationStrategy: 'increment' }, { name: "name", type: "varchar", isNullable: false }, { name: "path", type: "varchar", isNullable: true }, - { name: "description", type: "varchar", isNullable: true,}, + { name: "action", type: "varchar", isNullable: true }, + { name: "created_at", type: "timestamp", isNullable: true }, + { name: "updated_at", type: "timestamp", isNullable: true }, ], indices: [ { columnNames: ['id'] }, diff --git a/nest-cli.json b/nest-cli.json index 60b0fc2..42df025 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -5,6 +5,7 @@ "compilerOptions": { "deleteOutDir": true, "watchAssets": true, + "plugins": ["@nestjs/swagger"], "assets": [ { "include": "service-account.json", diff --git a/package.json b/package.json index cee3b94..c543987 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,6 @@ "@nestjs/platform-express": "^10.0.2", "@nestjs/swagger": "^7.1.13", "@nestjs/typeorm": "^10.0.0", - "@nestjsx/crud": "^5.0.0-alpha.3", - "@nestjsx/crud-typeorm": "^5.0.0-alpha.3", "@svtslv/nestjs-ioredis": "^1.0.2", "bcrypt": "^5.1.0", "cache-manager-redis-store": "^3.0.1", diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index 005bafc..5941f77 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -3,11 +3,13 @@ import { SwaggerModule } from "@nestjs/swagger"; import { UserModule } from "src/apps/user/user.module"; import { UsersModule } from "./users/users.module"; import { RolesModule } from './roles/roles.module'; +import { PermissionsModule } from './permissions/permissions.module'; @Module({ imports: [ UsersModule, RolesModule, + PermissionsModule, ] }) export class AdminModule {} \ No newline at end of file diff --git a/src/admin/permissions/dto/create-permission.dto.ts b/src/admin/permissions/dto/create-permission.dto.ts new file mode 100644 index 0000000..fd52934 --- /dev/null +++ b/src/admin/permissions/dto/create-permission.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty } from "class-validator"; + +export class CreatePermissionDto { + @ApiProperty() + name: string; + + @ApiProperty() + path: string; + + @ApiProperty() + action: string; +} diff --git a/src/admin/permissions/dto/update-permission.dto.ts b/src/admin/permissions/dto/update-permission.dto.ts new file mode 100644 index 0000000..fbb7640 --- /dev/null +++ b/src/admin/permissions/dto/update-permission.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreatePermissionDto } from './create-permission.dto'; + +export class UpdatePermissionDto extends PartialType(CreatePermissionDto) {} diff --git a/src/admin/permissions/permissions.controller.ts b/src/admin/permissions/permissions.controller.ts new file mode 100644 index 0000000..462f1f0 --- /dev/null +++ b/src/admin/permissions/permissions.controller.ts @@ -0,0 +1,26 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { PermissionsService } from './permissions.service'; +import { CreatePermissionDto } from './dto/create-permission.dto'; +import { UpdatePermissionDto } from './dto/update-permission.dto'; +import { BaseCrudController } from 'src/shared/base-crud/base-crud.controller'; +import { ApiSecurity, ApiTags } from '@nestjs/swagger'; + +@Controller('admin/permissions') +@ApiTags("Admin > Permissions") +@ApiSecurity('api-key') +export class PermissionsController extends BaseCrudController { + + constructor(public readonly permissionsService: PermissionsService) { + super(permissionsService); + } + + @Post() + create(createDTO: CreatePermissionDto) { + return this.create(createDTO); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() updateDTO: UpdatePermissionDto) { + return this.service.update(+id, updateDTO); + } +} diff --git a/src/admin/permissions/permissions.module.ts b/src/admin/permissions/permissions.module.ts new file mode 100644 index 0000000..ba72a0b --- /dev/null +++ b/src/admin/permissions/permissions.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { PermissionsService } from './permissions.service'; +import { PermissionsController } from './permissions.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Permission } from 'src/entities/permission.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Permission]), + ], + controllers: [PermissionsController], + providers: [PermissionsService], +}) +export class PermissionsModule {} diff --git a/src/admin/permissions/permissions.service.ts b/src/admin/permissions/permissions.service.ts new file mode 100644 index 0000000..3146692 --- /dev/null +++ b/src/admin/permissions/permissions.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; +import { CreatePermissionDto } from './dto/create-permission.dto'; +import { UpdatePermissionDto } from './dto/update-permission.dto'; +import { BaseCrudService } from 'src/shared/base-crud/base-crud.service'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Permission } from 'src/entities/permission.entity'; +import { Repository } from 'typeorm'; +import { IBaseService } from 'src/shared/base-crud/base-service.interface'; + +@Injectable() +export class PermissionsService extends BaseCrudService { + constructor( + @InjectRepository(Permission) + repo: Repository + ) { + super(repo); + } +} diff --git a/src/admin/roles/dto/create-role.dto.ts b/src/admin/roles/dto/create-role.dto.ts index 0d6d172..37174da 100644 --- a/src/admin/roles/dto/create-role.dto.ts +++ b/src/admin/roles/dto/create-role.dto.ts @@ -1,9 +1,9 @@ -import { ApiProperty } from "@nestjsx/crud/lib/crud"; -import { Exclude } from "class-transformer"; +import { ApiProperty } from "@nestjs/swagger"; +import { IsString } from "class-validator"; export class CreateRoleDto { - @ApiProperty({example: 'Admin'}) + @IsString() name: string; @ApiProperty({example: 'ADMIN'}) diff --git a/src/admin/roles/roles.controller.ts b/src/admin/roles/roles.controller.ts index 3503d25..518cb53 100644 --- a/src/admin/roles/roles.controller.ts +++ b/src/admin/roles/roles.controller.ts @@ -3,13 +3,23 @@ import { RolesService } from './roles.service'; import { CreateRoleDto } from './dto/create-role.dto'; import { UpdateRoleDto } from './dto/update-role.dto'; import { ApiSecurity, ApiTags } from '@nestjs/swagger'; -import { BaseCrudController } from 'src/i18n/base-crud/base-crud.controller'; +import { BaseCrudController } from 'src/shared/base-crud/base-crud.controller'; @Controller('admin/roles') -@ApiTags("Admin > Role") +@ApiTags("Admin > Roles") @ApiSecurity('api-key') export class RolesController extends BaseCrudController { constructor(public readonly service: RolesService) { super(service); } + + @Post() + create(createDTO: CreateRoleDto) { + return this.create(createDTO); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() updateDTO: UpdateRoleDto) { + return this.service.update(+id, updateDTO); + } } diff --git a/src/admin/roles/roles.service.ts b/src/admin/roles/roles.service.ts index 64a6340..9ef9a11 100644 --- a/src/admin/roles/roles.service.ts +++ b/src/admin/roles/roles.service.ts @@ -4,11 +4,11 @@ import { UpdateRoleDto } from './dto/update-role.dto'; import { Role } from 'src/entities/role.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { IBaseService } from 'src/i18n/base-crud/base-service.interface'; -import { BaseCrudService } from 'src/i18n/base-crud/base-crud.service'; +import { IBaseService } from 'src/shared/base-crud/base-service.interface'; +import { BaseCrudService } from 'src/shared/base-crud/base-crud.service'; @Injectable() -export class RolesService extends BaseCrudService implements IBaseService { +export class RolesService extends BaseCrudService { constructor( @InjectRepository(Role) public roleRepository: Repository diff --git a/src/admin/users/dto/create-user.dto.ts b/src/admin/users/dto/create-user.dto.ts index 856ddf2..e76434f 100644 --- a/src/admin/users/dto/create-user.dto.ts +++ b/src/admin/users/dto/create-user.dto.ts @@ -1,4 +1,5 @@ -import { ApiProperty } from "@nestjsx/crud/lib/crud"; + +import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsEmail, MinLength, Matches, IsNotEmpty } from 'class-validator'; import { Match } from "src/common/decorators/validation.decorator"; diff --git a/src/entities/permission.entity.ts b/src/entities/permission.entity.ts index c99baa4..fa07172 100644 --- a/src/entities/permission.entity.ts +++ b/src/entities/permission.entity.ts @@ -14,7 +14,7 @@ export class Permission { path: string; @Column({ type: "varchar", nullable: true }) - description: string; + action: string; @OneToMany(type => RoleToPermission, roleToPermission => roleToPermission.permission) roles: RoleToPermission[]; diff --git a/src/i18n/base-crud/base-crud.controller.ts b/src/i18n/base-crud/base-crud.controller.ts deleted file mode 100644 index 2f5fc53..0000000 --- a/src/i18n/base-crud/base-crud.controller.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; -import { ApiSecurity, ApiTags } from '@nestjs/swagger'; -import { IBaseService } from './base-service.interface'; - - -export abstract class BaseCrudController { - constructor(public readonly service: IBaseService) {} - - @Post() - create(@Body() createRoleDto: CreateDTO) { - return this.service.create(createRoleDto); - } - - @Get() - findAll() { - return this.service.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.service.findOne(+id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() updateRoleDto: UpdateDTO) { - return this.service.update(+id, updateRoleDto); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.service.remove(+id); - } -} diff --git a/src/i18n/base-crud/base-service.interface.ts b/src/i18n/base-crud/base-service.interface.ts deleted file mode 100644 index 627cd22..0000000 --- a/src/i18n/base-crud/base-service.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface IBaseService { - create(data: CreateDTO): any; - findAll(): any; - findOne(id: number): any; - update(id: number, data:UpdateDTO ): any; - remove(id: number): any; -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 06b0033..3d61dec 100644 --- a/src/main.ts +++ b/src/main.ts @@ -40,6 +40,8 @@ async function bootstrap() { .addBearerAuth({name: "RefreshToken", type: "http"}) .addTag('Auth', "All about authentication") .addTag('Admin > Users', "User CRUD") + .addTag('Admin > Permissions', "Permissions CRUD") + .addTag('Admin > Roles', "Roles CRUD") .build(); const document = SwaggerModule.createDocument(app, config); diff --git a/src/shared/base-crud/base-crud.controller.ts b/src/shared/base-crud/base-crud.controller.ts new file mode 100644 index 0000000..a130e1e --- /dev/null +++ b/src/shared/base-crud/base-crud.controller.ts @@ -0,0 +1,38 @@ +import { Get, Post, Body, Patch, Param, Delete, Injectable } from '@nestjs/common'; +import { IBaseService } from './base-service.interface'; +import { ApiBody, ApiConsumes } from '@nestjs/swagger'; +import { CreateRoleDto } from 'src/admin/roles/dto/create-role.dto'; + +export abstract class BaseCrudController { + constructor(public readonly service: IBaseService) {} + + @Post() + create(@Body() createDTO: CreateDTO) { + return this.service.create(createDTO); + } + + @Get() + findAll() { + return this.service.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.service.findOne(+id); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() updateDTO: UpdateDTO) { + return this.service.update(+id, updateDTO); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.service.remove(+id); + } +} + + +class CreateDTO { + +} \ No newline at end of file diff --git a/src/i18n/base-crud/base-crud.service.ts b/src/shared/base-crud/base-crud.service.ts similarity index 98% rename from src/i18n/base-crud/base-crud.service.ts rename to src/shared/base-crud/base-crud.service.ts index 86fea72..a4b6e2a 100644 --- a/src/i18n/base-crud/base-crud.service.ts +++ b/src/shared/base-crud/base-crud.service.ts @@ -2,7 +2,7 @@ import { Repository } from "typeorm"; import { IBaseService } from "./base-service.interface"; import { InjectRepository } from "@nestjs/typeorm"; -export abstract class BaseCrudService implements IBaseService { +export abstract class BaseCrudService implements IBaseService { constructor( public roleRepository: Repository ) {} diff --git a/src/shared/base-crud/base-service.interface.ts b/src/shared/base-crud/base-service.interface.ts new file mode 100644 index 0000000..5605b40 --- /dev/null +++ b/src/shared/base-crud/base-service.interface.ts @@ -0,0 +1,7 @@ +export interface IBaseService { + create(data: any): any; + findAll(): any; + findOne(id: number): any; + update(id: number, data:any ): any; + remove(id: number): any; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 191336b..152abc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -951,36 +951,6 @@ dependencies: uuid "9.0.0" -"@nestjsx/crud-request@^5.0.0-alpha.3": - version "5.0.0-alpha.3" - resolved "https://registry.yarnpkg.com/@nestjsx/crud-request/-/crud-request-5.0.0-alpha.3.tgz#ee62f5ff454daef4973990faf9d1568e2a4b9bc8" - integrity sha512-EFeGgh/sSHhD3px9aj4hyjvOqBepMcRFAi5lrYuSUDmnj/eQOa4dg4orehU8gKrSAmgKNe9r0dx0kel7Zgivzg== - dependencies: - "@nestjsx/util" "^5.0.0-alpha.3" - qs "^6.8.0" - -"@nestjsx/crud-typeorm@^5.0.0-alpha.3": - version "5.0.0-alpha.3" - resolved "https://registry.yarnpkg.com/@nestjsx/crud-typeorm/-/crud-typeorm-5.0.0-alpha.3.tgz#069dedb5cc82fd4749082594b5f6952c160fcd51" - integrity sha512-9FN4iBELmhhSoVJj8457kaNGFKhm7IA7zJbLJRGuIb0+12WwCOhY0/bhMg1pWgZ1qUYtkYoELyMiMDroPPtARw== - dependencies: - "@zmotivat0r/o0" "^1.0.2" - -"@nestjsx/crud@^5.0.0-alpha.3": - version "5.0.0-alpha.3" - resolved "https://registry.yarnpkg.com/@nestjsx/crud/-/crud-5.0.0-alpha.3.tgz#84d494ceb5b10135afe53c62ed2a0e8015522f8b" - integrity sha512-LPFtrrE4nGT1vvzM+IpA4CNazO67Tz8gc4lAfSjwujIeOHPGfj3vJKUlcU/ESRUW/t+bLOF3S1tY/N0GQ/SRyg== - dependencies: - "@nestjsx/crud-request" "^5.0.0-alpha.3" - "@nestjsx/util" "^5.0.0-alpha.3" - deepmerge "^3.2.0" - pluralize "^8.0.0" - -"@nestjsx/util@^5.0.0-alpha.3": - version "5.0.0-alpha.3" - resolved "https://registry.yarnpkg.com/@nestjsx/util/-/util-5.0.0-alpha.3.tgz#d1182280e9254f0d335efa369da17fa25d260135" - integrity sha512-UTYIxtyx80M42gOZ0VIInSiOLn78P6k1BCziiN8gE3FglzivQ1RGvPpmsRwioYEWG27gOgnMwOQnXv8Zbq8dzQ== - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -1605,11 +1575,6 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -"@zmotivat0r/o0@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@zmotivat0r/o0/-/o0-1.0.2.tgz#5eb069a631678736234d4c34b07c3a2bae8f8e4b" - integrity sha512-wQ/sHqJEtYb+QtgxtWlBpGASFZ2avpoRWbvpginEuy7howZHTZTy3zYMRMaBKok4bZwIGU5bjmzE59+m1IWqKQ== - abbrev@1, abbrev@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -2623,11 +2588,6 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.3.0.tgz#d3c47fd6f3a93d517b14426b0628a17b0125f5f7" - integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA== - deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" @@ -6133,7 +6093,7 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pluralize@8.0.0, pluralize@^8.0.0: +pluralize@8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== @@ -6388,13 +6348,6 @@ qs@6.11.0, qs@^6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.8.0: - version "6.11.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" - integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== - dependencies: - side-channel "^1.0.4" - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" From cce47f65d17cb49975dcf954be0eb8a97a6633c2 Mon Sep 17 00:00:00 2001 From: Dani Date: Thu, 26 Oct 2023 15:47:07 +0700 Subject: [PATCH 4/9] update to docker network --- .env.example | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index f9556d6..6c43eaf 100644 --- a/.env.example +++ b/.env.example @@ -7,9 +7,9 @@ DB_HOST="pgsql" DB_PORT="5432" DB_USER="user_db_dev" DB_PASS="pass_db_dev" -DB_NAME="db-${PROJECT_NAME}" +DB_NAME="db-starter-kit" -MAIL_HOST=host.docker.internal +MAIL_HOST=smtp MAIL_PORT=1025 MAIL_USER= MAIL_PASS= @@ -28,7 +28,6 @@ GCS_BUCKET_NAME=app.appspot.com GOOGLE_APPLICATION_CREDENTIALS=/app/dist/service-account.json - # DOCKER ENV DB_EXPOSED_PORT=8610 APP_EXPOSED_PORT=8611 From 68b279784941d8fd820990e91f3c8e4518eca197 Mon Sep 17 00:00:00 2001 From: Dani Date: Thu, 2 Nov 2023 13:55:19 +0700 Subject: [PATCH 5/9] add misc get string permission for casbin --- .rc | 3 -- .../1697207588388-CreatePermissionsTable.ts | 2 +- src/admin/admin.module.ts | 2 ++ src/admin/misc/misc.controller.ts | 32 +++++++++++++++++++ src/admin/misc/misc.module.ts | 9 ++++++ src/admin/misc/misc.service.ts | 4 +++ .../permissions/dto/create-permission.dto.ts | 2 +- .../permissions/permissions.controller.ts | 16 +++++----- src/admin/roles/roles.controller.ts | 28 ++++++++++++---- src/admin/roles/roles.module.ts | 3 +- src/admin/roles/roles.service.ts | 25 ++++++++++++++- src/common/guards/api-key.guard.ts | 4 ++- src/entities/permission.entity.ts | 4 +-- 13 files changed, 110 insertions(+), 24 deletions(-) delete mode 100644 .rc create mode 100644 src/admin/misc/misc.controller.ts create mode 100644 src/admin/misc/misc.module.ts create mode 100644 src/admin/misc/misc.service.ts diff --git a/.rc b/.rc deleted file mode 100644 index b1253d0..0000000 --- a/.rc +++ /dev/null @@ -1,3 +0,0 @@ -alias dyarn="docker compose exec app yarn" -alias dreset="docker compose restart app" -alias dlog="docker compose logs app -f" \ No newline at end of file diff --git a/migrations/1697207588388-CreatePermissionsTable.ts b/migrations/1697207588388-CreatePermissionsTable.ts index e6f75ae..5f23263 100644 --- a/migrations/1697207588388-CreatePermissionsTable.ts +++ b/migrations/1697207588388-CreatePermissionsTable.ts @@ -9,7 +9,7 @@ export class CreatePermissionsTable1697207588388 implements MigrationInterface { { name: "id", type: "int8", isPrimary: true, isGenerated: true, generationStrategy: 'increment' }, { name: "name", type: "varchar", isNullable: false }, { name: "path", type: "varchar", isNullable: true }, - { name: "action", type: "varchar", isNullable: true }, + { name: "actions", type: "json", isNullable: true }, { name: "created_at", type: "timestamp", isNullable: true }, { name: "updated_at", type: "timestamp", isNullable: true }, ], diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index 5941f77..7d1ee37 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -4,12 +4,14 @@ import { UserModule } from "src/apps/user/user.module"; import { UsersModule } from "./users/users.module"; import { RolesModule } from './roles/roles.module'; import { PermissionsModule } from './permissions/permissions.module'; +import { MiscModule } from './misc/misc.module'; @Module({ imports: [ UsersModule, RolesModule, PermissionsModule, + MiscModule, ] }) export class AdminModule {} \ No newline at end of file diff --git a/src/admin/misc/misc.controller.ts b/src/admin/misc/misc.controller.ts new file mode 100644 index 0000000..867cdad --- /dev/null +++ b/src/admin/misc/misc.controller.ts @@ -0,0 +1,32 @@ +import { Controller, Get, Header } from '@nestjs/common'; + +@Controller('misc') +export class MiscController { + + @Get('/permissions') + @Header('content-type', 'text/plain') + getPermission() { + return ` +p, admin, posts, (list)|(create) +p, admin, posts/*, (edit)|(show)|(delete) +p, admin, posts/*, field + +p, admin, users, (list)|(create) +p, admin, users/*, (edit)|(show)|(delete) + +p, admin, roles, (list)|(create) +p, admin, roles/*, (edit)|(show)|(delete) + +p, admin, access_control + +p, admin, permissions, (list)|(create) +p, admin, permissions/*, (edit)|(show)|(delete) + +p, editor, posts, (list)|(create) +p, editor, posts/*, (edit)|(show) +p, editor, posts/hit, field, deny + +p, editor, categories, list + `; + } +} diff --git a/src/admin/misc/misc.module.ts b/src/admin/misc/misc.module.ts new file mode 100644 index 0000000..7fbd904 --- /dev/null +++ b/src/admin/misc/misc.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { MiscController } from './misc.controller'; +import { MiscService } from './misc.service'; + +@Module({ + controllers: [MiscController], + providers: [MiscService] +}) +export class MiscModule {} diff --git a/src/admin/misc/misc.service.ts b/src/admin/misc/misc.service.ts new file mode 100644 index 0000000..1f65b94 --- /dev/null +++ b/src/admin/misc/misc.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class MiscService {} diff --git a/src/admin/permissions/dto/create-permission.dto.ts b/src/admin/permissions/dto/create-permission.dto.ts index fd52934..4965068 100644 --- a/src/admin/permissions/dto/create-permission.dto.ts +++ b/src/admin/permissions/dto/create-permission.dto.ts @@ -9,5 +9,5 @@ export class CreatePermissionDto { path: string; @ApiProperty() - action: string; + actions: string[]; } diff --git a/src/admin/permissions/permissions.controller.ts b/src/admin/permissions/permissions.controller.ts index 462f1f0..e2efd6b 100644 --- a/src/admin/permissions/permissions.controller.ts +++ b/src/admin/permissions/permissions.controller.ts @@ -14,13 +14,13 @@ export class PermissionsController extends BaseCrudController { constructor( @InjectRepository(Role) - public roleRepository: Repository + public roleRepository: Repository, + + @InjectRepository(RoleToPermission) + public roleToPermission: Repository, ) { super(roleRepository) } + + + async assignPermission(roleId: number, permissionIds: string[]) { + await this.roleToPermission.delete({role_id: roleId}); + console.log(permissionIds); + await this.roleToPermission.createQueryBuilder().insert().into('role_to_permissions').values(permissionIds.map(id => ({ role_id: roleId, permission_id: id }))).execute(); + } + + async getPermission(roleId: number) { + return await this.roleToPermission.find({ + where: { + role_id: roleId + }, + relations: { + permission: true, + role: true + } + }); + } } diff --git a/src/common/guards/api-key.guard.ts b/src/common/guards/api-key.guard.ts index 48a49a5..04511ed 100644 --- a/src/common/guards/api-key.guard.ts +++ b/src/common/guards/api-key.guard.ts @@ -9,7 +9,8 @@ export class ApiKeyGuard implements CanActivate { private excluded = [ /^\/$/, - /^\/webhook(.*)$/ + /^\/webhook(.*)$/, + /^\/misc(.*)$/, ]; canActivate( @@ -25,6 +26,7 @@ export class ApiKeyGuard implements CanActivate { /* catch x-api-key from header and verify with the env */ const key = req.headers['x-api-key'] ?? req.query.api_key + console.log(key) if(key == undefined || key == '') { throw new HttpException('X-API-KEY is not provided.', HttpStatus.UNAUTHORIZED); } diff --git a/src/entities/permission.entity.ts b/src/entities/permission.entity.ts index fa07172..226fec8 100644 --- a/src/entities/permission.entity.ts +++ b/src/entities/permission.entity.ts @@ -13,8 +13,8 @@ export class Permission { @Column({ type: "varchar", nullable: true }) path: string; - @Column({ type: "varchar", nullable: true }) - action: string; + @Column({ type: "json", nullable: true }) + actions: string[]; @OneToMany(type => RoleToPermission, roleToPermission => roleToPermission.permission) roles: RoleToPermission[]; From 021c5feb6652b9ba6ef9740cc9fa41c1750c96ac Mon Sep 17 00:00:00 2001 From: Dani Date: Mon, 6 Nov 2023 13:37:18 +0700 Subject: [PATCH 6/9] build role adapter --- ...99245515021-AddColumnRoleIdToUsersTable.ts | 17 +++++++ src/admin/misc/misc.controller.ts | 48 ++++++++++++------- src/admin/misc/misc.module.ts | 5 ++ src/admin/misc/misc.service.ts | 44 ++++++++++++++++- src/entities/user.entity.ts | 10 +++- 5 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 migrations/1699245515021-AddColumnRoleIdToUsersTable.ts diff --git a/migrations/1699245515021-AddColumnRoleIdToUsersTable.ts b/migrations/1699245515021-AddColumnRoleIdToUsersTable.ts new file mode 100644 index 0000000..dd5b88f --- /dev/null +++ b/migrations/1699245515021-AddColumnRoleIdToUsersTable.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm" + +export class AddColumnRoleIdToUsersTable1699245515021 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn('users', new TableColumn({ + name: "role_id", + type: "int8", + default: 1, //admin + })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('users', 'role_id'); + } + +} diff --git a/src/admin/misc/misc.controller.ts b/src/admin/misc/misc.controller.ts index 867cdad..91e1cd0 100644 --- a/src/admin/misc/misc.controller.ts +++ b/src/admin/misc/misc.controller.ts @@ -1,32 +1,46 @@ -import { Controller, Get, Header } from '@nestjs/common'; +import { Controller, Get, Header, Req } from '@nestjs/common'; +import { Request } from 'express'; +import { UseJwtGuard } from 'src/common/guards/jwt.guard'; +import { MiscService } from './misc.service'; @Controller('misc') export class MiscController { + constructor( + private miscService: MiscService, + ) {} + @Get('/permissions') - @Header('content-type', 'text/plain') - getPermission() { + @UseJwtGuard() + // @Header('content-type', 'text/plain') + async getPermission( + @Req() req: Request + ) { + console.log(req.user); + + return await this.miscService.getUserPermission(req.user.id); + return ` -p, admin, posts, (list)|(create) -p, admin, posts/*, (edit)|(show)|(delete) -p, admin, posts/*, field + p, admin, posts, (list)|(create) + p, admin, posts/*, (edit)|(show)|(delete) + p, admin, posts/*, field -p, admin, users, (list)|(create) -p, admin, users/*, (edit)|(show)|(delete) + p, admin, users, (list)|(create) + p, admin, users/*, (edit)|(show)|(delete) -p, admin, roles, (list)|(create) -p, admin, roles/*, (edit)|(show)|(delete) + p, admin, roles, (list)|(create) + p, admin, roles/*, (edit)|(show)|(delete) -p, admin, access_control + p, admin, access_control -p, admin, permissions, (list)|(create) -p, admin, permissions/*, (edit)|(show)|(delete) + p, admin, permissions, (list)|(create) + p, admin, permissions/*, (edit)|(show)|(delete) -p, editor, posts, (list)|(create) -p, editor, posts/*, (edit)|(show) -p, editor, posts/hit, field, deny + p, editor, posts, (list)|(create) + p, editor, posts/*, (edit)|(show) + p, editor, posts/hit, field, deny -p, editor, categories, list + p, editor, categories, list `; } } diff --git a/src/admin/misc/misc.module.ts b/src/admin/misc/misc.module.ts index 7fbd904..2d1fd04 100644 --- a/src/admin/misc/misc.module.ts +++ b/src/admin/misc/misc.module.ts @@ -1,8 +1,13 @@ import { Module } from '@nestjs/common'; import { MiscController } from './misc.controller'; import { MiscService } from './misc.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from 'src/entities/user.entity'; @Module({ + imports: [ + TypeOrmModule.forFeature([User]), + ], controllers: [MiscController], providers: [MiscService] }) diff --git a/src/admin/misc/misc.service.ts b/src/admin/misc/misc.service.ts index 1f65b94..0246359 100644 --- a/src/admin/misc/misc.service.ts +++ b/src/admin/misc/misc.service.ts @@ -1,4 +1,46 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { RoleToPermission } from 'src/entities/role-to-permission.entity'; +import { User } from 'src/entities/user.entity'; +import { Repository } from 'typeorm'; @Injectable() -export class MiscService {} +export class MiscService { + + constructor( + @InjectRepository(User) + private userRepository: Repository, + ) {} + + + async getUserPermission(userId: number) { + const user = await this.userRepository.findOne({ + where: { + id: userId + }, + relations: { + role: { + permissions: { + permission: true + }, + } + } + }) + + const permissions = user.role.permissions; + let buildPermissionCasbin = ''; + permissions.forEach((item: RoleToPermission) => { + console.log(item.permission); + let actions = item.permission.actions.map((x: string) => `(${x})`).join('|'); + buildPermissionCasbin += `p, ${user.role.key}, ${item.permission.path}, ${actions}${"\n"}` + }) + + console.log(buildPermissionCasbin.trim()); + return { + role: user.role.key, + permissions, + casbin_permission_adapter: buildPermissionCasbin.trim() + } + + } +} diff --git a/src/entities/user.entity.ts b/src/entities/user.entity.ts index e0073b6..934fa09 100644 --- a/src/entities/user.entity.ts +++ b/src/entities/user.entity.ts @@ -1,6 +1,7 @@ import { strRandom } from 'src/common/utils'; -import { Entity, Column, PrimaryGeneratedColumn, BeforeInsert, UpdateDateColumn, CreateDateColumn } from 'typeorm'; +import { Entity, Column, PrimaryGeneratedColumn, BeforeInsert, UpdateDateColumn, CreateDateColumn, OneToOne, JoinColumn } from 'typeorm'; import { v4 } from 'uuid' +import { Role } from './role.entity'; @Entity({ name: 'users' }) @@ -26,6 +27,9 @@ export class User { @Column() name: string; + @Column() + role_id: string; + @Column() avatar: string; @@ -54,6 +58,10 @@ export class User { @CreateDateColumn() created_at: Date + @OneToOne(type => Role) + @JoinColumn({ name: "role_id" }) + role: Role; + @BeforeInsert() beforeInsert() { this.code = strRandom(6) From 0d6b30fa3642d391d6b98341aa3efabb53f2219d Mon Sep 17 00:00:00 2001 From: Dani Date: Mon, 6 Nov 2023 16:07:07 +0700 Subject: [PATCH 7/9] fix bug when build casbit adaptor --- src/admin/misc/misc.controller.ts | 34 ++++++++++++++++--------------- src/admin/misc/misc.service.ts | 6 +++++- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/admin/misc/misc.controller.ts b/src/admin/misc/misc.controller.ts index 91e1cd0..3eec11a 100644 --- a/src/admin/misc/misc.controller.ts +++ b/src/admin/misc/misc.controller.ts @@ -18,29 +18,31 @@ export class MiscController { ) { console.log(req.user); + // await this.miscService.getUserPermission(req.user.id); return await this.miscService.getUserPermission(req.user.id); - return ` - p, admin, posts, (list)|(create) - p, admin, posts/*, (edit)|(show)|(delete) - p, admin, posts/*, field + // return ` + // p, admin, posts, (list)|(create) + // p, admin, posts/*, (edit)|(show)|(delete) + // p, admin, posts/*, field - p, admin, users, (list)|(create) - p, admin, users/*, (edit)|(show)|(delete) + // p, admin, users, (list)|(create) + // p, admin, users/*, (edit)|(show)|(delete) - p, admin, roles, (list)|(create) - p, admin, roles/*, (edit)|(show)|(delete) + // p, admin, roles, (list)|(create) + // p, admin, roles/*, (edit)|(show)|(delete) - p, admin, access_control + // p, admin, access_control - p, admin, permissions, (list)|(create) - p, admin, permissions/*, (edit)|(show)|(delete) + // p, admin, permissions, (list)|(create) + // p, admin, permissions/*, (edit)|(show)|(delete) - p, editor, posts, (list)|(create) - p, editor, posts/*, (edit)|(show) - p, editor, posts/hit, field, deny + // p, editor, posts, (list)|(create) + // p, editor, posts/*, (edit)|(show) + // p, editor, posts/hit, field, deny - p, editor, categories, list - `; + // p, editor, categories, list + // `; } + } diff --git a/src/admin/misc/misc.service.ts b/src/admin/misc/misc.service.ts index 0246359..d958cc4 100644 --- a/src/admin/misc/misc.service.ts +++ b/src/admin/misc/misc.service.ts @@ -32,7 +32,11 @@ export class MiscService { permissions.forEach((item: RoleToPermission) => { console.log(item.permission); let actions = item.permission.actions.map((x: string) => `(${x})`).join('|'); - buildPermissionCasbin += `p, ${user.role.key}, ${item.permission.path}, ${actions}${"\n"}` + if(actions == '') { + buildPermissionCasbin += `p, ${user.role.key}, ${item.permission.path}${"\n"}` + } else { + buildPermissionCasbin += `p, ${user.role.key}, ${item.permission.path}, ${actions}${"\n"}` + } }) console.log(buildPermissionCasbin.trim()); From 169fbe54761f9e7065f47c277aab16e19544349e Mon Sep 17 00:00:00 2001 From: Dani Date: Mon, 6 Nov 2023 16:39:27 +0700 Subject: [PATCH 8/9] add inital data migration --- ...7208335927-CreateRoleToPermissionsTable.ts | 14 +++ ...99245515021-AddColumnRoleIdToUsersTable.ts | 15 +++- migrations/1699262051499-CreateInitialData.ts | 90 +++++++++++++++++++ 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 migrations/1699262051499-CreateInitialData.ts diff --git a/migrations/1697208335927-CreateRoleToPermissionsTable.ts b/migrations/1697208335927-CreateRoleToPermissionsTable.ts index 490bfb4..98f463a 100644 --- a/migrations/1697208335927-CreateRoleToPermissionsTable.ts +++ b/migrations/1697208335927-CreateRoleToPermissionsTable.ts @@ -15,6 +15,20 @@ export class CreateRoleToPermissionsTable1697208335927 implements MigrationInter indices: [ { columnNames: ['role_id'] }, { columnNames: ['role_id', 'permission_id'] }, + ], + foreignKeys: [ + { + columnNames: ['role_id'], + referencedColumnNames: ['id'], + referencedTableName: 'roles', + onDelete: 'CASCADE', + }, + { + columnNames: ['permission_id'], + referencedColumnNames: ['id'], + referencedTableName: 'permissions', + onDelete: 'CASCADE', + } ] })) } diff --git a/migrations/1699245515021-AddColumnRoleIdToUsersTable.ts b/migrations/1699245515021-AddColumnRoleIdToUsersTable.ts index dd5b88f..db6ac5e 100644 --- a/migrations/1699245515021-AddColumnRoleIdToUsersTable.ts +++ b/migrations/1699245515021-AddColumnRoleIdToUsersTable.ts @@ -1,4 +1,4 @@ -import { MigrationInterface, QueryRunner, TableColumn } from "typeorm" +import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey } from "typeorm" export class AddColumnRoleIdToUsersTable1699245515021 implements MigrationInterface { @@ -6,12 +6,23 @@ export class AddColumnRoleIdToUsersTable1699245515021 implements MigrationInterf await queryRunner.addColumn('users', new TableColumn({ name: "role_id", type: "int8", - default: 1, //admin + isNullable: true, + foreignKeyConstraintName: "fk_users_role_id", + })); + + await queryRunner.createForeignKey('users', new TableForeignKey({ + columnNames: ['role_id'], + referencedColumnNames: ['id'], + referencedTableName: 'roles', + onDelete: 'SET NULL', + name: "fk_users_role_id", })) } public async down(queryRunner: QueryRunner): Promise { await queryRunner.dropColumn('users', 'role_id'); + + await queryRunner.dropForeignKey('users', 'fk_users_role_id'); } } diff --git a/migrations/1699262051499-CreateInitialData.ts b/migrations/1699262051499-CreateInitialData.ts new file mode 100644 index 0000000..73d4f22 --- /dev/null +++ b/migrations/1699262051499-CreateInitialData.ts @@ -0,0 +1,90 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class CreateInitialData1699262051499 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + /* Create Permission */ + await queryRunner.connect(); + + const query = queryRunner.manager.createQueryBuilder(); + + await query.insert().into('roles', ['name', 'key', 'description']).values([ + { + name: 'Admin', + key: 'admin', + description: 'Super User', + } + ]).execute() + + await query.insert().into('permissions', ['name', 'path', 'actions']).values([ + { + name: "User", + path: "users", + actions: `["list", "create"]`, + }, + { + name: "User {*}", + path: "users/*", + actions: `["update", "show", "delete"]`, + }, + { + name: "access_control", + path: "access_control", + actions: "[]", + }, + { + name: "Roles", + path: "roles", + actions: `["list", "create"]`, + }, + { + name: "Roles {*}", + path: "roles/*", + actions: `["update", "show", "delete"]`, + }, + { + name: "Permissions", + path: "permissions", + actions: `["list", "create"]`, + }, + { + name: "Permissions", + path: "permissions/*", + actions: `["update", "show", "delete"]`, + }, + ]).execute() + + /* get admin role ID */ + const adminRole = await query + .select('role.id') + .from("roles", 'role') + .where({ key: 'admin' }) + .getRawOne(); + + /* get all permissions ids */ + const permissionIds = await query + .select('permission.id') + .from("permissions", 'permission') + .getRawMany(); + + + /* assign all permissions to admin */ + await query + .insert() + .into('role_to_permissions') + .values(permissionIds.map((permission) => ({ role_id: adminRole.id, permission_id: permission.id }))) + .execute(); + + /* update superadmin role */ + await query + .update('users') + .set({ role_id: adminRole.id }) + .where({ email: 'admin@mail.com' }) + .execute() + + } + + public async down(queryRunner: QueryRunner): Promise { + } + +} From c349d6a55bcded3b8216f5e92e5f1783a989a8d7 Mon Sep 17 00:00:00 2001 From: Dani Date: Mon, 6 Nov 2023 16:45:45 +0700 Subject: [PATCH 9/9] add `edit` action to persmissions --- migrations/1699262051499-CreateInitialData.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/migrations/1699262051499-CreateInitialData.ts b/migrations/1699262051499-CreateInitialData.ts index 73d4f22..38f2477 100644 --- a/migrations/1699262051499-CreateInitialData.ts +++ b/migrations/1699262051499-CreateInitialData.ts @@ -25,7 +25,7 @@ export class CreateInitialData1699262051499 implements MigrationInterface { { name: "User {*}", path: "users/*", - actions: `["update", "show", "delete"]`, + actions: `["update", "show", "delete", "edit"]`, }, { name: "access_control", @@ -40,7 +40,7 @@ export class CreateInitialData1699262051499 implements MigrationInterface { { name: "Roles {*}", path: "roles/*", - actions: `["update", "show", "delete"]`, + actions: `["update", "show", "delete", "edit"]`, }, { name: "Permissions", @@ -50,7 +50,7 @@ export class CreateInitialData1699262051499 implements MigrationInterface { { name: "Permissions", path: "permissions/*", - actions: `["update", "show", "delete"]`, + actions: `["update", "show", "delete", "edit"]`, }, ]).execute()