Skip to content

Commit

Permalink
Merge pull request #80 from Arios67/feat/#70
Browse files Browse the repository at this point in the history
[#70]feat: 마감된 작품 낙찰 API
  • Loading branch information
Arios67 committed Mar 29, 2022
2 parents 591eb96 + 74f5a58 commit 2ccf93a
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 81 deletions.
11 changes: 0 additions & 11 deletions ars/src/apis/art/art.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -89,14 +88,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);
}
}
21 changes: 1 addition & 20 deletions ars/src/apis/art/art.service.ts
Original file line number Diff line number Diff line change
@@ -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, Not, Repository } from 'typeorm';
import { ArtImage } from '../artImage/entities/artImage.entity';
Expand All @@ -14,9 +13,6 @@ export class ArtService {
@InjectRepository(ArtImage)
private readonly artImageRepository: Repository<ArtImage>,

@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache,

private readonly connection: Connection,
) {}

Expand All @@ -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 = [];

Expand Down Expand Up @@ -151,11 +146,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) {
Expand All @@ -165,13 +155,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];
}
}
4 changes: 2 additions & 2 deletions ars/src/apis/art/dto/createArtInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export class CreateArtInput {
@Field(() => Int)
price: number;

@Field(() => String)
deadline: string;
@Field(() => Date)
deadline: Date;

@Field(() => [String])
image_urls: string[];
Expand Down
8 changes: 6 additions & 2 deletions ars/src/apis/art/entities/art.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,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)
Expand Down
1 change: 0 additions & 1 deletion ars/src/apis/payment/entities/payment.entity.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
17 changes: 16 additions & 1 deletion ars/src/apis/payment/payment.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
74 changes: 61 additions & 13 deletions ars/src/apis/payment/payment.resolver.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
103 changes: 82 additions & 21 deletions ars/src/apis/payment/payment.service.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,115 @@
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';
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<Art>,

@InjectRepository(Payment)
private readonly paymentRepository: Repository<Payment>,

@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 });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion ars/src/apis/pointTransaction/pointTransaction.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,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(
'취소 가능한 포인트가 부족합니다.',
);
Expand Down
1 change: 0 additions & 1 deletion ars/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 2ccf93a

Please sign in to comment.