-
Notifications
You must be signed in to change notification settings - Fork 0
feat(s4): infrastructure persistence — JPA entities, repos, adapters (STA-134) #124
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package com.stablecoin.payments.custody.infrastructure.persistence; | ||
|
|
||
| import com.stablecoin.payments.custody.domain.model.ChainTransfer; | ||
| import com.stablecoin.payments.custody.domain.model.TransferStatus; | ||
| import com.stablecoin.payments.custody.domain.model.TransferType; | ||
| import com.stablecoin.payments.custody.domain.port.ChainTransferRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.entity.ChainTransferJpaRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.ChainTransferEntityUpdater; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.ChainTransferPersistenceMapper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| @Slf4j | ||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class ChainTransferPersistenceAdapter implements ChainTransferRepository { | ||
|
|
||
| private final ChainTransferJpaRepository jpa; | ||
| private final ChainTransferPersistenceMapper mapper; | ||
| private final ChainTransferEntityUpdater updater; | ||
|
|
||
| @Override | ||
| public ChainTransfer save(ChainTransfer transfer) { | ||
| var existing = jpa.findById(transfer.transferId()); | ||
| if (existing.isPresent()) { | ||
| updater.updateEntity(existing.get(), transfer); | ||
| return mapper.toDomain(jpa.save(existing.get())); | ||
| } | ||
| return mapper.toDomain(jpa.save(mapper.toEntity(transfer))); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<ChainTransfer> findById(UUID transferId) { | ||
| return jpa.findById(transferId).map(mapper::toDomain); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<ChainTransfer> findByPaymentIdAndType(UUID paymentId, TransferType type) { | ||
| return jpa.findByPaymentIdAndTransferType(paymentId, type).map(mapper::toDomain); | ||
| } | ||
|
|
||
| @Override | ||
| public List<ChainTransfer> findByStatus(TransferStatus status) { | ||
| return jpa.findByStatus(status).stream() | ||
| .map(mapper::toDomain) | ||
| .toList(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.stablecoin.payments.custody.infrastructure.persistence; | ||
|
|
||
| import com.stablecoin.payments.custody.domain.model.TransferLifecycleEvent; | ||
| import com.stablecoin.payments.custody.domain.port.TransferLifecycleEventRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.entity.TransferLifecycleEventJpaRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.TransferLifecycleEventPersistenceMapper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.UUID; | ||
|
|
||
| @Slf4j | ||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class TransferLifecycleEventPersistenceAdapter implements TransferLifecycleEventRepository { | ||
|
|
||
| private final TransferLifecycleEventJpaRepository jpa; | ||
| private final TransferLifecycleEventPersistenceMapper mapper; | ||
|
|
||
| @Override | ||
| public TransferLifecycleEvent save(TransferLifecycleEvent event) { | ||
| return mapper.toDomain(jpa.save(mapper.toEntity(event))); | ||
| } | ||
|
|
||
| @Override | ||
| public List<TransferLifecycleEvent> findByTransferId(UUID transferId) { | ||
| return jpa.findByTransferId(transferId).stream() | ||
| .map(mapper::toDomain) | ||
| .toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||||||||||||||||
| package com.stablecoin.payments.custody.infrastructure.persistence; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import com.stablecoin.payments.custody.domain.model.TransferParticipant; | ||||||||||||||||||||||||
| import com.stablecoin.payments.custody.domain.port.TransferParticipantRepository; | ||||||||||||||||||||||||
| import com.stablecoin.payments.custody.infrastructure.persistence.entity.TransferParticipantJpaRepository; | ||||||||||||||||||||||||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.TransferParticipantPersistenceMapper; | ||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||
| import org.springframework.stereotype.Repository; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Unused Logger is declared but never used. Either add logging for persistence operations or remove the annotation. ♻️ Option A: Remove unused annotation-@Slf4j
`@Repository`
`@RequiredArgsConstructor`♻️ Option B: Add debug logging `@Override`
public TransferParticipant save(TransferParticipant participant) {
- return mapper.toDomain(jpa.save(mapper.toEntity(participant)));
+ log.debug("Saving transfer participant: {}", participant.participantId());
+ var saved = mapper.toDomain(jpa.save(mapper.toEntity(participant)));
+ log.debug("Saved transfer participant: {}", saved.participantId());
+ return saved;
}📝 Committable suggestion
Suggested change
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| @Repository | ||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||
| public class TransferParticipantPersistenceAdapter implements TransferParticipantRepository { | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private final TransferParticipantJpaRepository jpa; | ||||||||||||||||||||||||
| private final TransferParticipantPersistenceMapper mapper; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||
| public TransferParticipant save(TransferParticipant participant) { | ||||||||||||||||||||||||
| return mapper.toDomain(jpa.save(mapper.toEntity(participant))); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||
| public List<TransferParticipant> findByTransferId(UUID transferId) { | ||||||||||||||||||||||||
| return jpa.findByTransferId(transferId).stream() | ||||||||||||||||||||||||
| .map(mapper::toDomain) | ||||||||||||||||||||||||
| .toList(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package com.stablecoin.payments.custody.infrastructure.persistence; | ||
|
|
||
| import com.stablecoin.payments.custody.domain.model.StablecoinTicker; | ||
| import com.stablecoin.payments.custody.domain.model.WalletBalance; | ||
| import com.stablecoin.payments.custody.domain.port.WalletBalanceRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.entity.WalletBalanceJpaRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.WalletBalanceEntityUpdater; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.WalletBalancePersistenceMapper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| @Slf4j | ||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class WalletBalancePersistenceAdapter implements WalletBalanceRepository { | ||
|
|
||
| private final WalletBalanceJpaRepository jpa; | ||
| private final WalletBalancePersistenceMapper mapper; | ||
| private final WalletBalanceEntityUpdater updater; | ||
|
|
||
| @Override | ||
| public WalletBalance save(WalletBalance balance) { | ||
| var existing = jpa.findById(balance.balanceId()); | ||
| if (existing.isPresent()) { | ||
| updater.updateEntity(existing.get(), balance); | ||
| return mapper.toDomain(jpa.save(existing.get())); | ||
| } | ||
| return mapper.toDomain(jpa.save(mapper.toEntity(balance))); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<WalletBalance> findByWalletIdAndStablecoin(UUID walletId, StablecoinTicker stablecoin) { | ||
| return jpa.findByWalletIdAndStablecoin(walletId, stablecoin.ticker()) | ||
| .map(mapper::toDomain); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<WalletBalance> findByWalletIdAndStablecoinForUpdate(UUID walletId, StablecoinTicker stablecoin) { | ||
| return jpa.findByWalletIdAndStablecoinForUpdate(walletId, stablecoin.ticker()) | ||
| .map(mapper::toDomain); | ||
| } | ||
|
|
||
| @Override | ||
| public List<WalletBalance> findByWalletId(UUID walletId) { | ||
| return jpa.findByWalletId(walletId).stream() | ||
| .map(mapper::toDomain) | ||
| .toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package com.stablecoin.payments.custody.infrastructure.persistence; | ||
|
|
||
| import com.stablecoin.payments.custody.domain.model.ChainId; | ||
| import com.stablecoin.payments.custody.domain.model.Wallet; | ||
| import com.stablecoin.payments.custody.domain.model.WalletPurpose; | ||
| import com.stablecoin.payments.custody.domain.port.WalletRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.entity.WalletJpaRepository; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.WalletEntityUpdater; | ||
| import com.stablecoin.payments.custody.infrastructure.persistence.mapper.WalletPersistenceMapper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| @Slf4j | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Remove unused The annotation is present but no 🤖 Prompt for AI Agents |
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class WalletPersistenceAdapter implements WalletRepository { | ||
|
|
||
| private final WalletJpaRepository jpa; | ||
| private final WalletPersistenceMapper mapper; | ||
| private final WalletEntityUpdater updater; | ||
|
|
||
| @Override | ||
| public Wallet save(Wallet wallet) { | ||
| var existing = jpa.findById(wallet.walletId()); | ||
| if (existing.isPresent()) { | ||
| updater.updateEntity(existing.get(), wallet); | ||
| return mapper.toDomain(jpa.save(existing.get())); | ||
| } | ||
| return mapper.toDomain(jpa.save(mapper.toEntity(wallet))); | ||
| } | ||
|
Comment on lines
+27
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add The 🔒 Proposed fix+import org.springframework.transaction.annotation.Transactional;
...
+ `@Transactional`
`@Override`
public Wallet save(Wallet wallet) {
var existing = jpa.findById(wallet.walletId());🤖 Prompt for AI Agents |
||
|
|
||
| @Override | ||
| public Optional<Wallet> findById(UUID walletId) { | ||
| return jpa.findById(walletId).map(mapper::toDomain); | ||
| } | ||
|
|
||
| @Override | ||
| public List<Wallet> findByChainIdAndPurpose(ChainId chainId, WalletPurpose purpose) { | ||
| return jpa.findByChainIdAndPurposeAndActiveTrue(chainId.value(), purpose).stream() | ||
| .map(mapper::toDomain) | ||
| .toList(); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Wallet> findByAddress(String address) { | ||
| return jpa.findByAddress(address).map(mapper::toDomain); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package com.stablecoin.payments.custody.infrastructure.persistence.entity; | ||
|
|
||
| import com.stablecoin.payments.custody.domain.model.TransferStatus; | ||
| import com.stablecoin.payments.custody.domain.model.TransferType; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.EnumType; | ||
| import jakarta.persistence.Enumerated; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.time.Instant; | ||
| import java.util.UUID; | ||
|
|
||
| @Entity | ||
| @Table(name = "chain_transfers") | ||
|
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check for existing indexes on chain_transfers table in migration files
rg -n 'CREATE.*INDEX.*chain_transfers' --type sqlRepository: Puneethkumarck/stablebridge-platform Length of output: 494 🏁 Script executed: # Check the exact definitions of the indexes in the migration file
rg -A 2 'CREATE.*INDEX.*chain_transfers' blockchain-custody/blockchain-custody/src/main/resources/db/migration/V1__initial_schema.sqlRepository: Puneethkumarck/stablebridge-platform Length of output: 426 🏁 Script executed: # Find and examine ChainTransferJpaRepository to see actual query methods
fd -n 'ChainTransferJpaRepository' blockchain-custody/blockchain-custody/srcRepository: Puneethkumarck/stablebridge-platform Length of output: 310 🏁 Script executed: # Find ChainTransferJpaRepository with correct syntax
fd 'ChainTransferJpaRepository' blockchain-custody/blockchain-custody/srcRepository: Puneethkumarck/stablebridge-platform Length of output: 229 🏁 Script executed: # Also search for any query methods that use transfer_type and payment_id together
rg -A 5 'findByPaymentId' blockchain-custody/blockchain-custody/src --type javaRepository: Puneethkumarck/stablebridge-platform Length of output: 2867 🏁 Script executed: # Verify transfer_type field exists in ChainTransferEntity
rg -A 2 'transfer_type|transferType' blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/entity/ChainTransferEntity.javaRepository: Puneethkumarck/stablebridge-platform Length of output: 209 Add composite index on (payment_id, transfer_type) for query optimization. The Note: 🤖 Prompt for AI Agents |
||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Builder | ||
| public class ChainTransferEntity { | ||
|
|
||
| @Id | ||
| @Column(name = "transfer_id", updatable = false) | ||
| private UUID transferId; | ||
|
|
||
| @Column(name = "payment_id") | ||
| private UUID paymentId; | ||
|
|
||
| @Column(name = "correlation_id") | ||
| private UUID correlationId; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(name = "transfer_type", length = 20) | ||
| private TransferType transferType; | ||
|
|
||
| @Column(name = "parent_transfer_id") | ||
| private UUID parentTransferId; | ||
|
|
||
| @Column(name = "chain_id", length = 20) | ||
| private String chainId; | ||
|
|
||
| @Column(name = "stablecoin", length = 20) | ||
| private String stablecoin; | ||
|
|
||
| @Column(name = "amount", precision = 30, scale = 8) | ||
| private BigDecimal amount; | ||
|
|
||
| @Column(name = "from_wallet_id") | ||
| private UUID fromWalletId; | ||
|
|
||
| @Column(name = "to_address", length = 128) | ||
| private String toAddress; | ||
|
|
||
| @Column(name = "from_address", length = 128) | ||
| private String fromAddress; | ||
|
|
||
| @Column(name = "nonce") | ||
| private Long nonce; | ||
|
|
||
| @Column(name = "tx_hash", length = 128) | ||
| private String txHash; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(name = "status", length = 30) | ||
| private TransferStatus status; | ||
|
|
||
| @Column(name = "block_number") | ||
| private Long blockNumber; | ||
|
|
||
| @Column(name = "block_confirmed_at") | ||
| private Instant blockConfirmedAt; | ||
|
|
||
| @Column(name = "confirmations") | ||
| private Integer confirmations; | ||
|
|
||
| @Column(name = "gas_used", precision = 20, scale = 0) | ||
| private BigDecimal gasUsed; | ||
|
|
||
| @Column(name = "gas_price_gwei", precision = 20, scale = 9) | ||
| private BigDecimal gasPriceGwei; | ||
|
|
||
| @Column(name = "attempt_count") | ||
| private int attemptCount; | ||
|
|
||
| @Column(name = "failure_reason") | ||
| private String failureReason; | ||
|
|
||
| @Column(name = "error_code", length = 100) | ||
| private String errorCode; | ||
|
|
||
| @Column(name = "created_at", updatable = false) | ||
| private Instant createdAt; | ||
|
|
||
| @Column(name = "updated_at") | ||
| private Instant updatedAt; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.stablecoin.payments.custody.infrastructure.persistence.entity; | ||
|
|
||
| import com.stablecoin.payments.custody.domain.model.TransferStatus; | ||
| import com.stablecoin.payments.custody.domain.model.TransferType; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| public interface ChainTransferJpaRepository extends JpaRepository<ChainTransferEntity, UUID> { | ||
|
|
||
| Optional<ChainTransferEntity> findByPaymentIdAndTransferType(UUID paymentId, TransferType type); | ||
|
|
||
| List<ChainTransferEntity> findByStatus(TransferStatus status); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider pagination for This returns an unbounded 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package com.stablecoin.payments.custody.infrastructure.persistence.entity; | ||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| import java.time.Instant; | ||
| import java.util.UUID; | ||
|
|
||
| @Entity | ||
| @Table(name = "transfer_lifecycle_events") | ||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Builder | ||
| public class TransferLifecycleEventEntity { | ||
|
|
||
| @Id | ||
| @Column(name = "event_id", updatable = false) | ||
| private UUID eventId; | ||
|
|
||
| @Column(name = "transfer_id") | ||
| private UUID transferId; | ||
|
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider adding an index on The Add a table index in your migration script: CREATE INDEX idx_transfer_lifecycle_events_transfer_id
ON transfer_lifecycle_events(transfer_id);🤖 Prompt for AI Agents |
||
|
|
||
| @Column(name = "state", length = 50) | ||
| private String state; | ||
|
|
||
| @Column(name = "participant_type", length = 10) | ||
| private String participantType; | ||
|
Comment on lines
+32
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider using enums for
If the domain types are enums, prefer: `@Enumerated`(EnumType.STRING)
`@Column`(name = "state", length = 50)
private TransferState state;
`@Enumerated`(EnumType.STRING)
`@Column`(name = "participant_type", length = 10)
private ParticipantType participantType;🤖 Prompt for AI Agents |
||
|
|
||
| @Column(name = "address", length = 128) | ||
| private String address; | ||
|
|
||
| @Column(name = "occurred_at") | ||
| private Instant occurredAt; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 2467
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 2523
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 2275
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 248
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 204
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 254
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 3143
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 1289
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 3511
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 229
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 693
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 1833
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 204
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 2275
🏁 Script executed:
Repository: Puneethkumarck/stablebridge-platform
Length of output: 1971
Add
@Transactionaltosave()for atomicity.The find-then-update-then-save pattern (lines 29–34) is not atomic without an explicit transaction boundary. Each operation runs independently, creating a TOCTOU window for concurrent saves. Spring Data's
JpaRepository.save()inherits the caller's transactional context—it does not provide its own.While no callers currently exist, this pattern appears across adapters (e.g.,
WalletPersistenceAdapter), pushing transaction responsibility to clients. Simpler to make it safe at the adapter layer.🤖 Prompt for AI Agents