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/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c1e2c6f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + mysql: + image: mysql:9.5.0 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_ROOT_HOST: "%" + MYSQL_DATABASE: testdb + MYSQL_USER: user + MYSQL_PASSWORD: pass + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql +volumes: + mysql_data: \ No newline at end of file diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 diff --git a/pom.xml b/pom.xml index 002ff66..5a38a0f 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,11 @@ 9.5.0 runtime + + org.slf4j + slf4j-api + 2.0.17 + org.junit.jupiter junit-jupiter @@ -40,6 +45,11 @@ ${mockito.version} test + + org.slf4j + slf4j-nop + 2.0.17 + org.testcontainers junit-jupiter diff --git a/src/main/java/com/example/Account.java b/src/main/java/com/example/Account.java new file mode 100644 index 0000000..573b461 --- /dev/null +++ b/src/main/java/com/example/Account.java @@ -0,0 +1,83 @@ +package com.example; + +public class Account { + private int user_id; + private String name, password, first_name, last_name, ssn; + + public Account(int user_id, String name, String password, String first_name, String last_name, String ssn) { + this.user_id = user_id; + this.name = name; + this.password = password; + this.first_name = first_name; + this.last_name = last_name; + this.ssn = ssn; + } + + public Account(String name, String password, String first_name, String last_name, String ssn) { + this.name = name; + this.password = password; + this.first_name = first_name; + this.last_name = last_name; + this.ssn = ssn; + } + + public int getUser_id() { + return user_id; + } + + public void setUser_id(int user_id) { + this.user_id = user_id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFirst_name() { + return first_name; + } + + public void setFirst_name(String first_name) { + this.first_name = first_name; + } + + public String getLast_name() { + return last_name; + } + + public void setLast_name(String last_name) { + this.last_name = last_name; + } + + public String getSsn() { + return ssn; + } + + public void setSsn(String ssn) { + this.ssn = ssn; + } + + @Override + public String toString() { + return "Account{" + + "user_id=" + user_id + + ", name='" + name + '\'' + + ", password='" + password + '\'' + + ", first_name='" + first_name + '\'' + + ", last_name='" + last_name + '\'' + + ", ssn='" + ssn + '\'' + + '}'; + } +} diff --git a/src/main/java/com/example/AccountRepository.java b/src/main/java/com/example/AccountRepository.java new file mode 100644 index 0000000..73023d3 --- /dev/null +++ b/src/main/java/com/example/AccountRepository.java @@ -0,0 +1,15 @@ +package com.example; + +import java.util.List; +import java.util.Optional; + +public interface AccountRepository { + List findUsernames(); + List findPasswords(); + Optional findByUsername(String username); + List findAccounts(); + boolean createAccount(Account account); + int countAccounts(); + boolean updatePassword(int id, String password); + boolean deleteAccount(int id); +} diff --git a/src/main/java/com/example/AccountRepositoryImpl.java b/src/main/java/com/example/AccountRepositoryImpl.java new file mode 100644 index 0000000..5bd9137 --- /dev/null +++ b/src/main/java/com/example/AccountRepositoryImpl.java @@ -0,0 +1,176 @@ +package com.example; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class AccountRepositoryImpl implements AccountRepository { + private final DataSource dataSource; + Connection connection; + + public AccountRepositoryImpl(DataSource dataSource) { + this.dataSource = dataSource; + } + + + @Override + public List findUsernames() { + List usernames = new ArrayList<>(); + String sql = "select * from account"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + usernames.add(rs.getString("name")); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return usernames; + } + + @Override + public List findPasswords() { + List passwords = new ArrayList<>(); + String sql = "select * from account"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + passwords.add(rs.getString("password")); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return passwords; + } + + @Override + public Optional findByUsername(String username) { + String sql = "select * from account where name = ?"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ps.setString(1, username); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + Account account = new Account( + rs.getInt("user_id"), + rs.getString("name"), + rs.getString("password"), + rs.getString("first_name"), + rs.getString("last_name"), + rs.getString("ssn") + ); + return Optional.of(account); + } + else return Optional.empty(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAccounts() { + List accounts = new ArrayList<>(); + String sql = "select * from account"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + Account a = new Account( + rs.getInt("user_id"), + rs.getString("name"), + rs.getString("password"), + rs.getString("first_name"), + rs.getString("last_name"), + rs.getString("ssn") + ); + accounts.add(a); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return accounts; + } + + @Override + public boolean createAccount(Account account) { + String sql = "insert into account(name, password, first_name, last_name, ssn) values (?, ?, ?, ?, ?)"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ps.setString(1, account.getName()); + ps.setString(2, account.getPassword()); + ps.setString(3, account.getFirst_name()); + ps.setString(4, account.getLast_name()); + ps.setString(5, account.getSsn()); + if (ps.executeUpdate() == 1) { + return true; + } + + + } catch (SQLException e) { + return false; + } + return false; + } + + @Override + public int countAccounts() { + int count = 0; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement("select count(*) from account"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + count = rs.getInt(1); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return count; + } + @Override + public boolean updatePassword(int id, String password) { + String sql = "update account set password=? where user_id=?"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ps.setString(1, password); + ps.setInt(2, id); + int rowsAffected = ps.executeUpdate(); + + if (rowsAffected > 0) { + return true; + } + + } catch (SQLException e) { + return false; + } + return false; + } + + @Override + public boolean deleteAccount(int id) { + String sql = "delete from account where user_id=?"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ps.setInt(1, id); + int rowsAffected = ps.executeUpdate(); + if (rowsAffected > 0) { + return true; + } + } catch (SQLException e) { + return false; + } + return false; + } + +} diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 6dc6fbd..4b12a6f 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -1,9 +1,10 @@ package com.example; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; +import javax.sql.DataSource; import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Scanner; public class Main { @@ -26,13 +27,152 @@ public void run() { "as system properties (-Dkey=value) or environment variables."); } - try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) { - } catch (SQLException e) { - throw new RuntimeException(e); - } //Todo: Starting point for your code + + DataSource dataSource = new SimpleDriverManagerDataSource(jdbcUrl, dbUser, dbPass); + AccountRepository accountRepo = new AccountRepositoryImpl(dataSource); + MoonMissionRepository moonMissionRepo = new MoonMissionRepositoryImpl(dataSource); + + Scanner scanner = new Scanner(System.in); + + if (validateLogin(accountRepo, scanner)) return; + + showMenu(); + String choice = scanner.nextLine(); + if (!choice.isEmpty()) { + switch (choice) { + case "1" -> showMissions(moonMissionRepo); + case "2" -> getMissionById(scanner, moonMissionRepo); + case "3" -> countMissionsForYear(scanner, moonMissionRepo); + case "4" -> createAccount(scanner, accountRepo); + case "5" -> updatePasswordById(scanner, accountRepo); + case "6" -> deleteAccountById(scanner, accountRepo); + case "0" -> System.out.println("Exiting..."); + default -> System.out.println("Invalid choice."); + } + } else { + System.out.println("Choice cannot be empty."); + } + +// scanner.close(); + } + + private boolean validateLogin(AccountRepository accountRepo, Scanner scanner) { + Optional currentUser = login(accountRepo, scanner); + + while (currentUser.isEmpty()) { + System.out.println("Enter 0 to exit or 1 to try again."); + String answer = scanner.nextLine().trim(); + if (answer.equals("0")) { + System.out.println("Exiting..."); + return true; + } else if (answer.equals("1")) { + currentUser = login(accountRepo, scanner); + }else + System.out.println("Invalid choice."); + } + return false; + } + + private Optional login(AccountRepository accountRepo, Scanner sc) { + System.out.println("Username: "); + String user = sc.nextLine().trim(); + System.out.println("Password: "); + String pass = sc.nextLine().trim(); + if (user.isEmpty() || pass.isEmpty()) { + System.out.println("Username or password cannot be empty."); + return Optional.empty(); + } + + Optional maybeAccount = accountRepo.findByUsername(user); + if(maybeAccount.isEmpty() || !pass.equals(maybeAccount.get().getPassword())) { + System.out.println("Invalid username or password."); + return Optional.empty(); + } + return maybeAccount; + } + + private static void deleteAccountById(Scanner sc, AccountRepository accountRepo) { + System.out.println("Enter the user_id for the account you want to delete: "); + int userId = Integer.parseInt(sc.nextLine().trim()); + if(accountRepo.deleteAccount(userId)) + System.out.println("Account successfully deleted."); + else + System.out.println("Account could not be deleted."); + } + + private static void updatePasswordById(Scanner sc, AccountRepository accountRepo) { + System.out.println("Enter a user_id to update the password for: "); + int userId = Integer.parseInt(sc.nextLine().trim()); + System.out.println("Enter new password:"); + String newPassword = sc.nextLine().trim(); + while (newPassword.isEmpty()) { + System.out.println("Password cannot be empty."); + System.out.println("Enter new password:"); + newPassword = sc.nextLine().trim(); + } + if(accountRepo.updatePassword(userId, newPassword)) + System.out.println("Password successfully updated."); + else + System.out.println("Password could not be updated."); + } + + private static void createAccount(Scanner sc, AccountRepository accountRepo) { + String name, firstName, lastName, password, ssn; + System.out.println("Type in your username: "); + name = sc.nextLine().trim(); + System.out.println("Type in your first name: "); + firstName = sc.nextLine().trim(); + System.out.println("Type in your last name: "); + lastName = sc.nextLine().trim(); + System.out.println("Type in your password: "); + password = sc.nextLine().trim(); + System.out.println("Type in your ssn: "); + ssn = sc.nextLine().trim(); + Account newAccount = new Account(name, password, firstName, lastName, ssn); + if (accountRepo.createAccount(newAccount)) { + System.out.println("Account successfully created."); + } else { + System.out.println("Account could not be created."); + } + } + + private static void countMissionsForYear(Scanner sc, MoonMissionRepository moonMissionRepo) { + System.out.println("Enter a year for which you want number of missions listed: "); + int year = Integer.parseInt(sc.nextLine().trim()); + System.out.println("In " + year + " there were " + moonMissionRepo.missionsCountByYear(year) + " missions."); + } + + private static void getMissionById(Scanner sc, MoonMissionRepository moonMissionRepo) { + System.out.println("Provide a mission_id to get information: "); + String id = sc.nextLine().trim(); + List missions = moonMissionRepo.getMoonMissionById(id); + if(missions.isEmpty()) + System.out.println("Mission not found."); + else + System.out.println(missions); + } + + private static void showMissions(MoonMissionRepository moonMissionRepo) { + System.out.println(moonMissionRepo.listMoonMissions()); + } + + private static void showMenu() { + System.out.println("Options: "); + String menu = """ + 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. + """; + System.out.println(menu); + System.out.println("Enter your option (0-6): "); } + /** * Determines if the application is running in development mode based on system properties, * environment variables, or command-line arguments. diff --git a/src/main/java/com/example/MoonMission.java b/src/main/java/com/example/MoonMission.java new file mode 100644 index 0000000..6e6596f --- /dev/null +++ b/src/main/java/com/example/MoonMission.java @@ -0,0 +1,83 @@ +package com.example; + +import java.sql.Date; + +public class MoonMission { + private int mission_id; + private String spacecraft, carrier_rocket, operator, mission_type, outcome; + private Date launch_date; + + public MoonMission(int mission_id, String spacecraft, Date launch_date, String carrier_rocket, String operator, String mission_type, String outcome) { + this.mission_id = mission_id; + this.spacecraft = spacecraft; + this.carrier_rocket = carrier_rocket; + this.operator = operator; + this.mission_type = mission_type; + this.outcome = outcome; + this.launch_date = launch_date; + } + + public int getMission_id() { + return mission_id; + } + + public void setMission_id(int mission_id) { + this.mission_id = mission_id; + } + + public String getSpacecraft() { + return spacecraft; + } + + public void setSpacecraft(String spacecraft) { + this.spacecraft = spacecraft; + } + + public String getCarrier_rocket() { + return carrier_rocket; + } + + public void setCarrier_rocket(String carrier_rocket) { + this.carrier_rocket = carrier_rocket; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getMission_type() { + return mission_type; + } + + public void setMission_type(String mission_type) { + this.mission_type = mission_type; + } + + public String getOutcome() { + return outcome; + } + + public void setOutcome(String outcome) { + this.outcome = outcome; + } + + public Date getLaunch_date() { + return launch_date; + } + + public void setLaunch_date(Date launch_date) { + this.launch_date = launch_date; + } + + @Override + public String toString() { + return String.format( + "ID: %s | Spacecraft: %s | Rocket: %s | Operator: %s | Type: %s | Outcome: %s | Launch date: %s", + mission_id, spacecraft, carrier_rocket, operator, mission_type, outcome, launch_date + ); + } +} diff --git a/src/main/java/com/example/MoonMissionRepository.java b/src/main/java/com/example/MoonMissionRepository.java new file mode 100644 index 0000000..49b90cd --- /dev/null +++ b/src/main/java/com/example/MoonMissionRepository.java @@ -0,0 +1,9 @@ +package com.example; + +import java.util.List; + +public interface MoonMissionRepository { + List listMoonMissions(); + List getMoonMissionById(String id); + int missionsCountByYear(int year); +} diff --git a/src/main/java/com/example/MoonMissionRepositoryImpl.java b/src/main/java/com/example/MoonMissionRepositoryImpl.java new file mode 100644 index 0000000..0545799 --- /dev/null +++ b/src/main/java/com/example/MoonMissionRepositoryImpl.java @@ -0,0 +1,78 @@ +package com.example; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class MoonMissionRepositoryImpl implements MoonMissionRepository { + private final DataSource dataSource; + Connection connection; + + public MoonMissionRepositoryImpl(DataSource dataSource) { + this.dataSource = dataSource; + } + + + @Override + public List listMoonMissions() { + String sql = "select * from moon_mission"; + List moonMissions = new ArrayList<>(); + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + moonMissions.add(rs.getString("spacecraft")); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return moonMissions; + } + @Override + public List getMoonMissionById(String id) { + List moonMissions = new ArrayList<>(); + String sql = "select * from moon_mission where mission_id = ?"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ps.setString(1,id); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + MoonMission m = 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")); + moonMissions.add(m); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return moonMissions; + + } + + @Override + public int missionsCountByYear(int year) { + int count= 0; + String sql = "select count(*) as mission_count from moon_mission where year(launch_date) = ?"; + try { + connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ps.setInt(1, year); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + count = rs.getInt("mission_count"); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return count; + } +} diff --git a/src/main/java/com/example/SimpleDriverManagerDataSource.java b/src/main/java/com/example/SimpleDriverManagerDataSource.java new file mode 100644 index 0000000..0eab497 --- /dev/null +++ b/src/main/java/com/example/SimpleDriverManagerDataSource.java @@ -0,0 +1,76 @@ +package com.example; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +public class SimpleDriverManagerDataSource implements DataSource { + + private final String url; + private final String username; + private final String password; + + + public SimpleDriverManagerDataSource(String url, String username, String password) { + this.url = url; + this.username = username; + this.password = password; + } + + @Override + public Connection getConnection() { + try { + return DriverManager.getConnection(url, username, password); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public Connection getConnection(String username, String password) { + try { + return DriverManager.getConnection(url, username, password); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + + } + + @Override + public int getLoginTimeout() throws SQLException { + return 0; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } +}