Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
package com.codedifferently.lesson17.bank;

import com.codedifferently.lesson17.bank.exceptions.AccountNotFoundException;
import com.codedifferently.lesson17.bank.exceptions.IllegalOperationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/** Represents a bank ATM. */
/** Represents a bank ATM with audit logging capabilities. */
public class BankAtm {

private final Map<UUID, Customer> customerById = new HashMap<>();
private final Map<String, CheckingAccount> accountByNumber = new HashMap<>();
private final AuditLog auditLog = new AuditLog();

/**
* Gets the audit log for this ATM.
*
* @return The audit log instance.
*/
public AuditLog getAuditLog() {
return auditLog;
}

/**
* Adds a checking account to the bank.
Expand All @@ -25,6 +36,11 @@ public void addAccount(CheckingAccount account) {
owner -> {
customerById.put(owner.getId(), owner);
});

// Log account creation
String accountType = account instanceof SavingsAccount ? "SavingsAccount" : "CheckingAccount";
auditLog.logTransaction(account.getAccountNumber(), "ACCOUNT_CREATED",
account.getBalance(), "New " + accountType + " created");
}

/**
Expand All @@ -48,17 +64,31 @@ public Set<CheckingAccount> findAccountsByCustomerId(UUID customerId) {
public void depositFunds(String accountNumber, double amount) {
CheckingAccount account = getAccountOrThrow(accountNumber);
account.deposit(amount);

// Log the transaction
auditLog.logTransaction(accountNumber, "DEPOSIT", amount, "Cash deposit");
}

/**
* Deposits funds into an account using a check.
* Note: SavingsAccount does not support check deposits.
*
* @param accountNumber The account number.
* @param check The check to deposit.
*/
public void depositFunds(String accountNumber, Check check) {
CheckingAccount account = getAccountOrThrow(accountNumber);

// Restrict check operations for SavingsAccount (SOLID: Liskov Substitution Principle)
if (account instanceof SavingsAccount) {
throw new IllegalOperationException("SavingsAccount does not support check operations");
}

check.depositFunds(account);

// Log the transaction
auditLog.logTransaction(accountNumber, "CHECK_DEPOSIT", check.getAmount(),
"Check deposit from account " + check.getAccount().getAccountNumber());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,33 @@ public boolean getIsVoided() {
return isVoided;
}

/**
* Gets the amount of the check.
*
* @return The check amount.
*/
public double getAmount() {
return amount;
}

/**
* Gets the account the check is drawn on.
*
* @return The source account.
*/
public CheckingAccount getAccount() {
return account;
}

/**
* Gets the check number.
*
* @return The check number.
*/
public String getCheckNumber() {
return checkNumber;
}

/** Voids the check. */
public void voidCheck() {
isVoided = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.codedifferently.lesson17.bank.exceptions;

/**
* Exception thrown when an illegal operation is attempted on an account.
* For example, trying to write checks against a SavingsAccount.
*/
public class IllegalOperationException extends RuntimeException {

/**
* Creates a new IllegalOperationException with the specified message.
*
* @param message The error message.
*/
public IllegalOperationException(String message) {
super(message);
}

/**
* Creates a new IllegalOperationException with the specified message and cause.
*
* @param message The error message.
* @param cause The underlying cause.
*/
public IllegalOperationException(String message, Throwable cause) {
super(message, cause);
}
}
9 changes: 9 additions & 0 deletions lesson_17/jaizelc/bank/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

5 changes: 5 additions & 0 deletions lesson_17/jaizelc/bank/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build
85 changes: 85 additions & 0 deletions lesson_17/jaizelc/bank/bank_app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
eclipse
jacoco
id("io.freefair.lombok") version "8.10.2"
id("com.diffplug.spotless") version "6.25.0"
id("org.springframework.boot") version "3.4.0"
id("com.adarshr.test-logger") version "4.0.0"
}

apply(plugin = "io.spring.dependency-management")

repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}

dependencies {
// Use JUnit Jupiter for testing.
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.assertj:assertj-core:3.26.3")
testImplementation("at.favre.lib:bcrypt:0.10.2")

// This dependency is used by the application.
implementation("com.google.guava:guava:33.3.1-jre")
implementation("com.google.code.gson:gson:2.11.0")
implementation("org.projectlombok:lombok:1.18.30")
implementation("org.springframework.boot:spring-boot-starter")
}

application {
// Define the main class for the application.
mainClass.set("com.codedifferently.lesson17.Lesson17")
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
finalizedBy(tasks.jacocoTestReport)
}

tasks.jacocoTestReport {
dependsOn(tasks.test)
reports {
xml.required = true
}
}

tasks.jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = "0.8".toBigDecimal()
}
}
}
}

tasks.check {
dependsOn(tasks.jacocoTestCoverageVerification)
}

configure<com.diffplug.gradle.spotless.SpotlessExtension> {

format("misc", {
// define the files to apply `misc` to
target("*.gradle", ".gitattributes", ".gitignore")

// define the steps to apply to those files
trimTrailingWhitespace()
indentWithTabs() // or spaces. Takes an integer argument if you don't like 4
endWithNewline()
})

java {
// don't need to set target, it is inferred from java

// apply a specific flavor of google-java-format
googleJavaFormat()
// fix formatting of type annotations
formatAnnotations()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codedifferently.lesson17;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;

@Configuration
@SpringBootApplication(scanBasePackages = "com.codedifferently")
public class Lesson17 {

public static void main(String[] args) {
var application = new SpringApplication(Lesson17.class);
application.run(args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.codedifferently.lesson17.bank;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
* Represents an audit log entry for banking transactions. Follows Single Responsibility Principle -
* only handles transaction logging.
*/
public class AuditLog {

/** Represents a single audit entry. */
public static class AuditEntry {
private final LocalDateTime timestamp;
private final String accountNumber;
private final String transactionType;
private final double amount;
private final String description;

/**
* Creates a new audit entry.
*
* @param accountNumber The account number involved in the transaction.
* @param transactionType The type of transaction (DEPOSIT, WITHDRAWAL, etc.).
* @param amount The transaction amount.
* @param description Additional details about the transaction.
*/
public AuditEntry(
String accountNumber, String transactionType, double amount, String description) {
this.timestamp = LocalDateTime.now();
this.accountNumber = accountNumber;
this.transactionType = transactionType;
this.amount = amount;
this.description = description;
}

public LocalDateTime getTimestamp() {
return timestamp;
}

public String getAccountNumber() {
return accountNumber;
}

public String getTransactionType() {
return transactionType;
}

public double getAmount() {
return amount;
}

public String getDescription() {
return description;
}

@Override
public String toString() {
return String.format(
"[%s] %s - Account: %s, Amount: $%.2f - %s",
timestamp, transactionType, accountNumber, amount, description);
}
}

private final List<AuditEntry> entries = new ArrayList<>();

/**
* Logs a transaction.
*
* @param accountNumber The account number.
* @param transactionType The transaction type.
* @param amount The amount.
* @param description The description.
*/
public void logTransaction(
String accountNumber, String transactionType, double amount, String description) {
entries.add(new AuditEntry(accountNumber, transactionType, amount, description));
}

/**
* Gets all audit entries.
*
* @return List of all audit entries.
*/
public List<AuditEntry> getAuditEntries() {
return new ArrayList<>(entries); // Return copy for immutability
}

/**
* Gets audit entries for a specific account.
*
* @param accountNumber The account number to filter by.
* @return List of audit entries for the account.
*/
public List<AuditEntry> getAuditEntriesForAccount(String accountNumber) {
return entries.stream()
.filter(entry -> entry.getAccountNumber().equals(accountNumber))
.toList();
}
}
Loading
Loading