MarketplaceApi.java

/*
 * discogs-java-client - A Java SDK to access the Discogs API
 * Copyright © 2025 Andy Miles (andy.miles@amilesend.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.amilesend.discogs.api;

import com.amilesend.client.parse.parser.MapParser;
import com.amilesend.discogs.connection.DiscogsConnection;
import com.amilesend.discogs.model.Api;
import com.amilesend.discogs.model.AuthenticationOptional;
import com.amilesend.discogs.model.AuthenticationRequired;
import com.amilesend.discogs.model.marketplace.AddOrderMessageRequest;
import com.amilesend.discogs.model.marketplace.AddOrderMessageResponse;
import com.amilesend.discogs.model.marketplace.CreateListingRequest;
import com.amilesend.discogs.model.marketplace.CreateListingResponse;
import com.amilesend.discogs.model.marketplace.DeleteListingRequest;
import com.amilesend.discogs.model.marketplace.GetFeeRequest;
import com.amilesend.discogs.model.marketplace.GetFeeResponse;
import com.amilesend.discogs.model.marketplace.GetInventoryRequest;
import com.amilesend.discogs.model.marketplace.GetInventoryResponse;
import com.amilesend.discogs.model.marketplace.GetListingRequest;
import com.amilesend.discogs.model.marketplace.GetListingResponse;
import com.amilesend.discogs.model.marketplace.GetOrderMessagesRequest;
import com.amilesend.discogs.model.marketplace.GetOrderMessagesResponse;
import com.amilesend.discogs.model.marketplace.GetOrderRequest;
import com.amilesend.discogs.model.marketplace.GetOrderResponse;
import com.amilesend.discogs.model.marketplace.GetOrdersRequest;
import com.amilesend.discogs.model.marketplace.GetOrdersResponse;
import com.amilesend.discogs.model.marketplace.GetPriceSuggestionsRequest;
import com.amilesend.discogs.model.marketplace.GetPriceSuggestionsResponse;
import com.amilesend.discogs.model.marketplace.GetReleaseStatisticsRequest;
import com.amilesend.discogs.model.marketplace.GetReleaseStatisticsResponse;
import com.amilesend.discogs.model.marketplace.UpdateListingRequest;
import com.amilesend.discogs.model.marketplace.UpdateOrderRequest;
import com.amilesend.discogs.model.marketplace.UpdateOrderResponse;
import com.amilesend.discogs.model.marketplace.type.Condition;
import com.amilesend.discogs.model.marketplace.type.Price;
import com.amilesend.discogs.model.type.Currency;
import lombok.NonNull;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;

/**
 * The Discogs Marketplace API.
 * <br/>
 * <a href="https://www.discogs.com/developers#page:marketplace">API Documentation</a>
 *
 * @see ApiBase
 */
@Api
public class MarketplaceApi extends ApiBase {
    private static final String USERS_API_PATH = "/users/";
    private static final String MARKETPLACE_API_PATH = "/marketplace";
    private static final String LISTINGS_SUB_API_PATH = "/listings/";
    private static final String ORDERS_SUB_API_PATH = "/orders/";
    private static final String FEE_SUB_API_PATH = "/fee/";

    /**
     * Creates a new {@code MarketplaceApi} object.
     *
     * @param connection the underlying client connection
     */
    public MarketplaceApi(final DiscogsConnection connection) {
        super(connection);
    }

    /**
     * Gets the list of listing for a user's inventory.
     *
     * @param request the request
     * @return the response
     * @see GetInventoryRequest
     * @see GetInventoryResponse
     */
    public GetInventoryResponse getInventory(@NonNull final GetInventoryRequest request) {
        final String subPath = new StringBuilder(USERS_API_PATH)
                .append(URLEncoder.encode(request.getUsername(), StandardCharsets.UTF_8))
                .append("/inventory")
                .toString();
        return executeGet(subPath, request, GetInventoryResponse.class);
    }

    /**
     * Gets a listing.
     *
     * @param request the request
     * @return the response
     * @see GetListingRequest
     * @see GetListingResponse
     */
    public GetListingResponse getListing(@NonNull final GetListingRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append(LISTINGS_SUB_API_PATH)
                .append(request.getListingId())
                .toString();
        return executeGet(subPath, request, GetListingResponse.class);
    }

    /**
     * Updates a listing. Note: Must be authenticated as the listing owner.
     *
     * @param request the request
     * @see UpdateListingRequest
     */
    @AuthenticationRequired
    public void updateListing(@NonNull final UpdateListingRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append(LISTINGS_SUB_API_PATH)
                .append(request.getListingId())
                .toString();
        executePost(subPath, request);
    }

    /**
     * Deletes a listing. Note: Must be authenticated as the listing owner.
     *
     * @param request the request
     */
    @AuthenticationRequired
    public void deleteListing(@NonNull final DeleteListingRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append(LISTINGS_SUB_API_PATH)
                .append(request.getListingId())
                .toString();
        executeDelete(subPath, request);
    }

    /**
     * Creates a new listing. Note: Must be authenticated as the listing owner.
     *
     * @param request the request that describes the new listing
     * @return the response containing the listing identifier
     * @see CreateListingRequest
     * @see CreateListingResponse
     */
    @AuthenticationRequired
    public CreateListingResponse createListing(@NonNull final CreateListingRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append("/listings")
                .toString();
        return executePost(subPath, request, CreateListingResponse.class);
    }

    /**
     * Gets information for a specific order. Note: Must be authenticated as the seller.
     *
     * @param request the request
     * @return the response
     * @see GetOrderRequest
     * @see GetOrderResponse
     */
    @AuthenticationRequired
    public GetOrderResponse getOrder(@NonNull final GetOrderRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append(ORDERS_SUB_API_PATH)
                .append(URLEncoder.encode(request.getOrderId(), StandardCharsets.UTF_8))
                .toString();
        return executeGet(subPath, request, GetOrderResponse.class);
    }

    /**
     * Updates an order. Note: Must be authenticated as the seller.
     *
     * @param request the request with the attributes to update
     * @return the response
     * @see UpdateOrderRequest
     * @see UpdateOrderResponse
     */
    @AuthenticationRequired
    public UpdateOrderResponse updateOrder(@NonNull final UpdateOrderRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append(ORDERS_SUB_API_PATH)
                .append(URLEncoder.encode(request.getOrderId(), StandardCharsets.UTF_8))
                .toString();
        return executePost(subPath, request, UpdateOrderResponse.class);
    }

    /**
     * Gets the paginated list of orders for the authenticated user.
     *
     * @param request the request
     * @return the paginated list of orders
     * @see GetOrdersRequest
     * @see GetOrdersResponse
     */
    @AuthenticationRequired
    public GetOrdersResponse getOrders(@NonNull final GetOrdersRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append("/orders")
                .toString();
        return executeGet(subPath, request, GetOrdersResponse.class);
    }

    /**
     * Gets the paginated list of messages associated with an order. Note: Must be authenticated as the seller.
     *
     * @param request the request
     * @return the paginated list of messages.
     * @see GetOrderMessagesRequest
     * @see GetOrderMessagesResponse
     */
    @AuthenticationRequired
    public GetOrderMessagesResponse getOrderMessages(@NonNull final GetOrderMessagesRequest request) {
        final String subPath  = new StringBuilder(MARKETPLACE_API_PATH)
                .append(ORDERS_SUB_API_PATH)
                .append(URLEncoder.encode(request.getOrderId(), StandardCharsets.UTF_8))
                .append("/messages")
                .toString();
        return executeGet(subPath, request, GetOrderMessagesResponse.class);
    }

    /**
     * Adds a message to an order: Must be authenticated as the seller.
     *
     * @param request the request
     * @return the response
     * @see AddOrderMessageRequest
     * @see AddOrderMessageResponse
     */
    @AuthenticationRequired
    public AddOrderMessageResponse addOrderMessage(@NonNull final AddOrderMessageRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append(ORDERS_SUB_API_PATH)
                .append(URLEncoder.encode(request.getOrderId(), StandardCharsets.UTF_8))
                .append("/messages")
                .toString();
        return executePost(subPath, request, AddOrderMessageResponse.class);
    }

    /**
     * Gets the fee when selling an item at a given price.
     *
     * @param request the request
     * @return the response
     * @see GetFeeRequest
     * @see GetFeeResponse
     */
    @AuthenticationRequired
    public GetFeeResponse getFee(@NonNull final GetFeeRequest request) {
        final StringBuilder subPathBuilder = new StringBuilder(MARKETPLACE_API_PATH)
                .append(FEE_SUB_API_PATH)
                .append(request.getPrice());
        final Currency currency = request.getCurrency();
        final String subPath = Objects.nonNull(currency)
                ? subPathBuilder.append("/").append(currency).toString()
                : subPathBuilder.toString();
        return executeGet(subPath, request, GetFeeResponse.class);
    }

    /**
     * Gets a map of prices suggestions foa given release.
     *
     * @param request the request
     * @return the response
     * @see GetPriceSuggestionsRequest
     * @see GetPriceSuggestionsResponse
     */
    @AuthenticationRequired
    public GetPriceSuggestionsResponse getPriceSuggestions(@NonNull final GetPriceSuggestionsRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append("/price_suggestions/")
                .append(request.getReleaseId())
                .toString();

        final DiscogsConnection connection = getConnection();
        final Map<Condition, Price> prices =  connection.execute(
                connection.newRequestBuilder()
                        .url(buildHttpUrl(subPath, request))
                        .build(),
                new MapParser<>(Condition.class, Price.class));

        return GetPriceSuggestionsResponse.builder()
                .priceSuggestions(prices)
                .build();
    }

    /**
     * Gets the sales statistics for a release. Note: Authentication is optional. Authenticated users will have the
     * currency expressed by their configured preference. Unauthenticated users will have prices reflected in
     * {@link Currency#USD}.
     *
     * @param request the request
     * @return the response
     * @see GetReleaseStatisticsRequest
     * @see GetReleaseStatisticsResponse
     */
    @AuthenticationOptional
    public GetReleaseStatisticsResponse getReleaseStatistics(@NonNull final GetReleaseStatisticsRequest request) {
        final String subPath = new StringBuilder(MARKETPLACE_API_PATH)
                .append("/stats/")
                .append(request.getReleaseId())
                .toString();
        return executeGet(subPath, request, GetReleaseStatisticsResponse.class);
    }
}