diff --git a/README.md b/README.md index d20aaf9..7071577 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](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/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 6dc6fbd..a65193f 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -1,36 +1,90 @@ package com.example; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; +import com.example.jdbc.JdbcAccountRepository; +import com.example.jdbc.JdbcMoonMissionRepository; + import java.util.Arrays; +import java.util.Scanner; public class Main { - static void main(String[] args) { + private final JdbcAccountRepository accountRepo; + private final JdbcMoonMissionRepository missionRepo; + private final Scanner sc; + + public Main(SimpleDriverManagerDataSource dataSource) { + this.accountRepo = new JdbcAccountRepository(dataSource); + this.missionRepo = new JdbcMoonMissionRepository(dataSource); + this.sc = new Scanner(System.in); + } + + public static void main(String[] args) { if (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"); - 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."); + SimpleDriverManagerDataSource ds = new SimpleDriverManagerDataSource(jdbcUrl, dbUser, dbPass); + + new Main(ds).run(); + } + + public void run() { + boolean authenticated = false; + String username = ""; + + // Loop until valid credentials + while (!authenticated) { + System.out.print("Enter your Username (or 0 to exit): "); + username = sc.nextLine(); + if ("0".equals(username)) { + System.out.println("Exiting program."); + return; + } + + System.out.print("Enter your password: "); + String password = sc.nextLine(); + + if (accountRepo.validateCredentials(username, password)) { + System.out.println("Welcome / Välkommen: " + username); + authenticated = true; + } else { + System.out.println("Invalid Username or Password. Try again or type 0 to exit."); + } } - try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) { - } catch (SQLException e) { - throw new RuntimeException(e); + // Once authenticated, show menu until user exits + int choice; + do { + getOptions(); + + // safer input handling + String choiceStr = sc.nextLine(); + try { + choice = Integer.parseInt(choiceStr); + } catch (NumberFormatException e) { + choice = -1; // invalid + } + + options(choice); + } while (choice != 0); + + System.out.println("Goodbye, " + username + "!"); + } + + /** + * 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); } - //Todo: Starting point for your code + return (v == null || v.trim().isEmpty()) ? null : v.trim(); } /** @@ -48,15 +102,111 @@ private static boolean isDevMode(String[] args) { return Arrays.asList(args).contains("--dev"); //Argument --dev } - /** - * 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); + public void getOptions() { + System.out.println( + """ + Select an option: + 1 - List moon missions (prints spacecraft names from `moon_mission`). + 2 - Get a moon mission by mission_id (prints details for that mission). + 3 - Count missions for a given year (prompts: year; prints the number of missions launched that year). + 4 - Create an account (prompts: first name, last name, ssn, password; prints confirmation). + 5 - Update an account password (prompts: user_id, new password; prints confirmation). + 6 - Delete an account (prompts: user_id; prints confirmation). + 0 - Exit. + By pressing either of the numbers listed. + """ + ); + } + + public void options(int choice) { + + switch (choice) { + + case 1: missionRepo. + listMissions(). + forEach(System.out::println); + break; + + case 2: + System.out.print("Enter a mission ID you want to search for: "); + String missionIdStr = sc.nextLine(); + int missionId; + try{ + missionId = Integer.parseInt(missionIdStr); + } catch (NumberFormatException e) { + System.out.println("Invalid mission ID"); + break; + } + + missionRepo.getMissionById(missionId).ifPresentOrElse( + mission -> System.out.printf( + "Mission ID: %d%nSpacecraft: %s%nLaunch Date: %s%nCarrier Rocket: %s%nOutcome: %s%nMission Type: %s%nOperator: %s%n", + mission.getMissionId(), + mission.getSpacecraft(), + mission.getLaunchDate(), + mission.getCarrierRocket(), + mission.getOutcome(), + mission.getMissionType(), + mission.getOperator() + ), + () -> System.out.println("No mission found.") + ); + break; + + case 3: + System.out.print("Enter the year you wish to see the number of missions: "); + int year = sc.nextInt(); + sc.nextLine(); + int numOfMissions = missionRepo.countMissionsByYear(year); + System.out.println("There were: " + numOfMissions + " year " + year); + break; + + case 4: + System.out.println("Enter your first name: "); + String firstName = sc.nextLine(); + System.out.println("Enter your last name: "); + String lastName = sc.nextLine(); + System.out.println("Enter your ssn (social security number): "); + String ssn = sc.nextLine(); + System.out.println("Enter your password: "); + String password = sc.nextLine(); + + if (accountRepo.createAccount(firstName, lastName, ssn, password)) { + System.out.println("Your account was successfuly created"); + } else { + System.out.println("Something went wrong during the creation of your account"); + } + break; + + case 5: + System.out.println("Enter the userId of the account you would like to update your password for: "); + int userId = sc.nextInt(); + sc.nextLine(); + System.out.println("Enter your new password"); + String newPassword = sc.nextLine(); + + if (accountRepo.updatePassword(userId, newPassword)) { + System.out.println("Your password has been updated!"); + } else { + System.out.println("Something went wrong during password update."); + } + break; + + case 6: + System.out.println("Enter userID of the account u want deleted: "); + int userIdDelete = sc.nextInt(); + sc.nextLine(); + + boolean isDeleted = accountRepo.deleteAccount(userIdDelete); + System.out.println(isDeleted ? "You account was successfully deleted!" : "Something went wrong deleting your account."); + break; + + case 0: + System.out.println("Exiting the program."); + break; + + default: + System.out.println("Invalid choice"); } - return (v == null || v.trim().isEmpty()) ? null : v.trim(); } } diff --git a/src/main/java/com/example/MoonMission.java b/src/main/java/com/example/MoonMission.java new file mode 100644 index 0000000..3e42529 --- /dev/null +++ b/src/main/java/com/example/MoonMission.java @@ -0,0 +1,67 @@ +package com.example; + +import java.time.LocalDate; + +public class MoonMission { + private int missionId; + private String spacecraft; + private LocalDate launchDate; + private String carrierRocket; + private String operator; + private String missionType; + private String outcome; + + // GETTERS + public int getMissionId() { + return missionId; + } + public String getSpacecraft() { + return spacecraft; + } + public LocalDate getLaunchDate() { + return launchDate; + } + public String getCarrierRocket() { + return carrierRocket; + } + public String getOperator() { + return operator; + } + public String getMissionType() { + return missionType; + } + public String getOutcome() { + return outcome; + } + + // SETTERS + + + public void setMissionId(int missionId) { + this.missionId = missionId; + } + + public void setSpacecraft(String spacecraft) { + this.spacecraft = spacecraft; + } + + public void setLaunchDate(LocalDate launchDate) { + this.launchDate = launchDate; + } + + public void setCarrierRocket(String carrierRocket) { + this.carrierRocket = carrierRocket; + } + + public void setOutcome(String outcome) { + this.outcome = outcome; + } + + public void setMissionType(String missionType) { + this.missionType = missionType; + } + + public void setOperator(String operator) { + this.operator = operator; + } +} \ 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..dfc2a56 --- /dev/null +++ b/src/main/java/com/example/SimpleDriverManagerDataSource.java @@ -0,0 +1,28 @@ +package com.example; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class SimpleDriverManagerDataSource { + private final String jdbcUrl; + private final String dbUser; + private final String dbPass; + + // Resolve DB settings with precedence: System properties -> Environment variable + public SimpleDriverManagerDataSource(String jdbcUrl, String dbUser, String dbPass){ + this.jdbcUrl = jdbcUrl; + this.dbUser = dbUser; + this.dbPass = dbPass; + 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."); + } + } + + public Connection getConnection() throws SQLException{ + return DriverManager.getConnection(jdbcUrl, dbUser, dbPass); + } + +} diff --git a/src/main/java/com/example/jdbc/JdbcAccountRepository.java b/src/main/java/com/example/jdbc/JdbcAccountRepository.java new file mode 100644 index 0000000..0d6eb7a --- /dev/null +++ b/src/main/java/com/example/jdbc/JdbcAccountRepository.java @@ -0,0 +1,81 @@ +package com.example.jdbc; + +import com.example.repositorys.AccountRepository; +import com.example.SimpleDriverManagerDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class JdbcAccountRepository implements AccountRepository { + + private final SimpleDriverManagerDataSource ds; + + public JdbcAccountRepository(SimpleDriverManagerDataSource ds) { + this.ds = ds; + } + + @Override + public Boolean createAccount(String firstName, String lastName, String ssn, String password) { + String sql = "INSERT INTO account (first_name, last_name, ssn, password) VALUES (?, ?, ?, ?)"; + try (Connection connection = ds.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql)) { + + ps.setString(1, firstName); + ps.setString(2, lastName); + ps.setString(3, ssn); + ps.setString(4, password); + + return ps.executeUpdate() == 1; + } catch (SQLException e) { + throw new RuntimeException("Error creating account", e); + } + } + + @Override + public Boolean updatePassword(int userId, String newPassword) { + String sql = "UPDATE account SET password = ? WHERE user_id = ?"; + try (Connection connection = ds.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql)) { + + ps.setString(1, newPassword); + ps.setInt(2, userId); + + return ps.executeUpdate() == 1; + } catch (SQLException e) { + throw new RuntimeException("Error updating password", e); + } + } + + @Override + public Boolean deleteAccount(int userId) { + String sql = "DELETE FROM account WHERE user_id = ?"; + try (Connection connection = ds.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql)) { + + ps.setInt(1, userId); + return ps.executeUpdate() == 1; + } catch (SQLException e) { + throw new RuntimeException("Error deleting account", e); + } + } + + @Override + public Boolean validateCredentials(String username, String password) { + // Adjust column names depending on your schema! + String sql = "SELECT * FROM account WHERE name = ? AND password = ?"; + try (Connection connection = ds.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql)) { + + ps.setString(1, username); + ps.setString(2, password); + + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } catch (SQLException e) { + return false; + } + } +} diff --git a/src/main/java/com/example/jdbc/JdbcMoonMissionRepository.java b/src/main/java/com/example/jdbc/JdbcMoonMissionRepository.java new file mode 100644 index 0000000..1070152 --- /dev/null +++ b/src/main/java/com/example/jdbc/JdbcMoonMissionRepository.java @@ -0,0 +1,89 @@ +package com.example.jdbc; + +import com.example.MoonMission; +import com.example.SimpleDriverManagerDataSource; +import com.example.repositorys.MoonMissionRepository; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class JdbcMoonMissionRepository implements MoonMissionRepository { + + private final SimpleDriverManagerDataSource ds; + + public JdbcMoonMissionRepository(SimpleDriverManagerDataSource ds) { + this.ds = ds; + } + + @Override + public List listMissions() { + String sql = "SELECT spacecraft FROM moon_mission"; + List result = new ArrayList<>(); + + try (Connection connection = ds.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ResultSet rs = ps.executeQuery()) { + + while (rs.next()) { + result.add(rs.getString("spacecraft")); + } + return result; + } catch (SQLException e) { + throw new RuntimeException("Error listing missions", e); + } + } + + @Override + public Optional getMissionById(int missionId) { + String sql = "SELECT * FROM moon_mission WHERE mission_id = ?"; + + try (Connection connection = ds.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql)) { + + ps.setInt(1, missionId); + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + MoonMission mission = new MoonMission(); + mission.setMissionId(rs.getInt("mission_id")); + mission.setSpacecraft(rs.getString("spacecraft")); + java.sql.Date sqlDate = rs.getDate("launch_date"); + mission.setLaunchDate(sqlDate != null ? sqlDate.toLocalDate() : null); + mission.setCarrierRocket(rs.getString("carrier_rocket")); + mission.setOutcome(rs.getString("outcome")); + mission.setMissionType(rs.getString("mission_type")); + mission.setOperator(rs.getString("operator")); + return Optional.of(mission); + } + } + } catch (SQLException e) { + throw new RuntimeException("Error fetching mission by ID", e); + } + return Optional.empty(); + } + + @Override + public int countMissionsByYear(int year) { + String sql = "SELECT COUNT(*) AS mission_count FROM moon_mission WHERE YEAR(launch_date) = ?"; + + try (Connection connection = ds.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql)) { + + ps.setInt(1, year); + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return rs.getInt("mission_count"); + } + } + } catch (SQLException e) { + throw new RuntimeException("Error counting missions by year", e); + } + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/repositorys/AccountRepository.java b/src/main/java/com/example/repositorys/AccountRepository.java new file mode 100644 index 0000000..5e96d0c --- /dev/null +++ b/src/main/java/com/example/repositorys/AccountRepository.java @@ -0,0 +1,8 @@ +package com.example.repositorys; + +public interface AccountRepository { + Boolean createAccount(String firstName, String lastName, String ssn, String password); + Boolean updatePassword(int userId, String newPassword); + Boolean deleteAccount(int userId); + Boolean validateCredentials(String username, String password); +} diff --git a/src/main/java/com/example/repositorys/MoonMissionRepository.java b/src/main/java/com/example/repositorys/MoonMissionRepository.java new file mode 100644 index 0000000..602f7dd --- /dev/null +++ b/src/main/java/com/example/repositorys/MoonMissionRepository.java @@ -0,0 +1,12 @@ +package com.example.repositorys; + +import com.example.MoonMission; + +import java.util.List; +import java.util.Optional; + +public interface MoonMissionRepository { + List listMissions(); + Optional getMissionById(int missionId); + int countMissionsByYear(int year); +}