From a8d9167cb9ffccf1f3a097b71761e03f6956348b Mon Sep 17 00:00:00 2001 From: Arios67 Date: Tue, 29 Mar 2022 21:57:58 +0900 Subject: [PATCH] =?UTF-8?q?[#70]feat:=20=EB=A7=88=EA=B0=90=EB=90=9C=20?= =?UTF-8?q?=EC=9E=91=ED=92=88=20=EB=82=99=EC=B0=B0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ars/src/apis/art/art.resolver.ts | 11 -- ars/src/apis/art/art.service.ts | 21 +--- ars/src/apis/art/dto/createArtInput.ts | 4 +- ars/src/apis/art/entities/art.entity.ts | 9 +- .../apis/payment/entities/payment.entity.ts | 1 - ars/src/apis/payment/payment.module.ts | 17 ++- ars/src/apis/payment/payment.resolver.ts | 74 ++++++++++--- ars/src/apis/payment/payment.service.ts | 103 ++++++++++++++---- .../entities/pointTransaction.entity.ts | 2 +- .../pointTransaction.service.ts | 4 +- ars/src/app.module.ts | 1 - ars/src/common/graphql/schema.gql | 13 ++- ars/yarn.lock | 2 +- 13 files changed, 181 insertions(+), 81 deletions(-) diff --git a/ars/src/apis/art/art.resolver.ts b/ars/src/apis/art/art.resolver.ts index ac3e713..e7d2ceb 100644 --- a/ars/src/apis/art/art.resolver.ts +++ b/ars/src/apis/art/art.resolver.ts @@ -6,7 +6,6 @@ import { CurrentUser, ICurrentUser } from 'src/common/auth/gql-user.param'; import { ArtImage } from '../artImage/entities/artImage.entity'; import { FileService } from '../file/file.service'; import { LikeArtService } from '../likeArt/likeArt.service'; -import { Tag } from '../tag/entities/tag.entity'; import { ArtService } from './art.service'; import { CreateArtInput } from './dto/createArtInput'; import { Art } from './entities/art.entity'; @@ -83,14 +82,4 @@ export class ArtResolver { async fetchLikeArt(@CurrentUser() currentUser: ICurrentUser) { return await this.likeArtService.find(currentUser.id); } - - @UseGuards(GqlAuthAccessGuard) - @Mutation(() => [String]) - async Bid( - @Args('artId') artId: string, - @Args('bid_price') bid_price: number, - @CurrentUser() currentUser: ICurrentUser, - ) { - return await this.artService.call(artId, bid_price, currentUser.email); - } } diff --git a/ars/src/apis/art/art.service.ts b/ars/src/apis/art/art.service.ts index 9608ddc..2b68bdd 100644 --- a/ars/src/apis/art/art.service.ts +++ b/ars/src/apis/art/art.service.ts @@ -1,5 +1,4 @@ -import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'; -import { Cache } from 'cache-manager'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Connection, getRepository, MoreThan, Repository } from 'typeorm'; import { ArtImage } from '../artImage/entities/artImage.entity'; @@ -14,9 +13,6 @@ export class ArtService { @InjectRepository(ArtImage) private readonly artImageRepository: Repository, - @Inject(CACHE_MANAGER) - private readonly cacheManager: Cache, - private readonly connection: Connection, ) {} @@ -25,7 +21,6 @@ export class ArtService { await queryRunner.connect(); await queryRunner.startTransaction(); try { - const qb = getRepository(Art).createQueryBuilder('art'); const num = tags.length; let result = []; @@ -142,11 +137,6 @@ export class ArtService { }); } } - - this.cacheManager.set(result.id, [], { ttl: 10 }, async (ttl) => { - if (ttl === -2) console.log(result.id + ' 레디스 저장 만료'); - }); - await queryRunner.commitTransaction(); return result; } catch (error) { @@ -156,13 +146,4 @@ export class ArtService { await queryRunner.manager.release(); } } - - // 야매 입찰 - async call(artId, bid_price, email) { - await this.cacheManager.set(artId, [bid_price, email], { - ttl: 180, - }); - - return [bid_price, email]; - } } diff --git a/ars/src/apis/art/dto/createArtInput.ts b/ars/src/apis/art/dto/createArtInput.ts index 0a61bf5..8414e0c 100644 --- a/ars/src/apis/art/dto/createArtInput.ts +++ b/ars/src/apis/art/dto/createArtInput.ts @@ -17,8 +17,8 @@ export class CreateArtInput { @Field(() => Int) price: number; - @Field(() => String) - deadline: string; + @Field(() => Date) + deadline: Date; @Field(() => [String]) image_urls: string[]; diff --git a/ars/src/apis/art/entities/art.entity.ts b/ars/src/apis/art/entities/art.entity.ts index 9b9c134..6dd753d 100644 --- a/ars/src/apis/art/entities/art.entity.ts +++ b/ars/src/apis/art/entities/art.entity.ts @@ -4,6 +4,7 @@ import { User } from 'src/apis/user/entities/user.entity'; import { Column, CreateDateColumn, + DeleteDateColumn, Entity, ManyToOne, OneToOne, @@ -45,9 +46,13 @@ export class Art { @Field(() => Date) createdAt: string; + @DeleteDateColumn() + @Field(() => Date) + deletedAt: string; + @Column() - @Field(() => String) - deadline: string; + @Field(() => Date) + deadline: Date; @Column({ default: false }) @Field(() => Boolean) diff --git a/ars/src/apis/payment/entities/payment.entity.ts b/ars/src/apis/payment/entities/payment.entity.ts index c2e91e5..47648ee 100644 --- a/ars/src/apis/payment/entities/payment.entity.ts +++ b/ars/src/apis/payment/entities/payment.entity.ts @@ -1,6 +1,5 @@ import { Field, Int, ObjectType } from '@nestjs/graphql'; import { Art } from 'src/apis/art/entities/art.entity'; -import { PointTransaction } from 'src/apis/pointTransaction/entities/pointTransaction.entity'; import { User } from 'src/apis/user/entities/user.entity'; import { Column, diff --git a/ars/src/apis/payment/payment.module.ts b/ars/src/apis/payment/payment.module.ts index 59ac14d..2ca9eae 100644 --- a/ars/src/apis/payment/payment.module.ts +++ b/ars/src/apis/payment/payment.module.ts @@ -1,13 +1,28 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ArtService } from '../art/art.service'; +import { Art } from '../art/entities/art.entity'; +import { ArtImage } from '../artImage/entities/artImage.entity'; +import { User } from '../user/entities/user.entity'; +import { UserService } from '../user/user.service'; +import { Payment } from './entities/payment.entity'; import { PaymentResolver } from './payment.resolver'; import { PaymentServie } from './payment.service'; @Module({ - imports: [TypeOrmModule.forFeature()], + imports: [ + TypeOrmModule.forFeature([ + Art, // + ArtImage, + User, + Payment, + ]), + ], providers: [ PaymentResolver, // PaymentServie, + ArtService, + UserService, ], }) export class PaymentModule {} diff --git a/ars/src/apis/payment/payment.resolver.ts b/ars/src/apis/payment/payment.resolver.ts index d2ffcaf..54e76a5 100644 --- a/ars/src/apis/payment/payment.resolver.ts +++ b/ars/src/apis/payment/payment.resolver.ts @@ -1,27 +1,75 @@ -import { UseGuards } from '@nestjs/common'; -import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { CACHE_MANAGER, Inject, UseGuards } from '@nestjs/common'; +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { Cache } from 'cache-manager'; import { GqlAuthAccessGuard } from 'src/common/auth/gql-auth.guard'; import { CurrentUser, ICurrentUser } from 'src/common/auth/gql-user.param'; +import { UserService } from '../user/user.service'; import { Payment } from './entities/payment.entity'; import { PaymentServie } from './payment.service'; @Resolver() export class PaymentResolver { - constructor(private readonly paymentService: PaymentServie) {} + constructor( + private readonly paymentService: PaymentServie, + private readonly userService: UserService, + + @Inject(CACHE_MANAGER) + private readonly cacheManager: Cache, + ) {} + + @UseGuards(GqlAuthAccessGuard) + @Query(() => Payment) + async fetchPurchaseList(@CurrentUser() currentUser: ICurrentUser) { + return await this.paymentService.find(currentUser.id); + } + + @Mutation(() => String) + async checkTimedoutAndProcess() { + try { + const arts = await this.paymentService.checkTimeout(); + console.log(arts, '작품들'); + arts.map(async (e) => { + const bidInfo = await this.cacheManager.get(e.id); + const price = bidInfo[0]; + const bidder = await this.userService.findOne(bidInfo[1]); + if (price) { + await this.paymentService.successfulBid(e.id, price, bidder, e.user); + } else { + await this.paymentService.failedBid(e.id); + } + + return e.id; + }); + + return ''; + } catch (error) { + throw error + 'checkout'; + } + } + + @UseGuards(GqlAuthAccessGuard) + @Mutation(() => String) + async instantBid( + @Args('artId') artId: string, + @Args('price') price: number, + @Args('artistEmail') artistEmail: string, + @CurrentUser() currentUser: ICurrentUser, + ) { + await this.cacheManager.del(artId); + const artist = await this.userService.findOne(artistEmail); + const bidder = await this.userService.findOne(currentUser.email); + await this.paymentService.successfulBid(artId, price, bidder, artist); + + return 'artId'; + } @UseGuards(GqlAuthAccessGuard) - @Mutation(() => Payment) - async createPayment( + @Mutation(() => [String]) + async Bid( @Args('artId') artId: string, - @Args('amount') amount: number, + @Args('bid_price') bid_price: number, @CurrentUser() currentUser: ICurrentUser, ) { - return await this.paymentService.create( - { - amount, - artId, - }, - currentUser, - ); + return await this.paymentService.call(artId, bid_price, currentUser.email); } } diff --git a/ars/src/apis/payment/payment.service.ts b/ars/src/apis/payment/payment.service.ts index 201bb7a..0270b21 100644 --- a/ars/src/apis/payment/payment.service.ts +++ b/ars/src/apis/payment/payment.service.ts @@ -1,5 +1,7 @@ -import { Injectable } from '@nestjs/common'; -import { Connection } from 'typeorm'; +import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Cache } from 'cache-manager'; +import { Connection, LessThan, Repository } from 'typeorm'; import { Art } from '../art/entities/art.entity'; import { History } from '../history/entities/history.entity'; import { User } from '../user/entities/user.entity'; @@ -7,48 +9,107 @@ import { Payment } from './entities/payment.entity'; @Injectable() export class PaymentServie { - constructor(private readonly connection: Connection) {} - async create({ amount, artId }, currentUser) { + constructor( + @InjectRepository(Art) + private readonly artRepository: Repository, + + @InjectRepository(Payment) + private readonly paymentRepository: Repository, + + @Inject(CACHE_MANAGER) + private readonly cacheManager: Cache, + + private readonly connection: Connection, + ) {} + + // 마감된 작품 체크 + async checkTimeout() { + const time = new Date(); + const yyyy = time.getFullYear(); + const mm = time.getMonth() + 1; + const dd = time.getDate(); + const currentTime = `${yyyy}-${mm}-${dd}`; + + console.log(currentTime, ' 현 재 시 간 '); + return await this.artRepository.find({ + where: { + deadline: LessThan(currentTime), + }, + }); + } + + // 낙찰 + async successfulBid(artId, price, bidder, artist) { const queryRunner = this.connection.createQueryRunner(); await queryRunner.connect(); - await queryRunner.startTransaction('SERIALIZABLE'); + await queryRunner.startTransaction(); try { - // 현재 결제 요청 유저 정보 조회 - const user = await queryRunner.manager.findOne( - User, - { id: currentUser.id }, - { lock: { mode: 'pessimistic_write' } }, - ); - const art = await queryRunner.manager.findOne(Art, { id: artId }); + // 작품 정보 수정 및 softDelete + const soldoutArt = await queryRunner.manager.save(Art, { + ...art, + price: price, + is_soldout: true, + }); + await queryRunner.manager.softDelete(Art, { id: artId }); + // 낙찰 테이블 저장 const payment = await queryRunner.manager.save(Payment, { - amount: amount, - user: user, - art: art, + amount: price, + user: bidder, + art: soldoutArt, }); - // 히스토리 테이블 저장 + // 히스토리 테이블(낙찰자) 저장 await queryRunner.manager.save(History, { - point: amount, - user: user, + point: price, + user: bidder, payment: payment, }); // 유저 누적 포인트 업데이트 await queryRunner.manager.save(User, { - ...user, - point: user.point - amount, + ...bidder, + point: -price, + }); + + // 작가 누적 포인트 업데이트 + await queryRunner.manager.save(User, { + ...artist, + point: +price, }); await queryRunner.commitTransaction(); return payment; } catch (error) { await queryRunner.rollbackTransaction(); - throw error + 'create'; + throw error + 'successfulBid'; } finally { await queryRunner.release(); } } + + // 유찰 + async failedBid(artId) { + await this.artRepository.softDelete({ id: artId }); + } + + // 입찰 + async call(artId, bid_price, email) { + const art = await this.artRepository.findOne(artId); + const time = Number(art.deadline) - Number(new Date()); + console.log(time); + if (time > 0) { + await this.cacheManager.set(artId, [bid_price, email], { + ttl: time + 60000, + }); + } + return [bid_price, email]; + } + + // 구매한 작품 목록 조회 + async find(userId) { + return await this.paymentRepository.find({ user: userId }); + } } diff --git a/ars/src/apis/pointTransaction/entities/pointTransaction.entity.ts b/ars/src/apis/pointTransaction/entities/pointTransaction.entity.ts index 2aa0f43..cacb057 100644 --- a/ars/src/apis/pointTransaction/entities/pointTransaction.entity.ts +++ b/ars/src/apis/pointTransaction/entities/pointTransaction.entity.ts @@ -34,7 +34,7 @@ export class PointTransaction { @Column() @Field(() => Int) - charge_amount: number; + point: number; @Column({ type: 'enum', enum: POINTTRANSACTION_STATUS_ENUM }) @Field(() => POINTTRANSACTION_STATUS_ENUM) diff --git a/ars/src/apis/pointTransaction/pointTransaction.service.ts b/ars/src/apis/pointTransaction/pointTransaction.service.ts index a94df45..a29113b 100644 --- a/ars/src/apis/pointTransaction/pointTransaction.service.ts +++ b/ars/src/apis/pointTransaction/pointTransaction.service.ts @@ -71,7 +71,7 @@ export class PointTransactionServive { PointTransaction, { impUid: impUid, - point: charge_amount, + chargs_amount: charge_amount, user: user, status: POINTTRANSACTION_STATUS_ENUM.PAYMENT, }, @@ -146,7 +146,7 @@ export class PointTransactionServive { throw new UnprocessableEntityException('결제 기록이 존재하지 않습니다.'); const user = await this.userRepository.findOne({ id: currentUser.id }); - if (user.point < pointTransaction.charge_amount) + if (user.point < pointTransaction.point) throw new UnprocessableEntityException( '취소 가능한 포인트가 부족합니다.', ); diff --git a/ars/src/app.module.ts b/ars/src/app.module.ts index 65ba8a7..21ad61a 100644 --- a/ars/src/app.module.ts +++ b/ars/src/app.module.ts @@ -40,7 +40,6 @@ import { PaymentModule } from './apis/payment/payment.module'; TypeOrmModule.forRoot({ type: 'mysql', host: 'my_database', - //host: '10.115.0.112', //클러스터IP port: 3306, username: 'root', password: '3160', diff --git a/ars/src/common/graphql/schema.gql b/ars/src/common/graphql/schema.gql index c513461..fdc80fe 100644 --- a/ars/src/common/graphql/schema.gql +++ b/ars/src/common/graphql/schema.gql @@ -49,7 +49,8 @@ type Art { price: Int! thumbnail: String! createdAt: DateTime! - deadline: String! + deletedAt: DateTime! + deadline: DateTime! is_soldout: Boolean! user: User! payment: Payment! @@ -70,7 +71,7 @@ type PointTransaction { id: String! impUid: String! createdAt: DateTime! - charge_amount: Int! + point: Int! status: POINTTRANSACTION_ENUM! user: User! } @@ -129,6 +130,7 @@ type Query { fetchHistory(page: Float!): [History!]! fetchProfile: Profile! fetchUser: User! + fetchPurchaseList: Payment! fetchPointTransactions: [PointTransaction!]! } @@ -136,7 +138,6 @@ type Mutation { createArt(createArtInput: CreateArtInput!): Art! uploadArtImage(files: [Upload!]!): [String!]! addLikeArt(artId: String!): Boolean! - Bid(artId: String!, bid_price: Float!): [String!]! login(email: String!, password: String!): Token! restoreAccessToken: Token! logout: String! @@ -154,7 +155,9 @@ type Mutation { phoneAuth(phoneNum: String!, token: String!): Boolean! checkNickname(nickname: String!): Boolean! resetUserPassword(email: String!, password: String!): User! - createPayment(artId: String!, amount: Float!): Payment! + checkTimedoutAndProcess: String! + instantBid(artId: String!, price: Float!, artistEmail: String!): String! + Bid(artId: String!, bid_price: Float!): [String!]! createPointTransaction(impUid: String!, charge_amount: Float!): PointTransaction! cancelPointTransaction(impUid: String!): PointTransaction! } @@ -165,7 +168,7 @@ input CreateArtInput { start_price: Int! instant_bid: Int! price: Int! - deadline: String! + deadline: DateTime! image_urls: [String!]! is_soldout: Boolean! tags: [String!]! diff --git a/ars/yarn.lock b/ars/yarn.lock index e194d70..542825d 100644 --- a/ars/yarn.lock +++ b/ars/yarn.lock @@ -4962,7 +4962,7 @@ moment-timezone@^0.5.x: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@>=2.14.0: +"moment@>= 2.9.0", moment@>=2.14.0, moment@^2.29.1: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==