diff --git a/README.md b/README.md
index d20aaf9..7071577 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[](https://classroom.github.com/a/339Lr3BJ)
### How the tests work (and Docker requirement)
This project ships with an end‑to‑end CLI integration test suite that uses Testcontainers to spin up a temporary MySQL database.
diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
new file mode 100644
index 0000000..0c555fe
--- /dev/null
+++ b/dependency-reduced-pom.xml
@@ -0,0 +1,117 @@
+
+
+ 4.0.0
+ com.example
+ jdbc
+ 1.0-SNAPSHOT
+
+
+
+ maven-compiler-plugin
+ 3.13.0
+
+ ${maven.compiler.release}
+
+
+
+ maven-surefire-plugin
+ 3.2.5
+
+
+ maven-failsafe-plugin
+ 3.5.4
+
+
+ integration-test
+
+ integration-test
+ verify
+
+
+
+
+
+ maven-shade-plugin
+ 3.5.0
+
+
+ package
+
+ shade
+
+
+
+
+ com.example.Main
+
+
+
+
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 6.0.1
+ test
+
+
+ junit-jupiter-api
+ org.junit.jupiter
+
+
+ junit-jupiter-params
+ org.junit.jupiter
+
+
+ junit-jupiter-engine
+ org.junit.jupiter
+
+
+
+
+ org.assertj
+ assertj-core
+ 3.27.6
+ test
+
+
+ byte-buddy
+ net.bytebuddy
+
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.20.0
+ test
+
+
+ mockito-core
+ org.mockito
+
+
+ junit-jupiter-api
+ org.junit.jupiter
+
+
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.21.3
+ test
+
+
+
+ 25
+ UTF-8
+ 5.20.0
+ 6.0.1
+ 3.27.6
+
+
diff --git a/src/main/java/com/example/ConfigUtils.java b/src/main/java/com/example/ConfigUtils.java
new file mode 100644
index 0000000..cde3447
--- /dev/null
+++ b/src/main/java/com/example/ConfigUtils.java
@@ -0,0 +1,37 @@
+package com.example;
+
+import java.util.Arrays;
+
+/**
+ * Utility class for reading configuration and environment variables.
+ */
+public class ConfigUtils {
+
+ /**
+ * Determines if the application should run in development mode.
+ * Checks system property "devMode", environment variable "DEV_MODE",
+ * and command-line argument "--dev".
+ *
+ * @param args command-line arguments
+ * @return true if dev mode is enabled, false otherwise
+ */
+ public static boolean isDevMode(String[] args) {
+ if (Boolean.getBoolean("devMode")) return true;
+ if ("true".equalsIgnoreCase(System.getenv("DEV_MODE"))) return true;
+ return Arrays.asList(args).contains("--dev");
+ }
+
+ /**
+ * Resolves a configuration value from system properties or environment variables.
+ * Returns null if neither is set.
+ *
+ * @param propertyKey system property key
+ * @param envKey environment variable key
+ * @return trimmed configuration value or null if not set
+ */
+ public static String resolveConfig(String propertyKey, String envKey) {
+ String v = System.getProperty(propertyKey);
+ if (v == null || v.trim().isEmpty()) v = System.getenv(envKey);
+ return (v == null || v.trim().isEmpty()) ? null : v.trim();
+ }
+}
diff --git a/src/main/java/com/example/DevDatabaseInitializer.java b/src/main/java/com/example/DevDatabaseInitializer.java
index e8a45fe..b61fc2c 100644
--- a/src/main/java/com/example/DevDatabaseInitializer.java
+++ b/src/main/java/com/example/DevDatabaseInitializer.java
@@ -3,6 +3,10 @@
import org.testcontainers.containers.MySQLContainer;
+/**
+ * Initializes a MySQL development database using Testcontainers.
+ * Sets system properties for JDBC URL, username, and password after startup.
+ */
public class DevDatabaseInitializer {
private static MySQLContainer> mysql;
@@ -21,4 +25,4 @@ public static void start() {
System.setProperty("APP_DB_PASS", mysql.getPassword());
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java
index 6dc6fbd..1b929c3 100644
--- a/src/main/java/com/example/Main.java
+++ b/src/main/java/com/example/Main.java
@@ -1,62 +1,56 @@
package com.example;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.Arrays;
-
+import com.example.cli.*;
+import com.example.repository.*;
+import com.example.service.*;
+
+/**
+ * Entry point for the Moon Mission application.
+ *
+ *
+ * Initializes the development database if dev mode is enabled, sets up repositories,
+ * services, and CLI components, handles user login, and shows the main menu.
+ *
+ */
public class Main {
- static void main(String[] args) {
- if (isDevMode(args)) {
+ /**
+ * Starts the application.
+ *
+ * @param args Command-line arguments. Supports "--dev" to enable dev mode.
+ */
+ public static void main(String[] args) {
+ if (ConfigUtils.isDevMode(args)) {
DevDatabaseInitializer.start();
}
- new Main().run();
- }
- public void run() {
- // Resolve DB settings with precedence: System properties -> Environment variables
- String jdbcUrl = resolveConfig("APP_JDBC_URL", "APP_JDBC_URL");
- String dbUser = resolveConfig("APP_DB_USER", "APP_DB_USER");
- String dbPass = resolveConfig("APP_DB_PASS", "APP_DB_PASS");
+ String jdbcUrl = ConfigUtils.resolveConfig("APP_JDBC_URL", "APP_JDBC_URL");
+ String dbUser = ConfigUtils.resolveConfig("APP_DB_USER", "APP_DB_USER");
+ String dbPass = ConfigUtils.resolveConfig("APP_DB_PASS", "APP_DB_PASS");
if (jdbcUrl == null || dbUser == null || dbPass == null) {
- throw new IllegalStateException(
- "Missing DB configuration. Provide APP_JDBC_URL, APP_DB_USER, APP_DB_PASS " +
- "as system properties (-Dkey=value) or environment variables.");
+ throw new IllegalStateException("Missing DB configuration.");
}
- try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) {
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
- //Todo: Starting point for your code
- }
+ SimpleDriverManagerDataSource dataSource = new SimpleDriverManagerDataSource(jdbcUrl, dbUser, dbPass);
+ boolean devMode = ConfigUtils.isDevMode(args);
- /**
- * Determines if the application is running in development mode based on system properties,
- * environment variables, or command-line arguments.
- *
- * @param args an array of command-line arguments
- * @return {@code true} if the application is in development mode; {@code false} otherwise
- */
- private static boolean isDevMode(String[] args) {
- if (Boolean.getBoolean("devMode")) //Add VM option -DdevMode=true
- return true;
- if ("true".equalsIgnoreCase(System.getenv("DEV_MODE"))) //Environment variable DEV_MODE=true
- return true;
- return Arrays.asList(args).contains("--dev"); //Argument --dev
- }
+ AccountRepositoryJdbc accountRepo = new AccountRepositoryJdbc(dataSource, devMode);
+ MoonMissionRepositoryJdbc missionRepo = new MoonMissionRepositoryJdbc(dataSource, devMode);
- /**
- * Reads configuration with precedence: Java system property first, then environment variable.
- * Returns trimmed value or null if neither source provides a non-empty value.
- */
- private static String resolveConfig(String propertyKey, String envKey) {
- String v = System.getProperty(propertyKey);
- if (v == null || v.trim().isEmpty()) {
- v = System.getenv(envKey);
+ AccountService accountService = new AccountService(accountRepo);
+ MoonMissionService missionService = new MoonMissionService(missionRepo);
+
+ InputReader input = new InputReader();
+
+ AccountCLI accountCLI = new AccountCLI(accountService, input);
+ MoonMissionCLI missionCLI = new MoonMissionCLI(missionService, input);
+ MenuCLI menu = new MenuCLI(accountCLI, missionCLI, input);
+
+ LoginManager loginManager = new LoginManager(accountService, input);
+
+ if (loginManager.login()) {
+ menu.showMainMenu();
}
- return (v == null || v.trim().isEmpty()) ? null : v.trim();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/SimpleDriverManagerDataSource.java b/src/main/java/com/example/SimpleDriverManagerDataSource.java
new file mode 100644
index 0000000..2109297
--- /dev/null
+++ b/src/main/java/com/example/SimpleDriverManagerDataSource.java
@@ -0,0 +1,48 @@
+package com.example;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+/**
+ * Simple DataSource implementation using DriverManager.
+ *
+ *
+ * Provides basic JDBC connections using a URL, username, and password.
+ * Only getConnection methods are supported; other DataSource features throw
+ * UnsupportedOperationException.
+ *
+ */
+public class SimpleDriverManagerDataSource implements DataSource {
+ private final String url;
+ private final String username;
+ private final String password;
+
+ /**
+ * Creates a new DataSource with the given JDBC parameters.
+ *
+ * @param url the JDBC URL
+ * @param username the database username
+ * @param password the database password
+ */
+ public SimpleDriverManagerDataSource(String url, String username, String password) {
+ this.url = url;
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException { return DriverManager.getConnection(url, username, password); }
+
+ @Override public T unwrap(Class iface) { throw new UnsupportedOperationException(); }
+ @Override public boolean isWrapperFor(Class> iface) { return false; }
+ @Override public java.io.PrintWriter getLogWriter() { throw new UnsupportedOperationException(); }
+ @Override public void setLogWriter(java.io.PrintWriter out) { throw new UnsupportedOperationException(); }
+ @Override public void setLoginTimeout(int seconds) { throw new UnsupportedOperationException(); }
+ @Override public int getLoginTimeout() { return 0; }
+ @Override public java.util.logging.Logger getParentLogger() { throw new UnsupportedOperationException(); }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/cli/AccountCLI.java b/src/main/java/com/example/cli/AccountCLI.java
new file mode 100644
index 0000000..bdc1311
--- /dev/null
+++ b/src/main/java/com/example/cli/AccountCLI.java
@@ -0,0 +1,117 @@
+package com.example.cli;
+
+import com.example.model.Account;
+import com.example.service.AccountService;
+import com.example.repository.RepositoryException;
+
+import java.util.List;
+
+/**
+ * CLI for managing accounts.
+ * Handles creating, updating, and deleting accounts via AccountService.
+ */
+public class AccountCLI implements ExitMenuHandler {
+
+ private final AccountService service;
+ private final InputReader input;
+
+ public AccountCLI(AccountService service, InputReader input) {
+ this.service = service;
+ this.input = input;
+ }
+
+ /** Prompts the user and creates a new account. */
+ public void createAccount() {
+ try {
+ var first = input.readName("First name");
+ if (handleExitOrMenu(first.result())) return;
+
+ var last = input.readName("Last name");
+ if (handleExitOrMenu(last.result())) return;
+
+ var ssn = input.readSSN("SSN");
+ if (handleExitOrMenu(ssn.result())) return;
+
+ var pass = input.readPassword("Password");
+ if (handleExitOrMenu(pass.result())) return;
+
+ long id = service.createAccount(first.value(), last.value(), ssn.value(), pass.value());
+ System.out.println("\n✅ Account created with ID: " + id + " ✅\n");
+
+ } catch (RepositoryException e) {
+ System.out.println("❌ Error creating account: " + e.getMessage());
+ }
+ }
+
+ /** Updates the password of an existing account after selecting by user ID. */
+ public void updatePassword() {
+ try {
+ List accounts = service.listAccounts();
+ if (accounts.isEmpty()) {
+ System.out.println("❌ No accounts found ❌");
+ return;
+ }
+
+ System.out.println("\n📋 Existing accounts:");
+ accounts.forEach(System.out::println);
+
+ var idWrapper = input.readValidUserId("User ID");
+ if (handleExitOrMenu(idWrapper.result())) return;
+
+ if (service.getById(idWrapper.value()).isEmpty()) {
+ System.out.println("❌ Account with this ID does not exist ❌");
+ return;
+ }
+
+ var passWrapper = input.readPassword("New Password");
+ if (handleExitOrMenu(passWrapper.result())) return;
+
+ service.updatePassword(idWrapper.value(), passWrapper.value());
+ System.out.println("\n✅ Password updated ✅\n");
+
+ } catch (RepositoryException e) {
+ System.out.println("❌ Error updating password: " + e.getMessage());
+ }
+ }
+
+ /** Deletes an account after confirming with the user. */
+ public void deleteAccount() {
+ try {
+ List accounts = service.listAccounts();
+ if (accounts.isEmpty()) {
+ System.out.println("❌ No accounts found ❌");
+ return;
+ }
+
+ System.out.println("\n📋 Existing accounts:");
+ accounts.forEach(System.out::println);
+
+ var idWrapper = input.readValidUserId("User ID");
+ if (handleExitOrMenu(idWrapper.result())) return;
+
+ if (service.getById(idWrapper.value()).isEmpty()) {
+ System.out.println("❌ Account with this ID does not exist ❌");
+ return;
+ }
+
+ while (true) {
+ var confirmWrapper = input.readString("Are you sure you want to delete this account? (yes/no)");
+ if (handleExitOrMenu(confirmWrapper.result())) return;
+
+ String confirm = confirmWrapper.value();
+ if (confirm.equalsIgnoreCase("yes") || confirm.equalsIgnoreCase("y")) {
+ service.deleteAccount(idWrapper.value());
+ System.out.println("\n✅ Account deleted ✅\n");
+ break;
+ } else if (confirm.equalsIgnoreCase("no") || confirm.equalsIgnoreCase("n")) {
+ System.out.println("❌ Account deletion cancelled ❌\n");
+ break;
+ } else {
+ System.out.println("❌ Invalid input, type yes, no, or menu ❌");
+ }
+ }
+ } catch (RepositoryException e) {
+ System.out.println("❌ Error deleting account: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/cli/ExitMenuHandler.java b/src/main/java/com/example/cli/ExitMenuHandler.java
new file mode 100644
index 0000000..235126c
--- /dev/null
+++ b/src/main/java/com/example/cli/ExitMenuHandler.java
@@ -0,0 +1,22 @@
+package com.example.cli;
+
+/**
+ * Provides a default method to handle 'exit' or 'menu' commands from the user.
+ * Classes implementing this interface can easily check if the user wants to exit or return to the menu.
+ */
+public interface ExitMenuHandler {
+
+ /**
+ * Handles input results indicating exit or menu commands.
+ *
+ * @param result the result from InputReader (CONTINUE, EXIT, MENU)
+ * @return true if the user wants to exit, false if continue or menu
+ */
+ default boolean handleExitOrMenu(InputReader.InputResult result) {
+ if (result == InputReader.InputResult.EXIT) {
+ System.out.println("Exiting...");
+ return true;
+ }
+ return result == InputReader.InputResult.MENU;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/cli/InputReader.java b/src/main/java/com/example/cli/InputReader.java
new file mode 100644
index 0000000..e3327da
--- /dev/null
+++ b/src/main/java/com/example/cli/InputReader.java
@@ -0,0 +1,109 @@
+package com.example.cli;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * Handles user input from the console with validation and special commands.
+ * Supports reading strings, integers, user IDs, names, SSNs, and passwords.
+ */
+public class InputReader {
+
+ private final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+
+ /**
+ * Represents the result of reading input.
+ * CONTINUE = normal input, MENU = user wants to go back, EXIT = user wants to exit.
+ */
+ public enum InputResult { CONTINUE, MENU, EXIT }
+
+ /**
+ * Wraps a value read from the user along with the input result status.
+ *
+ * @param the type of the input value
+ * @param value the actual input value
+ * @param result the input result (CONTINUE, MENU, EXIT)
+ */
+ public record InputWrapper(T value, InputResult result) {}
+
+ // Reads a line from the console
+ private String readLine() {
+ try {
+ String line = reader.readLine();
+ return line != null ? line.trim() : "";
+ } catch (Exception e) {
+ throw new RuntimeException("Error reading input", e);
+ }
+ }
+
+ // Checks if input is '0' (exit) or 'menu'
+ private InputResult checkExitOrMenu(String input) {
+ if (input.equals("0")) return InputResult.EXIT;
+ if (input.equalsIgnoreCase("menu")) return InputResult.MENU;
+ return InputResult.CONTINUE;
+ }
+
+ /** Reads a string from the user with exit/menu handling. */
+ public InputWrapper readString(String label) {
+ System.out.print(label + ": ");
+ System.out.flush();
+ String input = readLine();
+ InputResult result = checkExitOrMenu(input);
+ return new InputWrapper<>(input, result);
+ }
+
+ /** Reads an integer from the user with validation and exit/menu handling. */
+ public InputWrapper readInt(String label) {
+ while (true) {
+ InputWrapper wrapper = readString(label);
+ if (wrapper.result() != InputResult.CONTINUE) return new InputWrapper<>(0, wrapper.result());
+
+ try {
+ return new InputWrapper<>(Integer.parseInt(wrapper.value()), InputResult.CONTINUE);
+ } catch (NumberFormatException e) {
+ System.out.println("❌ Please enter a valid number ❌");
+ }
+ }
+ }
+
+ /** Reads a valid user ID (long) from the user with validation and exit/menu handling. */
+ public InputWrapper readValidUserId(String label) {
+ while (true) {
+ InputWrapper wrapper = readString(label);
+ if (wrapper.result() != InputResult.CONTINUE) return new InputWrapper<>(0L, wrapper.result());
+
+ try {
+ return new InputWrapper<>(Long.parseLong(wrapper.value()), InputResult.CONTINUE);
+ } catch (NumberFormatException e) {
+ System.out.println("❌ Please enter a valid number ❌");
+ }
+ }
+ }
+
+ /** Reads a valid name from the user (capitalized, at least 3 letters). */
+ public InputWrapper readName(String label) {
+ while (true) {
+ InputWrapper wrapper = readString(label);
+ if (wrapper.result() != InputResult.CONTINUE) return wrapper;
+
+ if (wrapper.value().matches("[A-Z][a-zA-Z]{2,}")) return wrapper;
+ System.out.println("❌ Must start with a capital letter and be at least 3 letters ❌");
+ }
+ }
+
+ /** Reads a valid Swedish SSN from the user (######-####). */
+ public InputWrapper readSSN(String label) {
+ while (true) {
+ InputWrapper wrapper = readString(label);
+ if (wrapper.result() != InputResult.CONTINUE) return wrapper;
+
+ if (wrapper.value().matches("\\d{6}-\\d{4}")) return wrapper;
+ System.out.println("❌ SSN must match format ######-#### ❌");
+ }
+ }
+
+ /** Reads a password from the user. */
+ public InputWrapper readPassword(String label) {
+ return readString(label);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/cli/LoginManager.java b/src/main/java/com/example/cli/LoginManager.java
new file mode 100644
index 0000000..7657055
--- /dev/null
+++ b/src/main/java/com/example/cli/LoginManager.java
@@ -0,0 +1,66 @@
+package com.example.cli;
+
+import com.example.service.AccountService;
+import com.example.repository.RepositoryException;
+
+/**
+ * Handles user login attempts, including input reading, validation, and retry limits.
+ */
+public class LoginManager implements ExitMenuHandler {
+
+ private final AccountService service;
+ private final int maxAttempts;
+ private final InputReader input;
+
+ /**
+ * Creates a LoginManager with default max attempts (5).
+ */
+ public LoginManager(AccountService service, InputReader input) {
+ this(service, input, 5);
+ }
+
+ /**
+ * Creates a LoginManager with a specified maximum number of login attempts.
+ *
+ * @param maxAttempts maximum allowed attempts before login fails
+ */
+ public LoginManager(AccountService service, InputReader input, int maxAttempts) {
+ this.service = service;
+ this.input = input;
+ this.maxAttempts = maxAttempts;
+ }
+
+ /**
+ * Performs the login process, asking for username and password.
+ * Returns true if login succeeds, false if attempts are exhausted or user exits.
+ */
+ public boolean login() {
+ System.out.println("Type 0 to exit anytime.");
+
+ int attempts = 0;
+ while (attempts < maxAttempts) {
+ var usernameWrapper = input.readString("Username");
+ if (handleExitOrMenu(usernameWrapper.result())) return false;
+
+ var passwordWrapper = input.readPassword("Password");
+ if (handleExitOrMenu(passwordWrapper.result())) return false;
+
+ try {
+ if (service.validateLogin(usernameWrapper.value(), passwordWrapper.value())) {
+ System.out.println("\n✅ Login successful! Welcome, " + usernameWrapper.value() + "!\n");
+ return true;
+ } else {
+ System.out.println("❌ Invalid username or password ❌");
+ }
+ } catch (RepositoryException e) {
+ System.out.println("❌ Error validating login: " + e.getMessage());
+ }
+
+ attempts++;
+ if (attempts < maxAttempts) {
+ System.out.println("Attempts left: " + (maxAttempts - attempts));
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/example/cli/MenuCLI.java b/src/main/java/com/example/cli/MenuCLI.java
new file mode 100644
index 0000000..db94c82
--- /dev/null
+++ b/src/main/java/com/example/cli/MenuCLI.java
@@ -0,0 +1,67 @@
+package com.example.cli;
+
+/**
+ * Handles the main menu display and routes user selections
+ * to the appropriate CLI handlers for accounts or moon missions.
+ */
+public class MenuCLI {
+
+ private final AccountCLI accountCLI;
+ private final MoonMissionCLI missionCLI;
+ private final InputReader input;
+
+ /**
+ * Creates a MenuCLI with the required CLI handlers and input reader.
+ *
+ * @param accountCLI CLI handler for account-related actions
+ * @param missionCLI CLI handler for moon mission-related actions
+ * @param input input reader for user interaction
+ */
+ public MenuCLI(AccountCLI accountCLI, MoonMissionCLI missionCLI, InputReader input) {
+ this.accountCLI = accountCLI;
+ this.missionCLI = missionCLI;
+ this.input = input;
+ }
+
+ /**
+ * Displays the main menu, handles user input, and routes commands
+ * to the appropriate CLI methods until the user exits.
+ */
+ public void showMainMenu() {
+ while (true) {
+ printHeader();
+ var choiceWrapper = input.readInt("Choose option");
+
+ if (choiceWrapper.result() == InputReader.InputResult.EXIT) {
+ System.out.println("Exiting...");
+ return;
+ }
+
+ if (choiceWrapper.result() == InputReader.InputResult.MENU) continue;
+
+ switch (choiceWrapper.value()) {
+ case 1 -> missionCLI.listMissions();
+ case 2 -> missionCLI.getMissionById();
+ case 3 -> missionCLI.countMissionsByYear();
+ case 4 -> accountCLI.createAccount();
+ case 5 -> accountCLI.updatePassword();
+ case 6 -> accountCLI.deleteAccount();
+ default -> System.out.println("Invalid option, try again.");
+ }
+ }
+ }
+
+ /** Prints the menu header and available options. */
+ private void printHeader() {
+ System.out.println("\n 🌕 MOON MISSION HUB 🌕 ");
+ System.out.println("----------------------------------------");
+ System.out.println("Type 0 to exit from this menu, or 'menu' to go back to the main menu.");
+ System.out.println(" 1) List moon missions");
+ System.out.println(" 2) Get mission by ID");
+ System.out.println(" 3) Count missions by year");
+ System.out.println(" 4) Create account");
+ System.out.println(" 5) Update password");
+ System.out.println(" 6) Delete account");
+ System.out.println(" 0) Exit\n");
+ }
+}
diff --git a/src/main/java/com/example/cli/MoonMissionCLI.java b/src/main/java/com/example/cli/MoonMissionCLI.java
new file mode 100644
index 0000000..4559324
--- /dev/null
+++ b/src/main/java/com/example/cli/MoonMissionCLI.java
@@ -0,0 +1,97 @@
+package com.example.cli;
+
+import com.example.model.MoonMission;
+import com.example.service.MoonMissionService;
+import com.example.repository.RepositoryException;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * CLI handler for moon mission operations.
+ * Provides methods for listing, fetching by ID, and counting missions by year.
+ */
+public class MoonMissionCLI implements ExitMenuHandler {
+
+ private final MoonMissionService service;
+ private final InputReader input;
+
+ /**
+ * Creates a MoonMissionCLI with the provided service and input reader.
+ *
+ * @param service service handling moon mission data
+ * @param input input reader for user interaction
+ */
+ public MoonMissionCLI(MoonMissionService service, InputReader input) {
+ this.service = service;
+ this.input = input;
+ }
+
+ /** Lists all moon missions by spacecraft name. */
+ public void listMissions() {
+ try {
+ List missions = service.listMissions();
+ System.out.println("\n-- All Moon Missions --");
+ missions.forEach(m -> System.out.println(m.spacecraft()));
+ System.out.println("----------------------\n");
+ } catch (RepositoryException e) {
+ System.out.println("❌ Error listing missions: " + e.getMessage());
+ }
+ }
+
+ /** Fetches and displays a mission by its ID. */
+ public void getMissionById() {
+ var idWrapper = input.readInt("Mission ID");
+ if (handleExitOrMenu(idWrapper.result())) return;
+
+ try {
+ Optional mission = service.getMissionById(idWrapper.value());
+ mission.ifPresentOrElse(
+ m -> {
+ System.out.println("\n-- Mission Details --");
+ System.out.println(m);
+ System.out.println("-------------------\n");
+ },
+ () -> System.out.println("❌ No mission with that ID ❌")
+ );
+ } catch (RepositoryException e) {
+ System.out.println("❌ Error fetching mission: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Counts missions for a given year, prints total and detailed summaries
+ * of missions sorted by launch date (most recent first).
+ */
+ public void countMissionsByYear() {
+ var yearWrapper = input.readInt("Year");
+ if (handleExitOrMenu(yearWrapper.result())) return;
+
+ int year = yearWrapper.value();
+
+ try {
+ int count = service.countMissionsByYear(year);
+
+ System.out.println("\n---- Missions for year " + year + " ----");
+ System.out.println("Total missions: " + count);
+
+ List missions = service.listMissions();
+ missions.stream()
+ .filter(m -> m.launchDate().toLocalDate().getYear() == year)
+ .sorted(Comparator.comparing(MoonMission::launchDate).reversed())
+ .forEach(this::printMissionSummary);
+
+ System.out.println("----------------------------------------\n");
+
+ } catch (RepositoryException e) {
+ System.out.println("❌ Error counting/listing missions: " + e.getMessage());
+ }
+ }
+
+ /** Prints a short summary for a single moon mission. */
+ private void printMissionSummary(MoonMission m) {
+ System.out.printf("Spacecraft: %s | Launch Date: %s | Rocket: %s | Operator: %s%n",
+ m.spacecraft(), m.launchDate(), m.carrierRocket(), m.operator());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/model/Account.java b/src/main/java/com/example/model/Account.java
new file mode 100644
index 0000000..dd40376
--- /dev/null
+++ b/src/main/java/com/example/model/Account.java
@@ -0,0 +1,35 @@
+package com.example.model;
+
+/**
+ * Represents a user account in the system.
+ *
+ * @param userId unique identifier for the account
+ * @param firstName first name of the account holder
+ * @param lastName last name of the account holder
+ * @param ssn social security number
+ * @param password account password
+ * @param name full display name of the account holder
+ */
+public record Account(
+ long userId,
+ String firstName,
+ String lastName,
+ String ssn,
+ String password,
+ String name
+) {
+
+ /**
+ * Returns a readable string representation of the account,
+ * excluding the password for security reasons.
+ */
+ @Override
+ public String toString() {
+ return "Account: " +
+ "ID: " + userId +
+ ", First: " + firstName +
+ ", Last: " + lastName +
+ ", SSN: " + ssn +
+ ", Name: " + name;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/model/MoonMission.java b/src/main/java/com/example/model/MoonMission.java
new file mode 100644
index 0000000..5eafb84
--- /dev/null
+++ b/src/main/java/com/example/model/MoonMission.java
@@ -0,0 +1,40 @@
+package com.example.model;
+
+import java.sql.Date;
+
+/**
+ * Represents a moon mission with its main details.
+ *
+ * @param missionId unique identifier for the mission
+ * @param spacecraft name of the spacecraft
+ * @param launchDate launch date of the mission
+ * @param carrierRocket rocket used to carry the spacecraft
+ * @param operator organization responsible for the mission
+ * @param missionType type of mission (e.g., manned, unmanned)
+ * @param outcome result or outcome of the mission
+ */
+public record MoonMission(
+ int missionId,
+ String spacecraft,
+ Date launchDate,
+ String carrierRocket,
+ String operator,
+ String missionType,
+ String outcome
+) {
+
+ /**
+ * Returns a readable string representation of the moon mission.
+ */
+ @Override
+ public String toString() {
+ return "MoonMission: " +
+ "missionId: " + missionId +
+ ", spacecraft: " + spacecraft +
+ ", launchDate: " + launchDate +
+ ", carrierRocket: " + carrierRocket +
+ ", operator: " + operator +
+ ", missionType: " + missionType +
+ ", outcome: " + outcome;
+ }
+}
diff --git a/src/main/java/com/example/repository/AccountRepository.java b/src/main/java/com/example/repository/AccountRepository.java
new file mode 100644
index 0000000..fc5e062
--- /dev/null
+++ b/src/main/java/com/example/repository/AccountRepository.java
@@ -0,0 +1,19 @@
+package com.example.repository;
+
+import com.example.model.Account;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Interface defining the operations for managing accounts in the repository.
+ */
+public interface AccountRepository {
+ boolean validateLogin(String username, String password);
+ long createAccount(String firstName, String lastName, String ssn, String password);
+ void updatePassword(long userId, String newPassword);
+ void deleteAccount(long userId);
+ List listAccounts();
+ Optional getById(long userId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/repository/AccountRepositoryJdbc.java b/src/main/java/com/example/repository/AccountRepositoryJdbc.java
new file mode 100644
index 0000000..ea0a268
--- /dev/null
+++ b/src/main/java/com/example/repository/AccountRepositoryJdbc.java
@@ -0,0 +1,117 @@
+package com.example.repository;
+
+import com.example.model.Account;
+
+import javax.sql.DataSource;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * JDBC implementation of the {@link AccountRepository}.
+ * Handles database operations for Account objects.
+ */
+public class AccountRepositoryJdbc extends BaseRepository implements AccountRepository {
+
+ /**
+ * Constructs the repository with a given DataSource.
+ *
+ * @param dataSource the database source
+ * @param devMode if true, enables debug logging
+ */
+ public AccountRepositoryJdbc(DataSource dataSource, boolean devMode) {
+ super(dataSource, devMode);
+ }
+
+ /**
+ * Maps a ResultSet row to an Account object.
+ *
+ * @param rs the ResultSet to map
+ * @return the Account object
+ */
+ @Override
+ protected Account map(java.sql.ResultSet rs) throws java.sql.SQLException {
+ return new Account(
+ rs.getLong("user_id"),
+ rs.getString("first_name"),
+ rs.getString("last_name"),
+ rs.getString("ssn"),
+ rs.getString("password"),
+ rs.getString("name")
+ );
+ }
+
+ /**
+ * Retrieves a list of all accounts in the database.
+ *
+ * @return a {@link List} of {@link Account} objects
+ */
+ @Override
+ public List listAccounts() {
+ return queryList("SELECT * FROM account");
+ }
+
+ /**
+ * Retrieves an account by its unique user ID.
+ *
+ * @param userId the ID of the account to retrieve
+ * @return an {@link Optional} containing the {@link Account} if found, otherwise empty
+ */
+ @Override
+ public Optional getById(long userId) {
+ return querySingle("SELECT * FROM account WHERE user_id=?", userId);
+ }
+
+ /**
+ * Validates login credentials by checking if an account with the given
+ * username and password exists in the database.
+ *
+ * @param username the account's username
+ * @param password the account's password
+ * @return true if credentials match an account, false otherwise
+ */
+ @Override
+ public boolean validateLogin(String username, String password) {
+ return executeQuery(
+ "SELECT COUNT(*) FROM account WHERE name=? AND password=?",
+ rs -> { rs.next(); return rs.getInt(1) > 0; },
+ username, password
+ );
+ }
+
+ /**
+ * Creates a new account with the given personal information.
+ *
+ * @param firstName the first name of the user
+ * @param lastName the last name of the user
+ * @param ssn the social security number of the user
+ * @param password the password for the account
+ * @return the generated user ID of the newly created account
+ */
+ @Override
+ public long createAccount(String firstName, String lastName, String ssn, String password) {
+ String name = firstName.substring(0, 3) + lastName.substring(0, 3);
+ String sql = "INSERT INTO account (first_name, last_name, ssn, password, name) VALUES (?,?,?,?,?)";
+ return executeUpdateReturnId(sql, firstName, lastName, ssn, password, name);
+ }
+
+ /**
+ * Updates the password for an existing account.
+ *
+ * @param userId the ID of the account to update
+ * @param newPassword the new password to set
+ */
+ @Override
+ public void updatePassword(long userId, String newPassword) {
+ executeUpdate("UPDATE account SET password=? WHERE user_id=?", newPassword, userId);
+ }
+
+ /**
+ * Deletes an account from the database.
+ *
+ * @param userId the ID of the account to delete
+ */
+ @Override
+ public void deleteAccount(long userId) {
+ executeUpdate("DELETE FROM account WHERE user_id=?", userId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/repository/BaseRepository.java b/src/main/java/com/example/repository/BaseRepository.java
new file mode 100644
index 0000000..c6bbeb7
--- /dev/null
+++ b/src/main/java/com/example/repository/BaseRepository.java
@@ -0,0 +1,140 @@
+package com.example.repository;
+
+import javax.sql.DataSource;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+
+/**
+ * Abstract base class for JDBC repositories.
+ * Provides common database operations such as query, update, and mapping.
+ *
+ * @param the type of entity this repository handles
+ */
+public abstract class BaseRepository {
+ protected final DataSource dataSource;
+ protected final boolean devMode;
+
+ /**
+ * Constructs the repository with a DataSource.
+ *
+ * @param dataSource the database source
+ * @param devMode enables debug logging if true
+ */
+ protected BaseRepository(DataSource dataSource, boolean devMode) {
+ this.dataSource = dataSource;
+ this.devMode = devMode;
+ if (devMode) {
+ System.out.println("[DEV] Repository " + this.getClass().getSimpleName() + " initialized");
+ }
+ }
+
+ /**
+ * Gets a database connection from the DataSource.
+ */
+ protected Connection getConnection() throws SQLException {
+ return dataSource.getConnection();
+ }
+
+ /**
+ * Logs messages if devMode is enabled.
+ */
+ protected void log(String msg) {
+ if (devMode) System.out.println("[DEV] " + msg);
+ }
+
+ /**
+ * Wraps database exceptions into a RepositoryException.
+ */
+ protected RepositoryException dbError(String action, Exception e) {
+ log("ERROR during: " + action + " -> " + e.getMessage());
+ return new RepositoryException("Database error during: " + action, e);
+ }
+
+ /**
+ * Executes a query and applies a handler function to the ResultSet.
+ */
+ protected R executeQuery(String sql, SQLFunction handler, Object... params) {
+ try (Connection conn = getConnection();
+ PreparedStatement stmt = conn.prepareStatement(sql)) {
+
+ for (int i = 0; i < params.length; i++) stmt.setObject(i + 1, params[i]);
+ try (ResultSet rs = stmt.executeQuery()) {
+ return handler.apply(rs);
+ }
+ } catch (Exception e) {
+ throw dbError("executeQuery: " + sql, e);
+ }
+ }
+
+ /**
+ * Executes an update/insert/delete SQL statement.
+ */
+ protected void executeUpdate(String sql, Object... params) {
+ try (Connection conn = getConnection();
+ PreparedStatement stmt = conn.prepareStatement(sql)) {
+
+ for (int i = 0; i < params.length; i++) stmt.setObject(i + 1, params[i]);
+ stmt.executeUpdate();
+ } catch (SQLException e) {
+ throw dbError("executeUpdate: " + sql, e);
+ }
+ }
+
+ /**
+ * Executes an insert and returns the generated key.
+ */
+ protected long executeUpdateReturnId(String sql, Object... params) {
+ try (Connection conn = getConnection();
+ PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+
+ for (int i = 0; i < params.length; i++) stmt.setObject(i + 1, params[i]);
+ stmt.executeUpdate();
+
+ try (ResultSet keys = stmt.getGeneratedKeys()) {
+ return keys.next() ? keys.getLong(1) : 0;
+ }
+ } catch (SQLException e) {
+ throw dbError("executeUpdateReturnId: " + sql, e);
+ }
+ }
+
+ /**
+ * Functional interface for processing ResultSets.
+ */
+ @FunctionalInterface
+ protected interface SQLFunction { T apply(R result) throws Exception; }
+
+ /**
+ * Maps a ResultSet row to an entity.
+ *
+ * @param rs the ResultSet row
+ * @return the mapped entity
+ */
+ protected abstract T map(ResultSet rs) throws SQLException;
+
+ /**
+ * Executes a query and returns a list of entities.
+ */
+ protected List queryList(String sql, Object... params) {
+ return executeQuery(sql, rs -> {
+ List list = new ArrayList<>();
+ while (rs.next()) {
+ list.add(map(rs));
+ }
+ return list;
+ }, params);
+ }
+
+ /**
+ * Executes a query and returns a single entity wrapped in Optional.
+ */
+ protected Optional querySingle(String sql, Object... params) {
+ return executeQuery(sql, rs -> {
+ if (rs.next()) return Optional.of(map(rs));
+ return Optional.empty();
+ }, params);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/repository/MoonMissionRepository.java b/src/main/java/com/example/repository/MoonMissionRepository.java
new file mode 100644
index 0000000..f5bf1b8
--- /dev/null
+++ b/src/main/java/com/example/repository/MoonMissionRepository.java
@@ -0,0 +1,15 @@
+package com.example.repository;
+
+import com.example.model.MoonMission;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Interface defining the operations for managing MoonMissions in the repository.
+ */
+public interface MoonMissionRepository {
+ List listMissions();
+ Optional getMissionById(int missionId);
+ int countMissionsByYear(int year);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/repository/MoonMissionRepositoryJdbc.java b/src/main/java/com/example/repository/MoonMissionRepositoryJdbc.java
new file mode 100644
index 0000000..845349a
--- /dev/null
+++ b/src/main/java/com/example/repository/MoonMissionRepositoryJdbc.java
@@ -0,0 +1,79 @@
+package com.example.repository;
+
+import com.example.model.MoonMission;
+
+import javax.sql.DataSource;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * JDBC implementation of MoonMissionRepository using a DataSource.
+ * Handles mapping of ResultSet to MoonMission objects and executing SQL queries.
+ */
+public class MoonMissionRepositoryJdbc extends BaseRepository implements MoonMissionRepository {
+
+ /**
+ * Creates a new repository with the given DataSource and devMode flag.
+ *
+ * @param dataSource the DataSource to use for database connections
+ * @param devMode if true, prints debug information
+ */
+ public MoonMissionRepositoryJdbc(DataSource dataSource, boolean devMode) {
+ super(dataSource, devMode);
+ }
+
+ /**
+ * Maps a ResultSet row to a MoonMission object.
+ *
+ * @param rs the ResultSet to map
+ * @return a MoonMission object
+ */
+ @Override
+ protected MoonMission map(java.sql.ResultSet rs) throws java.sql.SQLException {
+ return new MoonMission(
+ rs.getInt("mission_id"),
+ rs.getString("spacecraft"),
+ rs.getDate("launch_date"),
+ rs.getString("carrier_rocket"),
+ rs.getString("operator"),
+ rs.getString("mission_type"),
+ rs.getString("outcome")
+ );
+ }
+
+ /**
+ * Retrieves a list of all moon missions in the database.
+ *
+ * @return a {@link List} of {@link MoonMission} objects
+ */
+ @Override
+ public List listMissions() {
+ return queryList("SELECT * FROM moon_mission");
+ }
+
+ /**
+ * Retrieves a moon mission by its unique mission ID.
+ *
+ * @param missionId the ID of the mission to retrieve
+ * @return an {@link Optional} containing the {@link MoonMission} if found, otherwise empty
+ */
+ @Override
+ public Optional getMissionById(int missionId) {
+ return querySingle("SELECT * FROM moon_mission WHERE mission_id=?", missionId);
+ }
+
+ /**
+ * Counts the number of moon missions launched in a specific year.
+ *
+ * @param year the year to count missions for
+ * @return the number of missions launched in the given year
+ */
+ @Override
+ public int countMissionsByYear(int year) {
+ return executeQuery(
+ "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date)=?",
+ rs -> { rs.next(); return rs.getInt(1); },
+ year
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/repository/RepositoryException.java b/src/main/java/com/example/repository/RepositoryException.java
new file mode 100644
index 0000000..9cd416f
--- /dev/null
+++ b/src/main/java/com/example/repository/RepositoryException.java
@@ -0,0 +1,17 @@
+package com.example.repository;
+
+/**
+ * Custom unchecked exception for repository/database errors.
+ */
+public class RepositoryException extends RuntimeException {
+
+ /**
+ * Creates a new RepositoryException with a message and a cause.
+ *
+ * @param message descriptive error message
+ * @param cause the underlying exception that caused this error
+ */
+ public RepositoryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/service/AccountService.java b/src/main/java/com/example/service/AccountService.java
new file mode 100644
index 0000000..ba0d6a7
--- /dev/null
+++ b/src/main/java/com/example/service/AccountService.java
@@ -0,0 +1,117 @@
+package com.example.service;
+
+import com.example.model.Account;
+import com.example.repository.AccountRepository;
+import com.example.repository.RepositoryException;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Service layer for managing accounts. Wraps AccountRepository
+ * and handles exceptions by throwing RepositoryException.
+ */
+public class AccountService {
+ private final AccountRepository repo;
+
+ /**
+ * Constructs the service with a given repository.
+ *
+ * @param repo the {@link AccountRepository} to use for data access
+ */
+ public AccountService(AccountRepository repo) {
+ this.repo = repo;
+ }
+
+ /**
+ * Validates a user's login credentials.
+ *
+ * @param username the username to validate
+ * @param password the password to validate
+ * @return true if the credentials are valid, false otherwise
+ * @throws RepositoryException if an error occurs while accessing the repository
+ */
+ public boolean validateLogin(String username, String password) {
+ try {
+ return repo.validateLogin(username, password);
+ } catch (Exception e) {
+ throw new RepositoryException("Error validating login", e);
+ }
+ }
+
+ /**
+ * Creates a new account in the system.
+ *
+ * @param firstName the first name of the user
+ * @param lastName the last name of the user
+ * @param ssn the Swedish personal identity number (######-####)
+ * @param password the password for the account
+ * @return the generated user ID of the new account
+ * @throws RepositoryException if an error occurs while creating the account
+ */
+ public long createAccount(String firstName, String lastName, String ssn, String password) {
+ try {
+ return repo.createAccount(firstName, lastName, ssn, password);
+ } catch (Exception e) {
+ throw new RepositoryException("Error creating account", e);
+ }
+ }
+
+ /**
+ * Updates the password for an existing account.
+ *
+ * @param userId the ID of the account to update
+ * @param newPassword the new password to set
+ * @throws RepositoryException if an error occurs while updating the password
+ */
+ public void updatePassword(long userId, String newPassword) {
+ try {
+ repo.updatePassword(userId, newPassword);
+ } catch (Exception e) {
+ throw new RepositoryException("Error updating password", e);
+ }
+ }
+
+ /**
+ * Deletes an account by its user ID.
+ *
+ * @param userId the ID of the account to delete
+ * @throws RepositoryException if an error occurs while deleting the account
+ */
+ public void deleteAccount(long userId) {
+ try {
+ repo.deleteAccount(userId);
+ } catch (Exception e) {
+ throw new RepositoryException("Error deleting account", e);
+ }
+ }
+
+ /**
+ * Lists all accounts in the system.
+ *
+ * @return a {@link List} of {@link Account} objects
+ * @throws RepositoryException if an error occurs while retrieving accounts
+ */
+ public List listAccounts() {
+ try {
+ return repo.listAccounts();
+ } catch (Exception e) {
+ throw new RepositoryException("Error listing accounts", e);
+ }
+ }
+
+ /**
+ * Retrieves an account by its user ID.
+ *
+ * @param userId the ID of the account to retrieve
+ * @return an {@link Optional} containing the {@link Account} if found, otherwise empty
+ * @throws RepositoryException if an error occurs while fetching the account
+ */
+ public Optional getById(long userId) {
+ try {
+ return repo.getById(userId);
+ } catch (Exception e) {
+ throw new RepositoryException("Error fetching account by ID", e);
+ }
+ }
+}
diff --git a/src/main/java/com/example/service/MoonMissionService.java b/src/main/java/com/example/service/MoonMissionService.java
new file mode 100644
index 0000000..66538d4
--- /dev/null
+++ b/src/main/java/com/example/service/MoonMissionService.java
@@ -0,0 +1,69 @@
+package com.example.service;
+
+import com.example.model.MoonMission;
+import com.example.repository.MoonMissionRepository;
+import com.example.repository.RepositoryException;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Service layer for managing moon missions. Wraps MoonMissionRepository
+ * and handles exceptions by throwing RepositoryException.
+ */
+public class MoonMissionService {
+ private final MoonMissionRepository repo;
+
+ /**
+ * Constructs the service with a given repository.
+ *
+ * @param repo the {@link MoonMissionRepository} to use for data access
+ */
+ public MoonMissionService(MoonMissionRepository repo) {
+ this.repo = repo;
+ }
+
+ /**
+ * Lists all moon missions in the repository.
+ *
+ * @return a {@link List} of {@link MoonMission} objects
+ * @throws RepositoryException if an error occurs while retrieving missions
+ */
+ public List listMissions() {
+ try {
+ return repo.listMissions();
+ } catch (Exception e) {
+ throw new RepositoryException("Error listing missions", e);
+ }
+ }
+
+ /**
+ * Retrieves a moon mission by its ID.
+ *
+ * @param id the ID of the mission to fetch
+ * @return an {@link Optional} containing the {@link MoonMission} if found, otherwise empty
+ * @throws RepositoryException if an error occurs while fetching the mission
+ */
+ public Optional getMissionById(int id) {
+ try {
+ return repo.getMissionById(id);
+ } catch (Exception e) {
+ throw new RepositoryException("Error fetching mission by ID", e);
+ }
+ }
+
+ /**
+ * Counts the number of moon missions that occurred in a specific year.
+ *
+ * @param year the year to count missions for
+ * @return the number of missions in the given year
+ * @throws RepositoryException if an error occurs while counting missions
+ */
+ public int countMissionsByYear(int year) {
+ try {
+ return repo.countMissionsByYear(year);
+ } catch (Exception e) {
+ throw new RepositoryException("Error counting missions by year", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/com/example/CliAppIT.java b/src/test/java/com/example/CliAppIT.java
similarity index 99%
rename from src/test/com/example/CliAppIT.java
rename to src/test/java/com/example/CliAppIT.java
index 7d1eafb..984707d 100644
--- a/src/test/com/example/CliAppIT.java
+++ b/src/test/java/com/example/CliAppIT.java
@@ -179,6 +179,7 @@ void deleteAccount_thenRowIsGone_andPrintsConfirmation() throws Exception {
"MB=V4cbAqPz4vqmQ",
"6", // delete account (menu option 6 after reordering)
Long.toString(userId),// user_id
+ "yes", // confirm delete ("y" eller "yes")
"0" // exit
) + System.lineSeparator();