Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bid model + Create Bid + Active landing page bid response #99

Merged
merged 17 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
840b689
feat-wip(bid): add bid model, get auction bids endpoint and service
vikinatora Oct 18, 2021
5efe018
feat(place-bid): add bid model, temp bidding functionality and upgrad…
vikinatora Oct 19, 2021
bbaeeb5
fix(bid-entity): combine the two migration files into a single on
vikinatora Oct 20, 2021
291e813
fix(auction-page): more active auctions returns only active, removed …
vikinatora Oct 20, 2021
1fedf7c
improve(bids): add get user bids endpoint, change auction page link, …
vikinatora Oct 22, 2021
8648706
fix(auction-page): reversed start and end date queries
vikinatora Oct 22, 2021
6869227
improve(my-bids): optimize queries and make code more readable and or…
vikinatora Oct 22, 2021
02710a6
Merge branch 'dev' into auctions
vikinatora Oct 22, 2021
5603fd7
refactor: remove unnecessary bidder grouping function
vikinatora Oct 22, 2021
502ac55
Merge branch 'auctions' of https://github.com/UniverseXYZ/UniverseApp…
vikinatora Oct 22, 2021
98fb06a
fix(my-bids): fix min and max bids query
vikinatora Oct 22, 2021
2ec0fdc
fix(my-bids): add auction creator info to auction response
vikinatora Oct 22, 2021
c92b930
optimize(my-bids): merge bid table queries together
vikinatora Oct 22, 2021
f238390
improve(my-bids): reduce .find() calls
vikinatora Oct 22, 2021
bee7ae5
improve(my-active-auctions): add bids response
vikinatora Oct 25, 2021
d4a843b
improve(past-auctions): add bids info to past auctions endpoint
vikinatora Oct 25, 2021
accdd0f
fix(active-auction-page): return all bids for the auction
vikinatora Oct 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/modules/auction/domain/auction.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,21 @@ export class Auction {
backgroundImageBlur: boolean;

@Column({ default: false })
@Exclude()
onChain: boolean;

@Column({ default: true })
@Exclude()
initialised: boolean;

@Column({ default: false })
@Exclude()
depositedNfts: boolean;

@Column({ default: false })
@Exclude()
canceled: boolean;

@Column({ default: false })
@Exclude()
finalised: boolean;

@Column({ nullable: true })
@Exclude()
onChainId: number;

@Column({ nullable: true })
Expand Down
8 changes: 4 additions & 4 deletions src/modules/auction/entrypoints/auction.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,16 +285,16 @@ export class AuctionController {
return await this.auctionService.listAuctionsByStatus(status, page, limit);
}

@Post('auctions/bid')
@Post('auction/placeBid')
@UseGuards(JwtAuthGuard)
async placeAuctionBid(@Req() req, @Body() placeBidBody: PlaceBidBody) {
return await this.auctionService.placeAuctionBid(req.user.sub, placeBidBody);
}

@Get('auctions/bids')
@Get('pages/my-bids')
@UseGuards(JwtAuthGuard)
async getAuctionBids(@Req() req, @Query('auctionId') auctionId: number) {
return await this.auctionService.getAuctionBids(auctionId);
async getUserBids(@Req() req) {
return await this.auctionService.getUserBids(req.user.sub);
}

//Todo: add tier info
Expand Down
7 changes: 0 additions & 7 deletions src/modules/auction/entrypoints/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,11 +675,4 @@ export class PlaceBidBody {
})
@IsNumber()
amount: number;

// @ApiProperty({
// example: '0.1',
// description: 'The crypto currency the user is bidding',
// })
// @IsString()
// currency: string;
}
150 changes: 133 additions & 17 deletions src/modules/auction/service-layer/auction.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ export class AuctionService {

async getAuctionPage(username: string, auctionName: string) {
const artist = await this.usersService.getByUsername(username);
const link = `universe.xyz/${username}/${auctionName}`;

//TODO: add a check if the auction has started
const auction = await this.auctionRepository.findOne({ link: link });
const auction = await this.auctionRepository.findOne({ link: auctionName });

if (!auction) {
throw new AuctionNotFoundException();
Expand Down Expand Up @@ -96,19 +95,42 @@ export class AuctionService {
.orderBy('bid.amount', 'DESC')
.getMany();

const bidders = this.groupBidsByUser(bids);

bidders.sort((a, b) => b.amount - a.amount).slice(0, 5);

return {
auction: auction,
auction: classToPlain(auction),
artist: classToPlain(artist),
collections: classToPlain(collections),
rewardTiers: rewardTiers.map((rewardTier) => ({
...classToPlain(rewardTier),
nfts: rewardTierNftsMap[rewardTier.id].map((nft) => classToPlain(nft)),
})),
moreActiveAuctions: moreActiveAuctions.map((a) => classToPlain(a)),
bids: bids,
bidders: bidders,
};
}

private groupBidsByUser(bids: AuctionBid[]) {
const bidders = [];
bids.forEach((bid) => {
const existingBidder = bidders.find((bidder) => bidder.userId === bid.userId);
if (!existingBidder) {
const newBidder = {
...bid,
amount: +bid.amount,
};

bidders.push(newBidder);
} else {
existingBidder.amount = +existingBidder.amount + +bid.amount;
}
});

return bidders;
}

async createRewardTier(
userId: number,
auctionId: number,
Expand Down Expand Up @@ -747,31 +769,125 @@ export class AuctionService {
};
}

public async getAuctionBids(auctionId: number) {
//TODO: Add results limit to get only (reward tiers * reward tier slots)
const bids = await this.auctionBidRepository.find({ where: { auctionId: auctionId }, order: { amount: 'DESC' } });
public async getUserBids(userId: number) {
//TODO: Add Pagination as this request can get quite computation heavy

// User should have only one bid per auction -> if user places multiple bids their amount should be accumulated into a single bid (That's how smart contract works)
const bids = await this.auctionBidRepository.find({ where: { userId: userId }, order: { createdAt: 'DESC' } });
const auctionIds = bids.map((bid) => bid.auctionId);

const [auctions, allAuctionBids, rewardTiers] = await Promise.all([
this.auctionRepository.find({ where: { id: In(auctionIds) } }),
this.auctionBidRepository.find({ where: { auctionId: In(auctionIds) } }),
this.rewardTierRepository.find({ where: { auctionId: In(auctionIds) } }),
]);

const auctionsById = auctions.reduce((acc, auction) => {
acc[auction.id] = auction;
return acc;
}, {});

const allAuctionBidders = this.groupBidsByUser(allAuctionBids);

const bidsByAuctionId = allAuctionBidders.reduce((acc, bid) => {
const group = acc[bid.auctionId] || [];
group.push(bid.amount);
acc[bid.auctionId] = group;
return acc;
}, {});

const rewardTierIds = rewardTiers.map((nft) => nft.id);
const rewardTiersNfts = await this.rewardTierNftRepository.find({ where: { rewardTierId: In(rewardTierIds) } });

const rewardTierNftsByRewardTierId = rewardTiersNfts.reduce((acc, rewardTierNft) => {
const group = acc[rewardTierNft.rewardTierId] || [];
group.push(rewardTierNft);
acc[rewardTierNft.rewardTierId] = group;
return acc;
}, {});

const rewardTiersByAuctionId = rewardTiers.reduce((acc, tier) => {
const group = acc[tier.auctionId] || [];
group.push(tier);
acc[tier.auctionId] = group;
return acc;
}, {});

const mappedBids = bids.map((bid) => {
const auctionBids = bidsByAuctionId[bid.auctionId];
const highestBid = Math.max(...auctionBids);
const lowestBid = Math.min(...auctionBids);

const tiers = rewardTiersByAuctionId[bid.auctionId];

// If auction has 5 winning slots but received only one bid -> numberOfWinners should be 1)
let numberOfWinners = tiers.reduce((acc, tier) => (acc += tier.numberOfWinners), 0);
numberOfWinners = Math.min(numberOfWinners, auctionBids.length);

const nftsBySlot = rewardTiersNfts
.filter((tierNft) =>
Object.keys(rewardTierNftsByRewardTierId)
.map((key) => +key)
.includes(tierNft.rewardTierId),
)
.reduce((acc, item) => {
const group = acc[item.slot] || [];
group.push(item);
acc[item.slot] = group;
return acc;
}, {});

let maxNfts = Number.MIN_SAFE_INTEGER;
let minNfts = Number.MAX_SAFE_INTEGER;

Object.keys(nftsBySlot).forEach((slot) => {
if (nftsBySlot[slot].length > maxNfts) {
maxNfts = nftsBySlot[slot].length;
}
if (nftsBySlot[slot].length < minNfts) {
minNfts = nftsBySlot[slot].length;
}
});

return {
bids: bids.map((bid) => classToPlain(bid)),
};
return {
bid: classToPlain(bid),
auction: classToPlain(auctionsById[bid.auctionId]),
highestBid: highestBid,
lowestBid: lowestBid,
numberOfWinners,
maxNfts,
minNfts,
};
});
return { bids: mappedBids, pagination: {} };
}

public async placeAuctionBid(userId: number, placeBidBody: PlaceBidBody) {
//TODO: This is a temporartu endpoint until the scraper functionality is finished
const bidder = await this.usersService.getById(userId);
//TODO: This is a temporary endpoint until the scraper functionality is finished
const auction = await this.auctionRepository.findOne(placeBidBody.auctionId);

if (!auction) {
throw new AuctionNotFoundException();
}

const bid = await this.auctionBidRepository.save({
userId: userId,
amount: placeBidBody.amount,
auctionId: placeBidBody.auctionId,
const bidder = await this.usersService.getById(userId);

const bid = await this.auctionBidRepository.findOne({
where: { userId, auctionId: placeBidBody.auctionId },
});

const response = { ...bid, user: bidder };
if (bid) {
await this.auctionBidRepository.update(bid.id, {
amount: +bid.amount + +placeBidBody.amount,
});
} else {
await this.auctionBidRepository.save({
userId: userId,
amount: placeBidBody.amount,
auctionId: placeBidBody.auctionId,
});
}
const response = { ...placeBidBody, user: bidder };
return {
bid: response,
};
Expand Down