diff --git a/README.md b/README.md index dd8c8f2..3fac579 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ - [x] user api - [x] signup - [x] login -- [] leaderboard api - - [] add 20 pts - - [] add 60 pts - - [] add 100 pts - - [] get standings - - [] get user score history +- [x] leaderboard api + - [x] add 20 pts + - [x] add 60 pts + - [x] add 100 pts + - [x] get standings + - [x] get user score history ## additional notes diff --git a/src/app.module.ts b/src/app.module.ts index 43651ca..a5b2cf6 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import { SequelizeModule } from '@nestjs/sequelize'; import { UserModule } from './user/user.module'; import { AuthModule } from './auth/auth.module'; import { ConfigModule } from '@nestjs/config'; +import { RankModule } from './rank/rank.module'; @Module({ imports: [ @@ -20,6 +21,7 @@ import { ConfigModule } from '@nestjs/config'; }), UserModule, AuthModule, + RankModule, ], }) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 990f845..144ac00 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -16,8 +16,8 @@ export class AuthController { @UseGuards(LocalAuthGuard) @Post('login') - async login(@Req() req: Request) { - return this.authService.authenticate(req.user); + async login(@Req() req) { + return this.authService.authenticate(req.user.dataValues); } @Post('register') diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts index 696f8eb..c83ad7e 100644 --- a/src/auth/jwt.strategy.ts +++ b/src/auth/jwt.strategy.ts @@ -5,11 +5,12 @@ import { ExtractJwt, Strategy } from "passport-jwt"; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private configService: ConfigService) { + constructor(configService: ConfigService) { + const secret = configService.get('JWT_SECRET'); super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: configService.get('JWT_SECRET'), + secretOrKey: secret, }); } diff --git a/src/rank/rank.controller.spec.ts b/src/rank/rank.controller.spec.ts new file mode 100644 index 0000000..59c2193 --- /dev/null +++ b/src/rank/rank.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RankController } from './rank.controller'; + +describe('RankController', () => { + let controller: RankController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [RankController], + }).compile(); + + controller = module.get(RankController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/rank/rank.controller.ts b/src/rank/rank.controller.ts new file mode 100644 index 0000000..1865bca --- /dev/null +++ b/src/rank/rank.controller.ts @@ -0,0 +1,45 @@ +import { Controller, Get, Param, Post, Req, UseGuards } from '@nestjs/common'; +import { Request } from 'express'; +import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; +import { Rank } from './rank.model'; +import { RankService } from './rank.service'; + +@Controller('rank') +export class RankController { + constructor(private rankService: RankService) {} + + @Get() + async getRankList(): Promise { + return this.rankService.findRankList(); + } + + @UseGuards(JwtAuthGuard) + @Get('/:id') + async getUserRank(@Param('id') userId: string): Promise { + return this.rankService.findByUserLatest(userId); + } + + @UseGuards(JwtAuthGuard) + @Get('/:id/all') + async getUserHistory(@Param('id') userId: string): Promise { + return this.rankService.findByUser(userId); + } + + @UseGuards(JwtAuthGuard) + @Post('/add/20') + async add20Points(@Req() req): Promise { + this.rankService.create(20, req.user.id); + } + + @UseGuards(JwtAuthGuard) + @Post('/add/60') + async add60Points(@Req() req): Promise { + this.rankService.create(60, req.user.id); + } + + @UseGuards(JwtAuthGuard) + @Post('/add/100') + async add100Points(@Req() req): Promise { + this.rankService.create(100, req.user.id); + } +} diff --git a/src/rank/rank.model.ts b/src/rank/rank.model.ts new file mode 100644 index 0000000..0ba21c9 --- /dev/null +++ b/src/rank/rank.model.ts @@ -0,0 +1,30 @@ +import { BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, NotEmpty, PrimaryKey, Table, UpdatedAt } from "sequelize-typescript"; +import { User } from "src/user/user.model"; + +@Table({ + timestamps: true, +}) +export class Rank extends Model { + @PrimaryKey + @Default(DataType.UUIDV4) + @Column(DataType.STRING) + id: string; + + @NotEmpty + @Column + points: number; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + + @NotEmpty + @ForeignKey(() => User) + @Column + userId: string; + + @BelongsTo(() => User) + user: User; +} diff --git a/src/rank/rank.module.ts b/src/rank/rank.module.ts new file mode 100644 index 0000000..a143793 --- /dev/null +++ b/src/rank/rank.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { RankService } from './rank.service'; +import { RankController } from './rank.controller'; +import { SequelizeModule } from '@nestjs/sequelize'; +import { Rank } from './rank.model'; + +@Module({ + imports: [ + SequelizeModule.forFeature([Rank]), + ], + providers: [RankService], + controllers: [RankController] +}) +export class RankModule {} diff --git a/src/rank/rank.service.spec.ts b/src/rank/rank.service.spec.ts new file mode 100644 index 0000000..ed2c656 --- /dev/null +++ b/src/rank/rank.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RankService } from './rank.service'; + +describe('RankService', () => { + let service: RankService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RankService], + }).compile(); + + service = module.get(RankService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/rank/rank.service.ts b/src/rank/rank.service.ts new file mode 100644 index 0000000..b981e11 --- /dev/null +++ b/src/rank/rank.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/sequelize'; +import { Op } from 'sequelize'; +import { col, fn } from 'sequelize'; +import { User } from 'src/user/user.model'; +import { Rank } from './rank.model'; + +@Injectable() +export class RankService { + constructor( + @InjectModel(Rank) private rankModel: typeof Rank + ) {} + + async create(points, userId): Promise { + return this.rankModel.create({ + points, + userId + }); + } + + async findAll(): Promise{ + return this.rankModel.findAll(); + } + + async findByUser(userId: string): Promise { + return this.rankModel.findAll({ + where: { + userId, + }, + }); + } + + async findByUserLatest(userId: string): Promise { + return this.rankModel.findOne({ + where: { + userId, + }, + order: ['createdAt', 'DESC'], + }); + } + + async findRankList(): Promise { + return this.rankModel.findAll({ + attributes: [ + 'id', + [fn('MAX', col('createdAt')), 'mDate'], + 'userId', + ], + group: ['userId'], + }).then((res) => { + const maxIds = []; + res.forEach(r => { + maxIds.push(r.id); + }); + return this.rankModel.findAll({ + attributes: ['id', 'points'], + where: { + id: { + [Op.in]: maxIds + }, + }, + include: [{ + model: User, + attributes: ['name'], + }], + }); + }); + } +} diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index e3fef51..9675c6c 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,6 +1,4 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; -import * as bcrypt from 'bcrypt'; -import { CreateUserDto } from './dtos/CreateUserDto'; +import { Controller, Get } from '@nestjs/common'; import { User } from './user.model'; import { UserService } from './user.service'; @@ -13,10 +11,4 @@ export class UserController { findAll(): Promise { return this.userService.findAll(); } - - @Post() - async create(user: CreateUserDto) { - user.password = await bcrypt.hash(user.password, 10); - return user; - } } diff --git a/src/user/user.model.ts b/src/user/user.model.ts index c1053c8..d6e220e 100644 --- a/src/user/user.model.ts +++ b/src/user/user.model.ts @@ -1,10 +1,10 @@ -import { Column, Model, Table, CreatedAt, UpdatedAt, PrimaryKey, DataType, NotEmpty, Default } from 'sequelize-typescript'; +import { Column, Model, Table, CreatedAt, UpdatedAt, PrimaryKey, DataType, NotEmpty, Default, HasMany } from 'sequelize-typescript'; +import { Rank } from 'src/rank/rank.model'; @Table({ timestamps: true, }) export class User extends Model { - @NotEmpty @PrimaryKey @Default(DataType.UUIDV4) @Column(DataType.STRING) @@ -29,4 +29,7 @@ export class User extends Model { @UpdatedAt @Column updatedAt: Date; + + @HasMany(() => Rank) + ranks: Rank[]; }