From 646d1564f9f52095a58a558df4c27889efccecf8 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:53:40 +0000 Subject: [PATCH 1/8] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) 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. From 66b030de8becc467200a4f3748a73ccd10619162 Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Fri, 5 Dec 2025 15:44:32 +0100 Subject: [PATCH 2/8] basic login functionality Signed-off-by: Jesper Larsson --- src/main/java/com/example/Main.java | 53 ++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 6dc6fbd..5d275ef 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -1,8 +1,6 @@ package com.example; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; +import java.sql.*; import java.util.Arrays; public class Main { @@ -27,12 +25,59 @@ public void run() { } try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) { + + boolean authorized = login(connection); + + if (authorized) { + promptMenu(); + } + else { + System.out.println("Username or password is invalid."); + System.exit(0); + } + + } catch (SQLException e) { + throw new RuntimeException(e); + } + + + + } + + + private boolean login(Connection connection) { + String unm = IO.readln("Username: "); + String pw = IO.readln("Password: "); + + String accQuery = "select count(*) from account where binary name = ? and binary password = ?"; + try(PreparedStatement statement = connection.prepareStatement(accQuery)){ + statement.setString(1, unm); + statement.setString(2, pw); + + ResultSet rs = statement.executeQuery(); + while(rs.next()) { + if(rs.getInt("count(*)") == 1){return true;} + } + return false; } catch (SQLException e) { throw new RuntimeException(e); } - //Todo: Starting point for your code } + private int promptMenu(){ + System.out.print( + "1) List moon missions (prints spacecraft names from `moon_mission`).\n" + + "2) Get a moon mission by mission_id (prints details for that mission).\n" + + "3) Count missions for a given year (prompts: year; prints the number of missions launched that year).\n" + + "4) Create an account (prompts: first name, last name, ssn, password; prints confirmation).\n" + + "5) Update an account password (prompts: user_id, new password; prints confirmation).\n" + + "6) Delete an account (prompts: user_id; prints confirmation).\n" + + "0) Exit.\n"); + + return Integer.parseInt(IO.readln()); + } + + /** * Determines if the application is running in development mode based on system properties, * environment variables, or command-line arguments. From 6477a6216bd1f1dddb1555e0f2688f5b0c65316d Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Wed, 10 Dec 2025 18:06:41 +0100 Subject: [PATCH 3/8] input and menu methods Signed-off-by: Jesper Larsson --- src/main/java/com/example/Main.java | 219 +++++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 5d275ef..5688698 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -2,6 +2,8 @@ import java.sql.*; import java.util.Arrays; +import java.util.Comparator; +import java.util.regex.Pattern; public class Main { @@ -28,20 +30,32 @@ public void run() { boolean authorized = login(connection); - if (authorized) { - promptMenu(); - } - else { + + if (!authorized){ System.out.println("Username or password is invalid."); System.exit(0); } + while(true) { + int option = promptMenu(); + + switch (option) { + case 1 -> listMissions(connection); + case 2 -> getMission(connection); + case 3 -> missionsCountYear(connection); + case 4 -> createAccount(connection); + case 5 -> System.out.println("5"); + case 6 -> System.out.println("6"); + case 0 -> System.exit(0); + + default -> System.out.println("Invalid choice.\n"); + } + } + } catch (SQLException e) { throw new RuntimeException(e); } - - } @@ -65,7 +79,7 @@ private boolean login(Connection connection) { } private int promptMenu(){ - System.out.print( + System.out.print("\n" + "1) List moon missions (prints spacecraft names from `moon_mission`).\n" + "2) Get a moon mission by mission_id (prints details for that mission).\n" + "3) Count missions for a given year (prompts: year; prints the number of missions launched that year).\n" + @@ -74,7 +88,196 @@ private int promptMenu(){ "6) Delete an account (prompts: user_id; prints confirmation).\n" + "0) Exit.\n"); - return Integer.parseInt(IO.readln()); + return getValidInt("Enter Choice: "); + } + + private void listMissions(Connection connection){ + String spaceshipQuery = "select spacecraft from moon_mission"; + + try(PreparedStatement statement = connection.prepareStatement(spaceshipQuery)){ + + ResultSet rs = statement.executeQuery(); + while(rs.next()) { + System.out.println(rs.getString("spacecraft")); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void getMission(Connection connection){ + String missionQuery = "select * from moon_mission where mission_id = ?"; + int id = getValidInt("Mission Id: "); + + try(PreparedStatement statement = connection.prepareStatement(missionQuery)){ + statement.setInt(1, id); + + ResultSet rs = statement.executeQuery(); + + if(rs.next()) { + System.out.println( + "\nSpacecraft: " + rs.getString("spacecraft") + + "\nLaunch date: " + rs.getString("launch_date") + + "\nCarrier rocket: " + rs.getString("carrier_rocket") + + "\nOperator: " + rs.getString("operator") + + "\nMission type: " + rs.getString("mission_type") + + "\nOutcome: " + rs.getString("outcome")); + } + else { + System.out.println("\nMission not found."); + } + + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void missionsCountYear(Connection connection){ + String missionYearQuery = "select count(*) from moon_mission where year(launch_date) = ?)"; + int year = getValidInt("Mission Year: "); + + try(PreparedStatement statement = connection.prepareStatement(missionYearQuery)){ + statement.setInt(1, year); + + ResultSet rs = statement.executeQuery(); + + while(rs.next()) { + System.out.println("\nMissions in " + year + ": " + rs.getInt("count(*)")); + } + + } catch (SQLException e) { + throw new RuntimeException(e); + } + + } + + private void createAccount(Connection connection) { + String fn = getValidName("First Name: "); + String ln = getValidName("Last Name: "); + String ssn = getValidSSN("SSN: "); + String pw = getValidPassword("Password: "); + + String accName; + if(fn.length() <= 3){ + if(ln.length() <= 3){ + accName = fn + ln; + } + else{ + accName = fn + ln.substring(0, 2); + } + } + else if(ln.length() <= 3){ + accName = fn.substring(0, 3) + ln; + } + else{ + accName = fn.substring(0, 3) + ln.substring(0, 3); + } + + String checkName = "select count(*) from account where name = ?"; + + while(true) { + try (PreparedStatement statement = connection.prepareStatement(checkName)) { + statement.setString(1, accName); + + ResultSet rs = statement.executeQuery(); + + + if(rs.next() && rs.getInt("count(*)") == 0){ + break; + } + else{ + accName = getValidName("Account Name: "); + } + + + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + String newAccQuery = "insert into account values (0, ?, ?, ?, ?, ?)"; + + try (PreparedStatement statement = connection.prepareStatement(newAccQuery)) { + statement.setString(1, accName); + statement.setString(2, pw); + statement.setString(3, fn); + statement.setString(4, ln); + statement.setString(5, ssn); + + statement.executeUpdate(); + + + System.out.println("\nAccount created successfully."); + + + } catch (SQLException e) { + throw new RuntimeException(e); + } + + } + + + private int getValidInt(String prompt){ + while(true){ + try { + int option = Integer.parseInt(IO.readln("\n" + prompt)); + + if (option >= 0) { + return option; + } + else { + System.out.println("Please enter a positive integer.\n"); + } + } + catch (NumberFormatException e){ + System.out.println("Please enter a valid integer\n"); + } + } + } + + private String getValidName(String prompt){ + while(true){ + String name = IO.readln("\n" + prompt).trim(); + + if (name.isBlank()) { + System.out.println("\nCannot be blank"); + } + else if(!Pattern.matches("^[a-zA-Z]+$", name)){ + System.out.println("\nMust only contain letters"); + } + else{ + return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase(); + } + } + } + + private String getValidSSN(String prompt){ + while(true){ + String ssn = IO.readln("\n" + prompt).trim(); + + if (ssn.isBlank()) { + System.out.println("\nCannot be blank"); + } + else if(!Pattern.matches("^\\d{6}-\\d{4}$", ssn)){ + System.out.println("\nMust follow pattern YYMMDD-XXXX"); + } + else { + return ssn; + } + } + } + + private String getValidPassword(String prompt){ + while(true){ + String pw = IO.readln("\n" + prompt); + + if(pw.length() < 6){ + System.out.println("Password must be at least 6 characters"); + } + else{ + return pw; + } + } } From fbbfc416b80ecb3143dd156b2e0cdcb7434b0705 Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Thu, 11 Dec 2025 13:17:13 +0100 Subject: [PATCH 4/8] basic functionality for all menu options Signed-off-by: Jesper Larsson --- src/main/java/com/example/Main.java | 48 ++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 5688698..d415110 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -32,7 +32,7 @@ public void run() { if (!authorized){ - System.out.println("Username or password is invalid."); + System.out.println("Invalid username or password"); System.exit(0); } @@ -44,8 +44,8 @@ public void run() { case 2 -> getMission(connection); case 3 -> missionsCountYear(connection); case 4 -> createAccount(connection); - case 5 -> System.out.println("5"); - case 6 -> System.out.println("6"); + case 5 -> updatePassword(connection); + case 6 -> deleteAccount(connection); case 0 -> System.exit(0); default -> System.out.println("Invalid choice.\n"); @@ -207,7 +207,7 @@ else if(ln.length() <= 3){ statement.executeUpdate(); - System.out.println("\nAccount created successfully."); + System.out.println("\nAccount created"); } catch (SQLException e) { @@ -216,6 +216,46 @@ else if(ln.length() <= 3){ } + private void updatePassword (Connection connection) { + int id = getValidInt("User id: "); + String newPassword = getValidPassword("New password: "); + + String updatePwQuery = "update account set password = ? where id = ?"; + + try (PreparedStatement statement = connection.prepareStatement(updatePwQuery)) { + statement.setInt(1, id); + statement.setString(2, newPassword); + + statement.executeUpdate(); + + + System.out.println("\nPassword updated"); + + + } catch (SQLException e) { + throw new RuntimeException(e); + } + + } + + private void deleteAccount(Connection connection) { + int id = getValidInt("User id: "); + + String deleteAccQuery = "delete from account where id = ?"; + + try (PreparedStatement statement = connection.prepareStatement(deleteAccQuery)) { + statement.setInt(1, id); + + statement.executeUpdate(); + + + System.out.println("\nAccount deleted"); + + + } catch (SQLException e) { + throw new RuntimeException(e); + } + } private int getValidInt(String prompt){ while(true){ From 16d6c4234aaac509d7f7bb284dcf6c717d42874b Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Thu, 11 Dec 2025 15:50:32 +0100 Subject: [PATCH 5/8] test green, not optimized or commented Signed-off-by: Jesper Larsson --- src/main/java/com/example/Main.java | 64 ++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index d415110..2342385 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -3,10 +3,13 @@ import java.sql.*; import java.util.Arrays; import java.util.Comparator; +import java.util.Scanner; import java.util.regex.Pattern; public class Main { + Scanner scanner = new Scanner(System.in); + static void main(String[] args) { if (isDevMode(args)) { DevDatabaseInitializer.start(); @@ -28,14 +31,28 @@ public void run() { try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) { - boolean authorized = login(connection); + while(true) { + //Scanner scanner = new Scanner(System.in); + boolean authorized = login(connection); + + if (!authorized) { + System.out.println("Invalid username or password"); + System.out.println("press 0 to exit"); + while(true){ + String exit = scanner.nextLine().trim(); + if(exit.equals("0")){ + return; + } + } - if (!authorized){ - System.out.println("Invalid username or password"); - System.exit(0); + } + else{ + break; + } } + while(true) { int option = promptMenu(); @@ -46,7 +63,9 @@ public void run() { case 4 -> createAccount(connection); case 5 -> updatePassword(connection); case 6 -> deleteAccount(connection); - case 0 -> System.exit(0); + case 0 -> { + return; + } default -> System.out.println("Invalid choice.\n"); } @@ -60,8 +79,10 @@ public void run() { private boolean login(Connection connection) { - String unm = IO.readln("Username: "); - String pw = IO.readln("Password: "); + System.out.println("Username: "); + String unm = scanner.nextLine(); + System.out.println("Password: "); + String pw = scanner.nextLine(); String accQuery = "select count(*) from account where binary name = ? and binary password = ?"; try(PreparedStatement statement = connection.prepareStatement(accQuery)){ @@ -133,7 +154,7 @@ private void getMission(Connection connection){ } private void missionsCountYear(Connection connection){ - String missionYearQuery = "select count(*) from moon_mission where year(launch_date) = ?)"; + String missionYearQuery = "select count(*) from moon_mission where year(launch_date) = ?"; int year = getValidInt("Mission Year: "); try(PreparedStatement statement = connection.prepareStatement(missionYearQuery)){ @@ -220,28 +241,27 @@ private void updatePassword (Connection connection) { int id = getValidInt("User id: "); String newPassword = getValidPassword("New password: "); - String updatePwQuery = "update account set password = ? where id = ?"; + String updatePwQuery = "update account set password = ? where user_id = ?"; try (PreparedStatement statement = connection.prepareStatement(updatePwQuery)) { - statement.setInt(1, id); - statement.setString(2, newPassword); + statement.setString(1, newPassword); + statement.setInt(2, id); statement.executeUpdate(); - - System.out.println("\nPassword updated"); - + System.out.println("updated"); } catch (SQLException e) { throw new RuntimeException(e); } + } private void deleteAccount(Connection connection) { int id = getValidInt("User id: "); - String deleteAccQuery = "delete from account where id = ?"; + String deleteAccQuery = "delete from account where user_id = ?"; try (PreparedStatement statement = connection.prepareStatement(deleteAccQuery)) { statement.setInt(1, id); @@ -249,7 +269,7 @@ private void deleteAccount(Connection connection) { statement.executeUpdate(); - System.out.println("\nAccount deleted"); + System.out.println("deleted"); } catch (SQLException e) { @@ -260,7 +280,8 @@ private void deleteAccount(Connection connection) { private int getValidInt(String prompt){ while(true){ try { - int option = Integer.parseInt(IO.readln("\n" + prompt)); + System.out.println("\n" + prompt); + int option = Integer.parseInt(scanner.nextLine()); if (option >= 0) { return option; @@ -277,7 +298,8 @@ private int getValidInt(String prompt){ private String getValidName(String prompt){ while(true){ - String name = IO.readln("\n" + prompt).trim(); + System.out.println("\n" + prompt); + String name = scanner.nextLine().trim(); if (name.isBlank()) { System.out.println("\nCannot be blank"); @@ -293,7 +315,8 @@ else if(!Pattern.matches("^[a-zA-Z]+$", name)){ private String getValidSSN(String prompt){ while(true){ - String ssn = IO.readln("\n" + prompt).trim(); + System.out.println("\n" + prompt); + String ssn = scanner.nextLine().trim(); if (ssn.isBlank()) { System.out.println("\nCannot be blank"); @@ -309,7 +332,8 @@ else if(!Pattern.matches("^\\d{6}-\\d{4}$", ssn)){ private String getValidPassword(String prompt){ while(true){ - String pw = IO.readln("\n" + prompt); + System.out.println("\n" + prompt); + String pw = scanner.nextLine(); if(pw.length() < 6){ System.out.println("Password must be at least 6 characters"); From 3f354f6d0910d47eb5ac65b1532bff27a1d647c0 Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Fri, 12 Dec 2025 13:11:46 +0100 Subject: [PATCH 6/8] a bit cleaner and with comments Signed-off-by: Jesper Larsson --- src/main/java/com/example/Main.java | 131 ++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 2342385..0be81fc 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -2,13 +2,12 @@ import java.sql.*; import java.util.Arrays; -import java.util.Comparator; import java.util.Scanner; import java.util.regex.Pattern; public class Main { - Scanner scanner = new Scanner(System.in); + private final Scanner scanner = new Scanner(System.in); static void main(String[] args) { if (isDevMode(args)) { @@ -31,8 +30,6 @@ public void run() { try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) { - while(true) { - //Scanner scanner = new Scanner(System.in); boolean authorized = login(connection); @@ -45,12 +42,7 @@ public void run() { return; } } - } - else{ - break; - } - } while(true) { @@ -77,28 +69,40 @@ public void run() { } - + /** + * Prompts username and password, checks if the combination is present in accounts + * + * @param connection + * @return true if the name/password combo exists + * false if either name/password isn't present + */ private boolean login(Connection connection) { System.out.println("Username: "); String unm = scanner.nextLine(); System.out.println("Password: "); String pw = scanner.nextLine(); + //Query count if a row has the username and password combo, binary ensures case sensitivity String accQuery = "select count(*) from account where binary name = ? and binary password = ?"; try(PreparedStatement statement = connection.prepareStatement(accQuery)){ statement.setString(1, unm); statement.setString(2, pw); ResultSet rs = statement.executeQuery(); - while(rs.next()) { - if(rs.getInt("count(*)") == 1){return true;} - } - return false; + + return rs.next() && rs.getInt("count(*)") == 1; //if the credentials are found count will return 1 + + } catch (SQLException e) { throw new RuntimeException(e); } } + /** + * Prompts the menu options and returns a user input integer + * @return integer >= 0 + * @see #getValidInt(String) + */ private int promptMenu(){ System.out.print("\n" + "1) List moon missions (prints spacecraft names from `moon_mission`).\n" + @@ -112,9 +116,13 @@ private int promptMenu(){ return getValidInt("Enter Choice: "); } + + /** + * Lists all spacecraft from the moon_mission table + * @param connection + */ private void listMissions(Connection connection){ String spaceshipQuery = "select spacecraft from moon_mission"; - try(PreparedStatement statement = connection.prepareStatement(spaceshipQuery)){ ResultSet rs = statement.executeQuery(); @@ -126,10 +134,16 @@ private void listMissions(Connection connection){ } } + + /** + * Prompts for a mission ID and prints its data is available + * @param connection + * @see #getValidInt(String) + */ private void getMission(Connection connection){ - String missionQuery = "select * from moon_mission where mission_id = ?"; int id = getValidInt("Mission Id: "); + String missionQuery = "select * from moon_mission where mission_id = ?"; try(PreparedStatement statement = connection.prepareStatement(missionQuery)){ statement.setInt(1, id); @@ -153,6 +167,11 @@ private void getMission(Connection connection){ } } + /** + * Prompts for a year and prints how many rows in moon_mission was launched then + * @param connection + * @see #getValidInt(String) + */ private void missionsCountYear(Connection connection){ String missionYearQuery = "select count(*) from moon_mission where year(launch_date) = ?"; int year = getValidInt("Mission Year: "); @@ -172,28 +191,35 @@ private void missionsCountYear(Connection connection){ } + /** + * Gives a flow for creating a new account, asking for First and Last name, SSN and password + * default accountname is assigned if available and promted for if not + * @param connection + * @see #getValidName(String) + * @see #getValidSSN(String) + * @see #getValidPassword(String) + */ private void createAccount(Connection connection) { String fn = getValidName("First Name: "); String ln = getValidName("Last Name: "); String ssn = getValidSSN("SSN: "); String pw = getValidPassword("Password: "); - String accName; - if(fn.length() <= 3){ - if(ln.length() <= 3){ - accName = fn + ln; - } - else{ - accName = fn + ln.substring(0, 2); - } + String accName; //Default accountname is first three letters of first and last name + if(fn.length() <= 3 && ln.length() <=3){ //if both first and last name are 3 or fewer letters + accName = fn + ln;} //accountname is both full combined + + else if (fn.length() <= 3){ //if only first name is 3 or fewer letters + accName = fn + ln.substring(0, 2); //accountname is full first name and first 3 from last name } - else if(ln.length() <= 3){ - accName = fn.substring(0, 3) + ln; + else if(ln.length() <= 3){ //if only last name is 3 or fewer letters + accName = fn.substring(0, 2) + ln; //accountname is first 3 from first name and full last name } - else{ - accName = fn.substring(0, 3) + ln.substring(0, 3); + else{ //if both are longer than 3 accountname follows default pattern + accName = fn.substring(0, 2) + ln.substring(0, 2); } + //Query to check if the accountname exists String checkName = "select count(*) from account where name = ?"; while(true) { @@ -203,11 +229,12 @@ else if(ln.length() <= 3){ ResultSet rs = statement.executeQuery(); - if(rs.next() && rs.getInt("count(*)") == 0){ + if(rs.next() && rs.getInt("count(*)") == 0){ //if accountname is available continue break; } else{ - accName = getValidName("Account Name: "); + accName = getValidName("Account Name: "); //if not prompt for a new accountname and check again + //todo add help method for accountname } @@ -216,7 +243,8 @@ else if(ln.length() <= 3){ } } - String newAccQuery = "insert into account values (0, ?, ?, ?, ?, ?)"; + //Query to add account, ID is auto assigned + String newAccQuery = "INSERT INTO account (name, password, first_name, last_name, ssn) VALUES (?, ?, ?, ?, ?)"; try (PreparedStatement statement = connection.prepareStatement(newAccQuery)) { statement.setString(1, accName); @@ -227,7 +255,7 @@ else if(ln.length() <= 3){ statement.executeUpdate(); - + //todo check if new account is present System.out.println("\nAccount created"); @@ -237,8 +265,15 @@ else if(ln.length() <= 3){ } + + /** + * Updates password after prompting for an ID and new password + * @param connection + * @see #getValidInt(String) + * @see #getValidPassword(String) + */ private void updatePassword (Connection connection) { - int id = getValidInt("User id: "); + int id = getValidInt("User id: "); //todo add check if account is present String newPassword = getValidPassword("New password: "); String updatePwQuery = "update account set password = ? where user_id = ?"; @@ -249,6 +284,7 @@ private void updatePassword (Connection connection) { statement.executeUpdate(); + //todo add check if updated System.out.println("updated"); } catch (SQLException e) { @@ -258,8 +294,13 @@ private void updatePassword (Connection connection) { } + /** + * Deletes account after prompting for an ID + * @param connection + * @see #getValidInt(String) + */ private void deleteAccount(Connection connection) { - int id = getValidInt("User id: "); + int id = getValidInt("User id: "); //todo add check if account is present String deleteAccQuery = "delete from account where user_id = ?"; @@ -268,7 +309,7 @@ private void deleteAccount(Connection connection) { statement.executeUpdate(); - + //todo add check if deleted System.out.println("deleted"); @@ -277,6 +318,11 @@ private void deleteAccount(Connection connection) { } } + /** + * Help method to get a valid and positive integer from input + * @param prompt message to explain what is asked + * @return integer >= 0 + */ private int getValidInt(String prompt){ while(true){ try { @@ -296,6 +342,11 @@ private int getValidInt(String prompt){ } } + /** + * Help method to get a valid string that only contains letters from input + * @param prompt message to explain what is asked + * @return Capitalized string with only letters + */ private String getValidName(String prompt){ while(true){ System.out.println("\n" + prompt); @@ -313,6 +364,11 @@ else if(!Pattern.matches("^[a-zA-Z]+$", name)){ } } + /** + * Help method to get a correctly formated SSN from input + * @param prompt message to explain what is asked + * @return String with YYMMDD-XXXX formatting + */ private String getValidSSN(String prompt){ while(true){ System.out.println("\n" + prompt); @@ -330,6 +386,11 @@ else if(!Pattern.matches("^\\d{6}-\\d{4}$", ssn)){ } } + /** + * Help method to get a valid password from input + * @param prompt message to explain what is asked + * @return string of at least 6 characters + */ private String getValidPassword(String prompt){ while(true){ System.out.println("\n" + prompt); From d446fb7cf8a5507392410c502bee17f3c7e7c7c1 Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Fri, 12 Dec 2025 13:17:18 +0100 Subject: [PATCH 7/8] repo Signed-off-by: Jesper Larsson --- src/main/java/com/repo/AccountRepository.java | 4 ++++ src/main/java/com/repo/MoonMissionRepository.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 src/main/java/com/repo/AccountRepository.java create mode 100644 src/main/java/com/repo/MoonMissionRepository.java diff --git a/src/main/java/com/repo/AccountRepository.java b/src/main/java/com/repo/AccountRepository.java new file mode 100644 index 0000000..3c69209 --- /dev/null +++ b/src/main/java/com/repo/AccountRepository.java @@ -0,0 +1,4 @@ +package com.repo; + +public class AccountRepository { +} diff --git a/src/main/java/com/repo/MoonMissionRepository.java b/src/main/java/com/repo/MoonMissionRepository.java new file mode 100644 index 0000000..0dcbb12 --- /dev/null +++ b/src/main/java/com/repo/MoonMissionRepository.java @@ -0,0 +1,4 @@ +package com.repo; + +public class MoonMissionRepository { +} From c6038c205cea011a9e1c0a9fa9c1bf8db01394f3 Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Fri, 12 Dec 2025 15:53:28 +0100 Subject: [PATCH 8/8] Created and using repositories Signed-off-by: Jesper Larsson --- src/main/java/com/example/Main.java | 256 +++++++----------- .../SimpleDriverManagerDataSource.java | 68 +++++ src/main/java/com/repo/AccountRepo.java | 28 ++ src/main/java/com/repo/AccountRepository.java | 143 +++++++++- src/main/java/com/repo/DTO/AccountDTO.java | 10 + src/main/java/com/repo/DTO/MissionDTO.java | 13 + src/main/java/com/repo/MoonMissionRepo.java | 16 ++ .../java/com/repo/MoonMissionRepository.java | 82 +++++- 8 files changed, 462 insertions(+), 154 deletions(-) create mode 100644 src/main/java/com/example/SimpleDriverManagerDataSource.java create mode 100644 src/main/java/com/repo/AccountRepo.java create mode 100644 src/main/java/com/repo/DTO/AccountDTO.java create mode 100644 src/main/java/com/repo/DTO/MissionDTO.java create mode 100644 src/main/java/com/repo/MoonMissionRepo.java diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 0be81fc..7f6a30f 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -1,7 +1,15 @@ package com.example; +import com.repo.AccountRepository; +import com.repo.DTO.AccountDTO; +import com.repo.DTO.MissionDTO; +import com.repo.MoonMissionRepository; + +import javax.sql.DataSource; import java.sql.*; import java.util.Arrays; +import java.util.List; +import java.util.Optional; import java.util.Scanner; import java.util.regex.Pattern; @@ -28,74 +36,69 @@ public void run() { "as system properties (-Dkey=value) or environment variables."); } - try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) { + DataSource ds = new SimpleDriverManagerDataSource( + System.getProperty("APP_JDBC_URL"), + System.getProperty("APP_DB_USER"), + System.getProperty("APP_DB_PASS") + ); - boolean authorized = login(connection); - if (!authorized) { - System.out.println("Invalid username or password"); - System.out.println("press 0 to exit"); - while(true){ - String exit = scanner.nextLine().trim(); - if(exit.equals("0")){ - return; - } - } - } + AccountRepository ac = new AccountRepository(ds); + MoonMissionRepository mmc = new MoonMissionRepository(ds); - while(true) { - int option = promptMenu(); - switch (option) { - case 1 -> listMissions(connection); - case 2 -> getMission(connection); - case 3 -> missionsCountYear(connection); - case 4 -> createAccount(connection); - case 5 -> updatePassword(connection); - case 6 -> deleteAccount(connection); - case 0 -> { - return; - } + boolean authorized = login(ac); - default -> System.out.println("Invalid choice.\n"); + + if (!authorized) { + System.out.println("Invalid username or password"); + System.out.println("press 0 to exit"); + while(true){ + String exit = scanner.nextLine().trim(); + if(exit.equals("0")){ + return; } } + } + + + while(true) { + int option = promptMenu(); + + switch (option) { + case 1 -> listMissions(mmc); + case 2 -> getMission(mmc); + case 3 -> missionsCountYear(mmc); + case 4 -> createAccount(ac); + case 5 -> updatePassword(ac); + case 6 -> deleteAccount(ac); + case 0 -> { + return; + } - } catch (SQLException e) { - throw new RuntimeException(e); + default -> System.out.println("Invalid choice.\n"); + } } + + } /** * Prompts username and password, checks if the combination is present in accounts * - * @param connection * @return true if the name/password combo exists * false if either name/password isn't present */ - private boolean login(Connection connection) { + private boolean login(AccountRepository ac) { System.out.println("Username: "); String unm = scanner.nextLine(); System.out.println("Password: "); String pw = scanner.nextLine(); - //Query count if a row has the username and password combo, binary ensures case sensitivity - String accQuery = "select count(*) from account where binary name = ? and binary password = ?"; - try(PreparedStatement statement = connection.prepareStatement(accQuery)){ - statement.setString(1, unm); - statement.setString(2, pw); - - ResultSet rs = statement.executeQuery(); - - return rs.next() && rs.getInt("count(*)") == 1; //if the credentials are found count will return 1 - - - } catch (SQLException e) { - throw new RuntimeException(e); - } + return ac.matchCredentials(unm, pw); } /** @@ -119,87 +122,58 @@ private int promptMenu(){ /** * Lists all spacecraft from the moon_mission table - * @param connection */ - private void listMissions(Connection connection){ - String spaceshipQuery = "select spacecraft from moon_mission"; - try(PreparedStatement statement = connection.prepareStatement(spaceshipQuery)){ + private void listMissions(MoonMissionRepository mmc){ + List missions = mmc.getAllMissions(); - ResultSet rs = statement.executeQuery(); - while(rs.next()) { - System.out.println(rs.getString("spacecraft")); - } - } catch (SQLException e) { - throw new RuntimeException(e); + for(MissionDTO mission : missions){ + System.out.println(mission.spacecraft()); } } /** * Prompts for a mission ID and prints its data is available - * @param connection * @see #getValidInt(String) */ - private void getMission(Connection connection){ + private void getMission(MoonMissionRepository mmc){ int id = getValidInt("Mission Id: "); - String missionQuery = "select * from moon_mission where mission_id = ?"; - try(PreparedStatement statement = connection.prepareStatement(missionQuery)){ - statement.setInt(1, id); - - ResultSet rs = statement.executeQuery(); - - if(rs.next()) { - System.out.println( - "\nSpacecraft: " + rs.getString("spacecraft") + - "\nLaunch date: " + rs.getString("launch_date") + - "\nCarrier rocket: " + rs.getString("carrier_rocket") + - "\nOperator: " + rs.getString("operator") + - "\nMission type: " + rs.getString("mission_type") + - "\nOutcome: " + rs.getString("outcome")); - } - else { - System.out.println("\nMission not found."); - } - - } catch (SQLException e) { - throw new RuntimeException(e); + Optional mission = mmc.getMissionById(id); + if(mission.isPresent()) { + MissionDTO m = mission.get(); + System.out.println( + "\nSpacecraft: " + m.spacecraft() + + "\nLaunch date: " + m.launchDate() + + "\nCarrier rocket: " + m.carrierRocket() + + "\nOperator: " + m.operator() + + "\nMission type: " + m.missionType() + + "\nOutcome: " + m.outcome()); + } + else { + System.out.println("\nMission not found."); } } /** * Prompts for a year and prints how many rows in moon_mission was launched then - * @param connection * @see #getValidInt(String) */ - private void missionsCountYear(Connection connection){ - String missionYearQuery = "select count(*) from moon_mission where year(launch_date) = ?"; + private void missionsCountYear(MoonMissionRepository mmc){ int year = getValidInt("Mission Year: "); - try(PreparedStatement statement = connection.prepareStatement(missionYearQuery)){ - statement.setInt(1, year); - - ResultSet rs = statement.executeQuery(); - - while(rs.next()) { - System.out.println("\nMissions in " + year + ": " + rs.getInt("count(*)")); - } - - } catch (SQLException e) { - throw new RuntimeException(e); - } + System.out.println("\nMissions in " + year + ": " + mmc.missionCount(year)); } /** * Gives a flow for creating a new account, asking for First and Last name, SSN and password * default accountname is assigned if available and promted for if not - * @param connection * @see #getValidName(String) * @see #getValidSSN(String) * @see #getValidPassword(String) */ - private void createAccount(Connection connection) { + private void createAccount(AccountRepository ac) { String fn = getValidName("First Name: "); String ln = getValidName("Last Name: "); String ssn = getValidSSN("SSN: "); @@ -219,48 +193,31 @@ else if(ln.length() <= 3){ //if only last name is 3 or fewer le accName = fn.substring(0, 2) + ln.substring(0, 2); } - //Query to check if the accountname exists - String checkName = "select count(*) from account where name = ?"; + //Check if the accountname exists + Optional nameCheck; while(true) { - try (PreparedStatement statement = connection.prepareStatement(checkName)) { - statement.setString(1, accName); - - ResultSet rs = statement.executeQuery(); + nameCheck = ac.getAccountByName(accName); - - if(rs.next() && rs.getInt("count(*)") == 0){ //if accountname is available continue + if(nameCheck.isEmpty()){ //if accountname is available continue break; } else{ accName = getValidName("Account Name: "); //if not prompt for a new accountname and check again //todo add help method for accountname } - - - } catch (SQLException e) { - throw new RuntimeException(e); - } } - //Query to add account, ID is auto assigned - String newAccQuery = "INSERT INTO account (name, password, first_name, last_name, ssn) VALUES (?, ?, ?, ?, ?)"; - - try (PreparedStatement statement = connection.prepareStatement(newAccQuery)) { - statement.setString(1, accName); - statement.setString(2, pw); - statement.setString(3, fn); - statement.setString(4, ln); - statement.setString(5, ssn); + //Create and add account + AccountDTO newAccount = new AccountDTO(0, accName, pw, fn, ln, ssn); - statement.executeUpdate(); + ac.createAccount(newAccount); - //todo check if new account is present + if(ac.accountExists(accName)){ System.out.println("\nAccount created"); - - - } catch (SQLException e) { - throw new RuntimeException(e); + } + else{ + System.out.println("\nAccount creation failed."); } } @@ -268,53 +225,48 @@ else if(ln.length() <= 3){ //if only last name is 3 or fewer le /** * Updates password after prompting for an ID and new password - * @param connection * @see #getValidInt(String) * @see #getValidPassword(String) */ - private void updatePassword (Connection connection) { + private void updatePassword (AccountRepository ac) { int id = getValidInt("User id: "); //todo add check if account is present - String newPassword = getValidPassword("New password: "); - - String updatePwQuery = "update account set password = ? where user_id = ?"; - try (PreparedStatement statement = connection.prepareStatement(updatePwQuery)) { - statement.setString(1, newPassword); - statement.setInt(2, id); + if(ac.accountExists(id)){ + String newPassword = getValidPassword("New password: "); - statement.executeUpdate(); + ac.updatePassword(id, newPassword); - //todo add check if updated - System.out.println("updated"); - - } catch (SQLException e) { - throw new RuntimeException(e); + if(ac.matchCredentials(ac.getAccountByID(id).get().name(), newPassword)){ + System.out.println("Password updated"); + } + else { + System.out.println("Password update failed."); + } + } + else{ + System.out.println("Account not found."); } - } /** * Deletes account after prompting for an ID - * @param connection * @see #getValidInt(String) */ - private void deleteAccount(Connection connection) { - int id = getValidInt("User id: "); //todo add check if account is present - - String deleteAccQuery = "delete from account where user_id = ?"; - - try (PreparedStatement statement = connection.prepareStatement(deleteAccQuery)) { - statement.setInt(1, id); - - statement.executeUpdate(); + private void deleteAccount(AccountRepository ac) { + int id = getValidInt("User id: "); - //todo add check if deleted - System.out.println("deleted"); - - - } catch (SQLException e) { - throw new RuntimeException(e); + if(ac.accountExists(id)){ + ac.deleteAccount(id); + if(!ac.accountExists(id)){ + System.out.println("Account deleted"); + } + else { + System.out.println("Account deletion failed."); + } + } + else { + System.out.println("Account not found."); } } diff --git a/src/main/java/com/example/SimpleDriverManagerDataSource.java b/src/main/java/com/example/SimpleDriverManagerDataSource.java new file mode 100644 index 0000000..e0df2f5 --- /dev/null +++ b/src/main/java/com/example/SimpleDriverManagerDataSource.java @@ -0,0 +1,68 @@ +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() 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 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; + } +} diff --git a/src/main/java/com/repo/AccountRepo.java b/src/main/java/com/repo/AccountRepo.java new file mode 100644 index 0000000..12b6096 --- /dev/null +++ b/src/main/java/com/repo/AccountRepo.java @@ -0,0 +1,28 @@ +package com.repo; + + +import com.repo.DTO.AccountDTO; + +import java.util.List; +import java.util.Optional; + +public interface AccountRepo { + + Optional getAccountByName(String name); + + Optional getAccountByID(int userid); + + boolean accountExists(String name); + + boolean accountExists(int userid); + + boolean matchCredentials(String name, String password); + + void createAccount(AccountDTO account); + + void updatePassword(int userid, String password); + + void deleteAccount(int userid); + + List getAllAccounts(); +} diff --git a/src/main/java/com/repo/AccountRepository.java b/src/main/java/com/repo/AccountRepository.java index 3c69209..f3f1e5c 100644 --- a/src/main/java/com/repo/AccountRepository.java +++ b/src/main/java/com/repo/AccountRepository.java @@ -1,4 +1,145 @@ package com.repo; -public class AccountRepository { +import com.repo.DTO.AccountDTO; + +import javax.sql.DataSource; +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 AccountRepository implements AccountRepo{ + private final DataSource dataSource; + + public AccountRepository(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public Optional getAccountByName(String name) { + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("select * from account where name = ?")){ + ps.setString(1, name); + + ResultSet rs = ps.executeQuery(); + if(rs.next()){ + return Optional.of(mapAccount(rs)); + } + } + catch(SQLException e){ + throw new RuntimeException(e); + } + return Optional.empty(); + } + + @Override + public Optional getAccountByID(int userid) { + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("select * from account where user_id = ?")){ + ps.setInt(1, userid); + + ResultSet rs = ps.executeQuery(); + if(rs.next()){ + return Optional.of(mapAccount(rs)); + } + } + catch(SQLException e){ + throw new RuntimeException(e); + } + return Optional.empty(); + } + + @Override + public boolean accountExists(String name) { + return getAccountByName(name).isPresent(); + } + + @Override + public boolean accountExists(int userid) { + return getAccountByID(userid).isPresent(); + } + + @Override + public boolean matchCredentials(String name, String password) { + Optional account = getAccountByName(name); + + return account.isPresent() && account.get().password().equals(password); + } + + @Override + public void createAccount(AccountDTO account) { + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("insert into account (name, password, first_name, last_name, ssn) values (?, ?, ?, ?, ?)")){ + ps.setString(1, account.name()); + ps.setString(2, account.password()); + ps.setString(3, account.firstName()); + ps.setString(4, account.lastName()); + ps.setString(5, account.ssn()); + + ps.executeUpdate(); + } + catch(SQLException e){ + throw new RuntimeException(e); + } + } + + @Override + public void updatePassword(int userid, String password) { + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("update account set password = ? where user_id = ?")){ + ps.setString(1, password); + ps.setInt(2, userid); + + ps.executeUpdate(); + } + catch(SQLException e){ + throw new RuntimeException(e); + } + } + + @Override + public void deleteAccount(int userid) { + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("delete from account where user_id = ?")){ + ps.setInt(1, userid); + + ps.executeUpdate(); + } + catch(SQLException e){ + throw new RuntimeException(e); + } + } + + @Override + public List getAllAccounts() { + List accounts = new ArrayList<>(); + + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("select * from account")){ + + ResultSet rs = ps.executeQuery(); + while(rs.next()){ + accounts.add(mapAccount(rs)); + } + } + catch(SQLException e){ + throw new RuntimeException(e); + } + return accounts; + } + + private AccountDTO mapAccount(ResultSet rs) throws SQLException { + return new AccountDTO( + rs.getInt("user_id"), + rs.getString("name"), + rs.getString("password"), + rs.getString("first_name"), + rs.getString("last_name"), + rs.getString("ssn") + ); + } + } diff --git a/src/main/java/com/repo/DTO/AccountDTO.java b/src/main/java/com/repo/DTO/AccountDTO.java new file mode 100644 index 0000000..8849113 --- /dev/null +++ b/src/main/java/com/repo/DTO/AccountDTO.java @@ -0,0 +1,10 @@ +package com.repo.DTO; + +public record AccountDTO ( + int userid, + String name, + String password, + String firstName, + String lastName, + String ssn){ +} diff --git a/src/main/java/com/repo/DTO/MissionDTO.java b/src/main/java/com/repo/DTO/MissionDTO.java new file mode 100644 index 0000000..6c1a2e0 --- /dev/null +++ b/src/main/java/com/repo/DTO/MissionDTO.java @@ -0,0 +1,13 @@ +package com.repo.DTO; + +import java.time.LocalDate; + +public record MissionDTO ( + int missionid, + String spacecraft, + LocalDate launchDate, + String carrierRocket, + String operator, + String missionType, + String outcome){ +} diff --git a/src/main/java/com/repo/MoonMissionRepo.java b/src/main/java/com/repo/MoonMissionRepo.java new file mode 100644 index 0000000..6c7fab8 --- /dev/null +++ b/src/main/java/com/repo/MoonMissionRepo.java @@ -0,0 +1,16 @@ +package com.repo; + + +import com.repo.DTO.MissionDTO; + +import java.util.List; +import java.util.Optional; + +public interface MoonMissionRepo { + + List getAllMissions(); + + Optional getMissionById(int missionid); + + int missionCount(int year); +} diff --git a/src/main/java/com/repo/MoonMissionRepository.java b/src/main/java/com/repo/MoonMissionRepository.java index 0dcbb12..98338db 100644 --- a/src/main/java/com/repo/MoonMissionRepository.java +++ b/src/main/java/com/repo/MoonMissionRepository.java @@ -1,4 +1,84 @@ package com.repo; -public class MoonMissionRepository { +import com.repo.DTO.MissionDTO; + +import javax.sql.DataSource; +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 MoonMissionRepository implements MoonMissionRepo{ + private final DataSource dataSource; + + public MoonMissionRepository(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public List getAllMissions() { + List missions = new ArrayList<>(); + + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("select * from moon_mission")){ + + ResultSet rs = ps.executeQuery(); + while(rs.next()){ + missions.add(mapMission(rs)); + } + } + catch(SQLException e){ + throw new RuntimeException(e); + } + return missions; + } + + @Override + public Optional getMissionById(int missionid) { + try(Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement("select * from moon_mission where missionid = ?")){ + ps.setInt(1, missionid); + + ResultSet rs = ps.executeQuery(); + if(rs.next()){ + return Optional.of(mapMission(rs)); + } + } + catch(SQLException e){ + throw new RuntimeException(e); + } + return Optional.empty(); + } + + @Override + public int missionCount(int year) { + try (Connection c = dataSource.getConnection(); + PreparedStatement ps = c.prepareStatement( + "select count(*) from moon_mission where year(launch_date) = ?")) { + ps.setInt(1, year); + + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + return rs.getInt("count(*)"); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return 0; + } + + private MissionDTO mapMission(ResultSet rs) throws SQLException { + return new MissionDTO( + rs.getInt("mission_id"), + rs.getString("spacecraft"), + rs.getDate("launch_date").toLocalDate(), + rs.getString("carrier_rocket"), + rs.getString("operator"), + rs.getString("mission_type"), + rs.getString("outcome") + ); + } }