Skip to content

Commit

Permalink
Add pending token airdrops to REST API (#9383)
Browse files Browse the repository at this point in the history
Adds the pending token airdrops endpoint to the REST API.

---------

Signed-off-by: Edwin Greene <[email protected]>
  • Loading branch information
edwin-greene authored Sep 18, 2024
1 parent 5f0f768 commit 61d6930
Show file tree
Hide file tree
Showing 10 changed files with 679 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
import static com.hedera.mirror.restjava.common.Constants.DEFAULT_LIMIT;
import static com.hedera.mirror.restjava.common.Constants.MAX_LIMIT;
import static com.hedera.mirror.restjava.common.Constants.RECEIVER_ID;
import static com.hedera.mirror.restjava.common.Constants.SENDER_ID;
import static com.hedera.mirror.restjava.common.Constants.TOKEN_ID;
import static com.hedera.mirror.restjava.dto.TokenAirdropRequest.AirdropRequestType.OUTSTANDING;
import static com.hedera.mirror.restjava.dto.TokenAirdropRequest.AirdropRequestType.PENDING;

import com.google.common.collect.ImmutableSortedMap;
import com.hedera.mirror.rest.model.TokenAirdrop;
Expand All @@ -29,6 +32,7 @@
import com.hedera.mirror.restjava.common.EntityIdRangeParameter;
import com.hedera.mirror.restjava.common.LinkFactory;
import com.hedera.mirror.restjava.dto.TokenAirdropRequest;
import com.hedera.mirror.restjava.dto.TokenAirdropRequest.AirdropRequestType;
import com.hedera.mirror.restjava.mapper.TokenAirdropMapper;
import com.hedera.mirror.restjava.service.Bound;
import com.hedera.mirror.restjava.service.TokenAirdropService;
Expand All @@ -55,6 +59,7 @@
public class TokenAirdropsController {
private static final Function<TokenAirdrop, Map<String, String>> EXTRACTOR = tokenAirdrop -> ImmutableSortedMap.of(
RECEIVER_ID, tokenAirdrop.getReceiverId(),
SENDER_ID, tokenAirdrop.getSenderId(),
TOKEN_ID, tokenAirdrop.getTokenId());

private final LinkFactory linkFactory;
Expand All @@ -68,16 +73,41 @@ TokenAirdropsResponse getOutstandingAirdrops(
@RequestParam(defaultValue = "asc") Sort.Direction order,
@RequestParam(name = RECEIVER_ID, required = false) @Size(max = 2) List<EntityIdRangeParameter> receiverIds,
@RequestParam(name = TOKEN_ID, required = false) @Size(max = 2) List<EntityIdRangeParameter> tokenIds) {
var entityIdsBound = new Bound(receiverIds, true, ACCOUNT_ID);
return processRequest(id, entityIdsBound, limit, order, tokenIds, OUTSTANDING, RECEIVER_ID);
}

@GetMapping(value = "/pending")
TokenAirdropsResponse getPendingAirdrops(
@PathVariable EntityIdParameter id,
@RequestParam(defaultValue = DEFAULT_LIMIT) @Positive @Max(MAX_LIMIT) int limit,
@RequestParam(defaultValue = "asc") Sort.Direction order,
@RequestParam(name = SENDER_ID, required = false) @Size(max = 2) List<EntityIdRangeParameter> senderIds,
@RequestParam(name = TOKEN_ID, required = false) @Size(max = 2) List<EntityIdRangeParameter> tokenIds) {
var entityIdsBound = new Bound(senderIds, true, ACCOUNT_ID);
return processRequest(id, entityIdsBound, limit, order, tokenIds, PENDING, SENDER_ID);
}

private TokenAirdropsResponse processRequest(
EntityIdParameter id,
Bound entityIdsBound,
int limit,
Sort.Direction order,
List<EntityIdRangeParameter> tokenIds,
AirdropRequestType type,
String primarySortField) {
var request = TokenAirdropRequest.builder()
.accountId(id)
.entityIds(new Bound(receiverIds, true, ACCOUNT_ID))
.entityIds(entityIdsBound)
.limit(limit)
.order(order)
.tokenIds(new Bound(tokenIds, false, TOKEN_ID))
.type(type)
.build();
var response = service.getOutstandingAirdrops(request);

var response = service.getAirdrops(request);
var airdrops = tokenAirdropMapper.map(response);
var sort = Sort.by(order, RECEIVER_ID, TOKEN_ID);
var sort = Sort.by(order, primarySortField, TOKEN_ID);
var pageable = PageRequest.of(0, limit, sort);
var links = linkFactory.create(airdrops, pageable, EXTRACTOR);
return new TokenAirdropsResponse().airdrops(airdrops).links(links);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@

package com.hedera.mirror.restjava.dto;

import static com.hedera.mirror.restjava.jooq.domain.Tables.TOKEN_AIRDROP;

import com.hedera.mirror.restjava.common.EntityIdParameter;
import com.hedera.mirror.restjava.service.Bound;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jooq.Field;
import org.springframework.data.domain.Sort;

@Data
Expand All @@ -39,4 +44,24 @@ public class TokenAirdropRequest {
private Bound entityIds;

private Bound tokenIds;

@Builder.Default
private AirdropRequestType type = AirdropRequestType.OUTSTANDING;

@Getter
@RequiredArgsConstructor
public enum AirdropRequestType {
OUTSTANDING(TOKEN_AIRDROP.SENDER_ACCOUNT_ID, TOKEN_AIRDROP.RECEIVER_ACCOUNT_ID),
PENDING(TOKEN_AIRDROP.RECEIVER_ACCOUNT_ID, TOKEN_AIRDROP.SENDER_ACCOUNT_ID);

// The base field is the conditional clause for the base DB query.
// The base field is the path parameter accountId, which is Sender Id for Outstanding Airdrops and Receiver Id
// for Pending Airdrops
private final Field<Long> baseField;

// The primary field is the primary sort field for the DB query.
// The primary field is the optional query parameter 'entityIds', which is Receiver Id for Outstanding Airdrops
// and Sender Id for Pending Airdrops
private final Field<Long> primaryField;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
public interface TokenAirdropRepositoryCustom extends JooqRepository {

@NotNull
Collection<TokenAirdrop> findAllOutstanding(TokenAirdropRequest request, EntityId accountId);
Collection<TokenAirdrop> findAll(TokenAirdropRequest request, EntityId accountId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package com.hedera.mirror.restjava.repository;

import static com.hedera.mirror.restjava.common.RangeOperator.EQ;
import static com.hedera.mirror.restjava.dto.TokenAirdropRequest.AirdropRequestType.OUTSTANDING;
import static com.hedera.mirror.restjava.dto.TokenAirdropRequest.AirdropRequestType.PENDING;
import static com.hedera.mirror.restjava.jooq.domain.Tables.TOKEN_AIRDROP;

import com.hedera.mirror.common.domain.entity.EntityId;
import com.hedera.mirror.common.domain.token.TokenAirdrop;
import com.hedera.mirror.restjava.dto.TokenAirdropRequest;
import com.hedera.mirror.restjava.dto.TokenAirdropRequest.AirdropRequestType;
import com.hedera.mirror.restjava.jooq.domain.enums.AirdropState;
import jakarta.inject.Named;
import java.util.Collection;
Expand All @@ -30,6 +33,7 @@
import lombok.RequiredArgsConstructor;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.SortField;
import org.springframework.data.domain.Sort.Direction;

Expand All @@ -38,20 +42,28 @@
class TokenAirdropRepositoryCustomImpl implements TokenAirdropRepositoryCustom {

private final DSLContext dslContext;
private static final Map<Direction, List<SortField<?>>> OUTSTANDING_SORT_ORDERS = Map.of(
Direction.ASC, List.of(TOKEN_AIRDROP.RECEIVER_ACCOUNT_ID.asc(), TOKEN_AIRDROP.TOKEN_ID.asc()),
Direction.DESC, List.of(TOKEN_AIRDROP.RECEIVER_ACCOUNT_ID.desc(), TOKEN_AIRDROP.TOKEN_ID.desc()));
private static final Map<AirdropRequestType, Map<Direction, List<SortField<?>>>> SORT_ORDERS = Map.of(
OUTSTANDING,
Map.of(
Direction.ASC, List.of(OUTSTANDING.getPrimaryField().asc(), TOKEN_AIRDROP.TOKEN_ID.asc()),
Direction.DESC,
List.of(OUTSTANDING.getPrimaryField().desc(), TOKEN_AIRDROP.TOKEN_ID.desc())),
PENDING,
Map.of(
Direction.ASC, List.of(PENDING.getPrimaryField().asc(), TOKEN_AIRDROP.TOKEN_ID.asc()),
Direction.DESC, List.of(PENDING.getPrimaryField().desc(), TOKEN_AIRDROP.TOKEN_ID.desc())));

@Override
public Collection<TokenAirdrop> findAllOutstanding(TokenAirdropRequest request, EntityId accountId) {
var fieldBounds = getFieldBound(request, true);
var condition = getBaseCondition(accountId, true)
public Collection<TokenAirdrop> findAll(TokenAirdropRequest request, EntityId accountId) {
var type = request.getType();
var fieldBounds = getFieldBound(request);
var condition = getBaseCondition(accountId, type.getBaseField())
.and(getBoundCondition(fieldBounds))
.and(TOKEN_AIRDROP.STATE.eq(AirdropState.PENDING))
// Exclude NFTs
.and(TOKEN_AIRDROP.SERIAL_NUMBER.eq(0L));

var order = OUTSTANDING_SORT_ORDERS.get(request.getOrder());
var order = SORT_ORDERS.get(type).get(request.getOrder());
return dslContext
.selectFrom(TOKEN_AIRDROP)
.where(condition)
Expand All @@ -60,17 +72,14 @@ public Collection<TokenAirdrop> findAllOutstanding(TokenAirdropRequest request,
.fetchInto(TokenAirdrop.class);
}

private ConditionalFieldBounds getFieldBound(TokenAirdropRequest request, boolean outstanding) {
var primaryField = outstanding ? TOKEN_AIRDROP.RECEIVER_ACCOUNT_ID : TOKEN_AIRDROP.SENDER_ACCOUNT_ID;
private ConditionalFieldBounds getFieldBound(TokenAirdropRequest request) {
var primaryField = request.getType().getPrimaryField();
var primary = new FieldBound(primaryField, request.getEntityIds());
var secondary = new FieldBound(TOKEN_AIRDROP.TOKEN_ID, request.getTokenIds());
return new ConditionalFieldBounds(primary, secondary);
}

private Condition getBaseCondition(EntityId accountId, boolean outstanding) {
return getCondition(
outstanding ? TOKEN_AIRDROP.SENDER_ACCOUNT_ID : TOKEN_AIRDROP.RECEIVER_ACCOUNT_ID,
EQ,
accountId.getId());
private Condition getBaseCondition(EntityId accountId, Field<Long> baseField) {
return getCondition(baseField, EQ, accountId.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@

public interface TokenAirdropService {

Collection<TokenAirdrop> getOutstandingAirdrops(TokenAirdropRequest request);
Collection<TokenAirdrop> getAirdrops(TokenAirdropRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public class TokenAirdropServiceImpl implements TokenAirdropService {
private final EntityService entityService;
private final TokenAirdropRepository repository;

public Collection<TokenAirdrop> getOutstandingAirdrops(TokenAirdropRequest request) {
public Collection<TokenAirdrop> getAirdrops(TokenAirdropRequest request) {
var id = entityService.lookup(request.getAccountId());
return repository.findAllOutstanding(request, id);
return repository.findAll(request, id);
}
}
Loading

0 comments on commit 61d6930

Please sign in to comment.