diff --git a/migrations/1695130227420-init-schema.ts b/migrations/1695130227420-init-schema.ts index 9bd6341..f77d2be 100644 --- a/migrations/1695130227420-init-schema.ts +++ b/migrations/1695130227420-init-schema.ts @@ -8,6 +8,7 @@ export class InitSchema1695130227420 implements MigrationInterface { "name" varchar NOT NULL, "password" varchar NOT NULL, email varchar NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), CONSTRAINT "users_pk" PRIMARY KEY (id), CONSTRAINT "users_email_unique" UNIQUE (email) ); @@ -18,7 +19,7 @@ export class InitSchema1695130227420 implements MigrationInterface { "createdAt" timestamp NOT NULL DEFAULT now(), "sourceUserId" int4 NULL, "targetUserId" int4 NULL, - CONSTRAINT "PK_bb38af5831e2c084a78e3622ff6" PRIMARY KEY (id) + CONSTRAINT "swipes_pk" PRIMARY KEY (id) ); ALTER TABLE swipes ADD CONSTRAINT "swipes_target_user_fk" FOREIGN KEY ("targetUserId") REFERENCES users(id); @@ -26,6 +27,7 @@ export class InitSchema1695130227420 implements MigrationInterface { CREATE TABLE pairs ( id serial4 NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), CONSTRAINT "pairs_pk" PRIMARY KEY (id) ); diff --git a/src/pairs/entities/pair-member.entity.ts b/src/pairs/entities/pair-member.entity.ts index 772c366..ac682a8 100644 --- a/src/pairs/entities/pair-member.entity.ts +++ b/src/pairs/entities/pair-member.entity.ts @@ -4,11 +4,17 @@ import { User } from 'src/users/entities/user.entity'; @Entity('pair-members') export class PairMember { - @PrimaryColumn('int', { name: 'pairId' }) + @PrimaryColumn({ type: 'int4', name: 'pairId' }) + pairId: number; + + @PrimaryColumn({ type: 'int', name: 'userId' }) + userId: number; + + // @PrimaryColumn('int', { name: 'pairId' }) @ManyToOne(() => Pair, (pair) => pair.pairMembers) pair: Pair; - @PrimaryColumn('int', { name: 'userId' }) + // @PrimaryColumn('int', { name: 'userId' }) @ManyToOne(() => User) user: User; } diff --git a/src/pairs/entities/pair.entity.ts b/src/pairs/entities/pair.entity.ts index 17fe199..b5f764b 100644 --- a/src/pairs/entities/pair.entity.ts +++ b/src/pairs/entities/pair.entity.ts @@ -1,4 +1,9 @@ -import { Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { + CreateDateColumn, + Entity, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; import { PairMember } from './pair-member.entity'; @Entity('pairs') @@ -8,4 +13,7 @@ export class Pair { @OneToMany(() => PairMember, (pairMember) => pairMember.pair) pairMembers: PairMember[]; + + @CreateDateColumn({ type: 'timestamp' }) + createdAt: Date; } diff --git a/src/pairs/pairs.service.ts b/src/pairs/pairs.service.ts index c3902a7..20cb3f1 100644 --- a/src/pairs/pairs.service.ts +++ b/src/pairs/pairs.service.ts @@ -30,17 +30,19 @@ export class PairsService { async (manager) => { const pair = this.pairsRepository.create(); await manager.save(pair); + const pairMembers = this.pairMembersRepository.create([ { - pair, - user: { id: userIds[0] }, + pairId: pair.id, + userId: userIds[0], }, { - pair, - user: { id: userIds[1] }, + pairId: pair.id, + userId: userIds[1], }, ]); await manager.save(pairMembers); + return pair; }, ); diff --git a/src/swipes/dto/create-swipe.dto.ts b/src/swipes/dto/create-swipe.dto.ts new file mode 100644 index 0000000..457b71d --- /dev/null +++ b/src/swipes/dto/create-swipe.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber } from 'class-validator'; + +export class CreateSwipeDto { + @ApiProperty() + @IsNumber() + userId: number; + + @ApiProperty() + @IsBoolean() + liked: boolean; +} diff --git a/src/swipes/dto/create-swipe.response.dto.ts b/src/swipes/dto/create-swipe.response.dto.ts new file mode 100644 index 0000000..693f150 --- /dev/null +++ b/src/swipes/dto/create-swipe.response.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { User } from 'src/users/entities/user.entity'; + +export class CreateSwipeResponseDto { + @ApiProperty() + id: number; + + @ApiProperty() + sourceUser: Pick; + + @ApiProperty() + targetUser: Pick; + + @ApiProperty() + liked: boolean; + + @ApiProperty() + createdAt: Date; +} diff --git a/src/swipes/swipes.controller.ts b/src/swipes/swipes.controller.ts new file mode 100644 index 0000000..71c6b03 --- /dev/null +++ b/src/swipes/swipes.controller.ts @@ -0,0 +1,38 @@ +import { + Body, + Controller, + HttpCode, + Post, + Req, + UseGuards, +} from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; +import { SwipesService } from './swipes.service'; +import { CreateSwipeDto } from './dto/create-swipe.dto'; +import JwtAuthGuard from 'src/auth/guards/jwt-auth.guard'; +import { RequestWithUser } from 'src/auth/interfaces/request-with-user.interface'; +import { CreateSwipeResponseDto } from './dto/create-swipe.response.dto'; + +@ApiTags('swipes') +@Controller('swipes') +export class SwipesController { + constructor(private readonly swipesService: SwipesService) {} + + @HttpCode(201) + @Post() + @UseGuards(JwtAuthGuard) + @ApiBody({ type: CreateSwipeDto }) + @ApiCreatedResponse({ type: CreateSwipeResponseDto }) + async create( + @Req() req: RequestWithUser, + @Body() createData: CreateSwipeDto, + ) { + const swipe = await this.swipesService.createSwipe( + req.user.id, + createData.userId, + createData.liked, + ); + + return swipe; + } +} diff --git a/src/swipes/swipes.module.ts b/src/swipes/swipes.module.ts index 0f373aa..604e64c 100644 --- a/src/swipes/swipes.module.ts +++ b/src/swipes/swipes.module.ts @@ -4,9 +4,11 @@ import { Swipe } from './entities/swipe.entity'; import { SwipesService } from './swipes.service'; import { UsersModule } from 'src/users/users.module'; import { PairsModule } from 'src/pairs/pairs.module'; +import { SwipesController } from './swipes.controller'; @Module({ imports: [TypeOrmModule.forFeature([Swipe]), UsersModule, PairsModule], providers: [SwipesService], + controllers: [SwipesController], }) export class SwipesModule {} diff --git a/src/swipes/swipes.service.ts b/src/swipes/swipes.service.ts index 8f8b85a..3b9f681 100644 --- a/src/swipes/swipes.service.ts +++ b/src/swipes/swipes.service.ts @@ -15,37 +15,53 @@ export class SwipesService { private readonly pairsService: PairsService, ) {} + get select() { + return { + id: true, + createdAt: true, + liked: true, + sourceUser: { id: true, email: true, name: true }, + targetUser: { id: true, email: true, name: true }, + }; + } + async createSwipe( sourceUserId: number, targetUserId: number, liked: boolean, ) { - const users = await this.usersService.getByIds([ - sourceUserId, - targetUserId, - ]); - if (users.length !== 2) { + const sourceUser = await this.usersService.getById(sourceUserId); + const targetUser = await this.usersService.getById(targetUserId); + if (!sourceUser || !targetUser) { throw new UserIdNotExistsException(); } + const existedSwipe = await this.getSwipe(sourceUserId, targetUserId, liked); + if (existedSwipe) { + return existedSwipe; + } + const swipe = this.swipesRepository.create({ sourceUser: { id: sourceUserId }, targetUser: { id: targetUserId }, liked, }); + await this.swipesRepository.save(swipe); if (liked) { - const answeringLikeSwipe = await this.swipesRepository.findBy({ - sourceUser: { id: targetUserId }, - targetUser: { id: sourceUserId }, - }); + const answeringLikeSwipe = await this.getSwipe( + targetUserId, + sourceUserId, + true, + ); + if (answeringLikeSwipe) { await this.pairsService.createPair([sourceUserId, targetUserId]); // TODO use transaction? } } - return swipe; + return this.getSwipeById(swipe.id); } async getSwipesByUser(sourceUserId: number) { @@ -53,6 +69,39 @@ export class SwipesService { if (!user) { throw new UserIdNotExistsException(); } - return this.swipesRepository.findBy({ sourceUser: { id: sourceUserId } }); + return this.swipesRepository.find({ + where: { sourceUser: { id: sourceUserId } }, + select: this.select, + relations: { + sourceUser: true, + targetUser: true, + }, + }); + } + + async getSwipe(sourceUserId: number, targetUserId: number, liked: boolean) { + return this.swipesRepository.findOne({ + where: { + sourceUser: { id: sourceUserId }, + targetUser: { id: targetUserId }, + liked, + }, + select: this.select, + relations: { + sourceUser: true, + targetUser: true, + }, + }); + } + + async getSwipeById(id: number) { + return this.swipesRepository.findOne({ + where: { id }, + select: this.select, + relations: { + sourceUser: true, + targetUser: true, + }, + }); } } diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index ce352a8..eefa1b5 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -1,5 +1,12 @@ +import { Exclude } from 'class-transformer'; import { Swipe } from 'src/swipes/entities/swipe.entity'; -import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { + Column, + CreateDateColumn, + Entity, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity('users') export class User { @@ -10,6 +17,7 @@ export class User { name: string; @Column() + @Exclude() password: string; @Column({ unique: true }) @@ -20,4 +28,7 @@ export class User { @OneToMany(() => Swipe, (swipe) => swipe.targetUser) swiped: Swipe[]; + + @CreateDateColumn({ type: 'timestamp' }) + createdAt: Date; } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index de1d78d..731ec2c 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -28,7 +28,10 @@ export class UsersService { } async getById(id: number) { - const user = await this.usersRepository.findOneBy({ id }); + const user = await this.usersRepository.findOne({ + where: { id }, + select: { id: true, email: true, name: true, swipes: true, swiped: true }, + }); if (!user) { throw new UserIdNotExistsException(); } @@ -41,6 +44,8 @@ export class UsersService { async create(userData: CreateUserDto) { const user = this.usersRepository.create(userData); + console.log('user', user); + await this.usersRepository.save(user); return user; }