Update the Backend API¶
Update the backend Java code to handle the new choice based on the user’s actions in the frontend.
The backend update includes modifications to three existing files within the app/backend
subdirectories, api.yaml
, Store.java
, and UserApiDelegateImpl.java
.
Step 1: Modify api.yaml
Define the API endpoint in api.yaml
to allow users to retrieve rejected transfer offers. The endpoint is added to api.yaml
(located in /app/backend/src/main/openapi/
) after the /user/transfer-offers/disclosed-contracts-for-accept
endpoint. It bridges the desired business operation with the system’s data and business logic.
123 /user/rejected-transfer-offers:
124 get:
125 security:
126 - BearerAuth: []
127 tags:
128 - read
129 summary: Get rejected transfer offers
130 operationId: getRejectedTransferOffers
131 responses:
132 '200':
133 description: Successfully retrieved rejected transfer offers
134 content:
135 application/json:
136 schema:
137 $ref: '#/components/schemas/ListRejectedTransferOfferResponse'
138 '403':
139 description: Permission denied
140 '500':
141 description: Internal server error
Rejected-transfer-offers
is a read operation API endpoint that uses a GET request to retrieve rejected transfer offers. It requires bearer token authentication and can return three types of responses: success (200), permission denied (403), or server error (500).
A success response returns data in a defined JSON format, structured according to the ListRejectedTransferOfferResponse
schema. The following schema includes a rejected transfer offer object and an array of items, ensuring a consistent data structure for clients.
Add the schema ListRejectedTransferOfferResponse
after the schema GetTransferOfferResponse
.
238 ListRejectedTransferOfferResponse:
239 type: object
240 required:
241 - rejected_transfer_offer
242 properties:
243 rejected_transfer_offers:
244 type: array
245 items:
246 $ref: "#/components/schemas/TemplateContract"
The schema ensures that the success response includes a list of rejected transfer offers. Together, the endpoint and schema define how to request and interpret data about rejected transfer offers.
Step 2: Update Store.java
Next, locate Store.java
in /app/backend/src/main/java/com/daml/app/template/store
.
Import the RejectedTransferOffer
template with the existing model imports.
20 import com.daml.app.template.codegen.com.daml.app.template.model.RejectedTransferOffer;
Add a method, listRejectedTransferOffers
, near the end of the file, above the listTransientFailureRequests
method. listRejectedTransferOffers
retrieves transfer offer rejections using the Participant Query Store (PQS).
423 public List<TemplateContract<RejectedTransferOffer.ContractId, RejectedTransferOffer>>
424 listRejectedTransferOffers(String userPartyId) {
425 return pqs.queryTemplates(
426 RejectedTransferOffer.COMPANION,
427 """
428 select contract_id, payload::text, metadata
429 from active('Com.Daml.App.Template.Model:RejectedTransferOffer')
430 where payload -> 'transferOffer' ->> 'provider' = ?
431 and (payload -> 'transferOffer' ->> 'sender' = ? or payload -> 'transferOffer' ->> 'receiver' = ?)
432 """,
433 getProviderParty(),
434 userPartyId,
435 userPartyId)
436 .value();
437 }
The listRejectedTransferOffers
method queries PQS for rejected transfer offers. It takes the user’s ID as input and returns a list of TemplateContract
objects containing the rejected transfer offer data.
PQS operates as a separate service within the participant’s infrastructure. It maintains a mirrored copy of the ledger data in a queryable format. PQS is intended as a long-running process that may be safely restarted at any time. It provides a scalable “read” pipeline that follows the Command Query Responsibility Segregation (CQRS) design pattern, which separates read operations from write operations to improve performance and scalability.
In this extension, PQS fetches and manages relevant rejected transfer offers, enhancing the application’s ability to handle the full lifecycle of transfer offers, including their rejection.
Step 3: Update UserApiDelegateImpl.java
Find UserApiDelegateImpl.java
in /app/backend/src/main/java/com/daml/app/template/api/
.
Implement the endpoint added under /user/
in api.yaml
by delegating rejected transfer offer contract retrieval to Store.java
.
First, import TemplateContract
and the RejectedTransferOffer
template from the Daml model.
4 import com.daml.app.common.TemplateContract;
5 import com.daml.app.template.codegen.com.daml.app.template.model.RejectedTransferOffer;
Import the Collectors
utility.
15 import java.util.stream.Collectors;
Then, implement the getRejectedTransferOffers
method after the currently existing getTransferOffer
method.
119 @Override
120 public ResponseEntity<ListRejectedTransferOfferResponse> getRejectedTransferOffers() {
121 final String userPartyId = getUserPartyId();
122 try {
123 List<TemplateContract<RejectedTransferOffer.ContractId, RejectedTransferOffer>>
124 rejectedOffers = store.listRejectedTransferOffers(userPartyId);
125
126 List<com.daml.app.template.model.TemplateContract> openApiRejectedOffers =
127 rejectedOffers.stream()
128 .map(offer -> offer.toOpenApiServer(objectMapper))
129 .collect(Collectors.toList());
130
131 var response = new ListRejectedTransferOfferResponse();
132 response.setRejectedTransferOffers(openApiRejectedOffers);
133
134 return ResponseEntity.ok(response);
135 } catch (Exception e) {
136 logger.error(
137 "Error fetching rejected transfer offers for user {}: {}", userPartyId, e.getMessage());
138 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
139 }
140 }
The getRejectedTransferOffers
method serves as an interface between the API and the data store.
It calls store.listRejectedTransferOffers
to fetch the list of the user’s observable rejected transfer offer contracts from the store. Then, it maps the retrieved rejected offers to the OpenAPI server format, creates a ListRejectedTransferOfferResponse
object, and populates the object with mapped offers. Finally, it returns an HTTP response containing the list of rejected transfer offers.
The interface ensures that the API can retrieve and return rejected transfer offers while maintaining consistency with the API specifications defined in api.yaml
.
Verify that all changes have been saved, then rebuild the backend.
./gradlew :app:backend:assemble
Gradle generates the backend files with the new API endpoints.