Skip to content

Commit

Permalink
Add OfferbookRestApi
Browse files Browse the repository at this point in the history
  • Loading branch information
HenrikJannsen committed Nov 21, 2024
1 parent a207df0 commit ffb63dc
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public DesktopApplicationService(String[] args, ShutDownHandler shutDownHandler)
webcamAppService);

var restApiConfig = RestApiService.Config.from(getConfig("restApi"));
var restApiResourceConfig = new RestApiResourceConfig(restApiConfig, networkService, userService, bondedRolesService);
var restApiResourceConfig = new RestApiResourceConfig(restApiConfig, networkService, userService, bondedRolesService, chatService);
restApiService = new RestApiService(restApiConfig, restApiResourceConfig);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public RestApiApplicationService(String[] args) {
tradeService);

var restApiConfig = RestApiService.Config.from(getConfig("restApi"));
var restApiResourceConfig = new RestApiResourceConfig(restApiConfig, networkService, userService, bondedRolesService);
var restApiResourceConfig = new RestApiResourceConfig(restApiConfig, networkService, userService, bondedRolesService, chatService);
restApiService=new RestApiService(restApiConfig, restApiResourceConfig);
}

Expand Down
1 change: 1 addition & 0 deletions chat/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ dependencies {
implementation(libs.chimp.jsocks)
implementation(libs.google.gson)
implementation(libs.typesafe.config)
implementation(libs.bundles.rest.api.libs)
}
134 changes: 134 additions & 0 deletions chat/src/main/java/bisq/chat/bisqeasy/offerbook/OfferbookRestApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.chat.bisqeasy.offerbook;

import bisq.common.currency.Market;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Path("/offerbook")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Bisq Easy Offerbook API")
public class OfferbookRestApi {

private final BisqEasyOfferbookChannelService bisqEasyOfferbookChannelService;

public OfferbookRestApi(BisqEasyOfferbookChannelService bisqEasyOfferbookChannelService) {
this.bisqEasyOfferbookChannelService = bisqEasyOfferbookChannelService;
}

/**
* Retrieves a list of markets.
*
* @return List of {@link Market} objects.
*/
@Operation(
summary = "Returns a list of markets",
description = "Fetches and returns a list of all available markets.",
responses = {
@ApiResponse(responseCode = "200", description = "Markets retrieved successfully",
content = @Content(schema = @Schema(type = "array", implementation = Market.class))),
@ApiResponse(responseCode = "500", description = "Internal server error")
}
)
@GET
@Path("markets")
public Response getMarkets() {
try {
List<Market> markets = bisqEasyOfferbookChannelService.getChannels().stream()
.map(BisqEasyOfferbookChannel::getMarket)
.collect(Collectors.toList());
return buildOkResponse(markets);
} catch (Exception e) {
log.error("Error retrieving markets", e);
return buildErrorResponse("Failed to retrieve markets");
}
}

/**
* Retrieves a map of the number of offers per market code.
*
* @return A map where the key is the market code, and the value is the number of offers.
*/
@Operation(
summary = "Returns a map of the number of offers per market code",
description = "Fetches and returns a map containing the number of offers for each market code.",
responses = {
@ApiResponse(responseCode = "200", description = "Offer counts retrieved successfully",
content = @Content(schema = @Schema(implementation = Map.class))),
@ApiResponse(responseCode = "500", description = "Internal server error")
}
)
@GET
@Path("markets/offers/count")
public Response getNumOffersByMarketCode() {
try {
Map<String, Integer> numOffersByMarketCode = bisqEasyOfferbookChannelService.getChannels().stream()
.collect(Collectors.toMap(
channel -> channel.getMarket().getQuoteCurrencyCode(),
channel -> (int) channel.getChatMessages().stream()
.filter(BisqEasyOfferbookMessage::hasBisqEasyOffer)
.count()
));
return buildOkResponse(numOffersByMarketCode);
} catch (Exception e) {
log.error("Error retrieving offer counts by market code", e);
return buildErrorResponse("Failed to retrieve offer counts");
}
}

/**
* Builds a successful 200 OK response.
*
* @param entity The response entity.
* @return The HTTP response.
*/
private Response buildOkResponse(Object entity) {
return Response.status(Response.Status.OK)
.entity(entity)
.build();
}

/**
* Builds an error response with a 500 status.
*
* @param message The error message.
* @return The HTTP response.
*/
private Response buildErrorResponse(String message) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", message))
.build();
}
}
8 changes: 8 additions & 0 deletions common/src/main/java/bisq/common/currency/Market.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import bisq.common.proto.NetworkProto;
import bisq.common.proto.PersistableProto;
import bisq.common.validation.NetworkDataValidation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -84,34 +85,41 @@ public static Market fromProto(bisq.common.protobuf.Market proto) {
proto.getQuoteCurrencyName());
}

@JsonIgnore
public String getQuoteCurrencyDisplayName() {
return FiatCurrency.isFiat(quoteCurrencyCode)
? FiatCurrencyRepository.getDisplayName(quoteCurrencyCode).orElse(quoteCurrencyName)
: quoteCurrencyName;
}

@JsonIgnore
public String getBaseCurrencyDisplayName() {
return FiatCurrency.isFiat(baseCurrencyCode)
? FiatCurrencyRepository.getDisplayName(baseCurrencyCode).orElse(baseCurrencyName)
: baseCurrencyName;
}

//todo (refactor, low prio) make static utils
@JsonIgnore
public String getFiatCurrencyName() {
return isFiat() ? getQuoteCurrencyDisplayName() : getBaseCurrencyDisplayName();
}

@JsonIgnore
public boolean isFiat() {
return FiatCurrency.isFiat(quoteCurrencyCode);
}

@JsonIgnore
public String getMarketCodes() {
return baseCurrencyCode + QUOTE_SEPARATOR + quoteCurrencyCode;
}

public static String createBitcoinFiatMarketCodes(String baseCurrencyCode, String quoteCurrencyCode) {
return baseCurrencyCode + QUOTE_SEPARATOR + quoteCurrencyCode;
}

@JsonIgnore
public String getMarketDisplayName() {
return getBaseCurrencyDisplayName() + QUOTE_SEPARATOR + getQuoteCurrencyDisplayName();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package bisq.rest_api;

import bisq.bonded_roles.BondedRolesService;
import bisq.chat.ChatService;
import bisq.chat.bisqeasy.offerbook.OfferbookRestApi;
import bisq.network.NetworkService;
import bisq.user.UserService;
import bisq.user.identity.UserIdentityRestApi;
Expand All @@ -12,7 +14,8 @@ public class RestApiResourceConfig extends BaseRestApiResourceConfig {
public RestApiResourceConfig(RestApiService.Config config,
NetworkService networkService,
UserService userService,
BondedRolesService bondedRolesService) {
BondedRolesService bondedRolesService,
ChatService chatService) {
super(config);

//todo apply filtering with whiteListEndPoints/whiteListEndPoints
Expand All @@ -21,11 +24,13 @@ public RestApiResourceConfig(RestApiService.Config config,
// As we want to pass the dependencies in the constructor, so we need the hack
// with AbstractBinder to register resources as classes for Swagger
register(UserIdentityRestApi.class);
register(OfferbookRestApi.class);

register(new AbstractBinder() {
@Override
protected void configure() {
bind(new UserIdentityRestApi(userService.getUserIdentityService())).to(UserIdentityRestApi.class);
bind(new OfferbookRestApi(chatService.getBisqEasyOfferbookChannelService())).to(OfferbookRestApi.class);
}
});
}
Expand Down

0 comments on commit ffb63dc

Please sign in to comment.