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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<artifactId>mysql</artifactId>
<version>1.21.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>2.0.17</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
333 changes: 311 additions & 22 deletions src/main/java/com/example/Main.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.example;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.*;
import java.util.Arrays;
import java.util.Scanner;


public class Main {

Expand All @@ -14,25 +14,6 @@ static void main(String[] args) {
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.");
}

try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) {
} catch (SQLException e) {
throw new RuntimeException(e);
}
//Todo: Starting point for your code
}

/**
* Determines if the application is running in development mode based on system properties,
* environment variables, or command-line arguments.
Expand All @@ -59,4 +40,312 @@ private static String resolveConfig(String propertyKey, String envKey) {
}
return (v == null || v.trim().isEmpty()) ? null : v.trim();
}

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.");
}

try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass);
Scanner scanner = new Scanner(System.in)) {

String loggedInUser = authenticateUser(connection, scanner);
if (loggedInUser == null) {
System.out.println("Invalid username or password. Exiting");
return;
}
System.out.println("\nWelcome, " + loggedInUser + "!");
boolean running = true;
while (running) {
System.out.println("""

Press a number for next assignment:

1) List moon missions.
2) Get a moon mission.
3) Count missions for a given year.
4) Create an account.
5) Update an account password.
6) Delete an account.
0) Exit.
""");

System.out.print("Choose (0-6): ");
if (!scanner.hasNextLine()) {
running = false;
break;
}
String choiceStr = scanner.nextLine().trim();

int choice;
try {
choice = Integer.parseInt(choiceStr);
} catch (NumberFormatException e) {
System.out.println("Invalid input. Please enter a number (0-6).");
continue;
}

switch (choice) {
case 0:
System.out.println("Exiting application. Goodbye!");
running = false;
break;
case 1:
System.out.println("Executing: 1) List moon missions...");
listMoonMissions(connection);
break;
case 2:
System.out.println("Executing: 2) Get a moon mission by mission_id...");
moonMissionsById(connection, scanner);
break;
case 3:
System.out.println("Executing: 3) Count missions for a given year...");
countingMissionsForAGivenYear(connection, scanner);
break;
case 4:
System.out.println("Executing: 4) Create an account...");
createAnAccount(connection, scanner);
break;
case 5:
System.out.println("Executing: 5) Update an account password...");
updateAccountPassword(connection, scanner);
break;
case 6:
System.out.println("Executing: 6) Delete an account...");
deleteAccount(connection, scanner);
break;
default:
System.out.println("Invalid choice. Please enter a number between 0 and 6.");
}
}

} catch (SQLException e) {
throw new RuntimeException("Database operation failed. " + e.getMessage(), e);
}
}


private String authenticateUser(Connection connection, Scanner scanner) {
System.out.print("Username: ");
if (!scanner.hasNextLine()) {
return null; // Hantera EOF i testmiljön
}
String username = scanner.nextLine().trim();

System.out.print("Password: ");
if (!scanner.hasNextLine()) {
return null; // Hantera EOF i testmiljön
}
String password = scanner.nextLine().trim();

String sql = " select name from account where name = ? and password = ? ";

try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, username);
stmt.setString(2, password);

try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getString("name");
} else {
return null;
}
}
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
Comment on lines +135 to +164
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Plaintext password authentication + weak exception context

  • Comparing password = ? suggests plaintext storage/comparison; please hash + salt passwords (and compare hashes), even for a CLI.
  • Also, Line 162 throws new RuntimeException(ex) with no context—hard to debug.
-        } catch (SQLException ex) {
-            throw new RuntimeException(ex);
+        } catch (SQLException ex) {
+            throw new RuntimeException("Error authenticating user.", ex);
         }
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 135 to 164, the method
currently selects by plaintext password and throws a RuntimeException with no
context; change the query to fetch the stored password hash by username only
(e.g., "select name, password_hash from account where name = ?"), remove
password from the WHERE clause, then compute/verify the provided password
against the stored salted hash using a secure algorithm/library (bcrypt or
Argon2) and return the username only on successful verification; also replace
the bare throw new RuntimeException(ex) with a more informative exception or log
message that includes context (e.g., "Failed to authenticate user: " + username)
while preserving the original exception as the cause.



private void listMoonMissions(Connection connection) {
String sql = " select spacecraft from moon_mission order by spacecraft ";
try (PreparedStatement stmt = connection.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
boolean found = false;
while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
found = true;
}
if (!found) {
System.out.println("No moon missions found.");
}

} catch (SQLException e) {
throw new RuntimeException("Error executing List Moon Missions: " + e.getMessage(), e);
}
}

private void moonMissionsById(Connection connection, Scanner scanner) {
System.out.println("Enter moon mission id: ");
if (!scanner.hasNextLine()) {
return;
}
String missionId = scanner.nextLine().trim();
long id;
try {
id = Long.parseLong(missionId);
} catch (NumberFormatException e) {
System.out.println("Invalid moon mission id.");
return;
}

String sql = "select spacecraft, mission_id, mission_type, launch_date from moon_mission where mission_id = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setLong(1, id);

try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
System.out.println("ID: " + rs.getLong("mission_id"));
System.out.println("Spacecraft: " + rs.getString("spacecraft"));
System.out.println("Mission type: " + rs.getString("mission_type"));
System.out.println("Launch date: " + rs.getString("launch_date"));
} else {
System.out.println("Mission id " + missionId + " not found. Please enter a number");
}
}

} catch (SQLException e) {
throw new RuntimeException(e);
}
}

Comment on lines +185 to +218
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

moonMissionsById: improve error messaging + keep SQL exception context

  • Line 210 message says “Please enter a number” when the ID was a number but wasn’t found.
  • Line 215 drops context.
-                } else {
-                    System.out.println("Mission id " + missionId + " not found. Please enter a number");
+                } else {
+                    System.out.println("Mission id " + missionId + " not found.");
                 }
...
-        } catch (SQLException e) {
-            throw new RuntimeException(e);
+        } catch (SQLException e) {
+            throw new RuntimeException("Error fetching moon mission by id=" + id, e);
         }
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 185-218, update the
user-facing "not found" message and preserve SQL exception context: change the
"Mission id ... not found. Please enter a number" line to a clear not-found
message (e.g., "Mission id X not found.") without the misleading "Please enter a
number" text, and replace the catch(SQLException e) throw with a
RuntimeException that includes context about the query (for example: throw new
RuntimeException("Failed to query moon mission with id " + id, e)) so the
original SQLException is preserved as the cause.

private void countingMissionsForAGivenYear(Connection connection, Scanner scanner) {
System.out.println("Enter year: ");
if (!scanner.hasNextLine()) {
System.out.println("Invalid year");
return;
}
String yearString = scanner.nextLine().trim();
int year;
try {
year = Integer.parseInt(yearString);
} catch (NumberFormatException e) {
System.out.println("Invalid year. Please enter a number (e.g. 1987).");
return;
}

String sql = " select count(*) as mission_count from moon_mission where year(launch_date) = ?";

try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setInt(1, year);

try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int count = rs.getInt("mission_count");
System.out.println("Mission count for year: " + year);
System.out.println("Number of moon missions: " + count);
} else
System.out.println("No moon missions for year: " + year);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

private void createAnAccount(Connection connection, Scanner scanner) {
System.out.println("Creating an account...");
System.out.print("Enter first name: ");
if (!scanner.hasNextLine()) {return;}
String firstName = scanner.nextLine().trim();
if (firstName.length() < 3) {
System.out.println("First name must be at least 3 characters long."); return; }
System.out.print("Enter last name: ");
if (!scanner.hasNextLine()) {return;}
String lastName = scanner.nextLine().trim();
if (lastName.length() < 3) {
System.out.println("Last name must be at least 3 characters long."); return; }
System.out.print("Enter ssn: ");
if (!scanner.hasNextLine()) {return;}
String ssn = scanner.nextLine().trim();
System.out.print("Enter password: ");
if (!scanner.hasNextLine()) {return;}
String password = scanner.nextLine().trim();
String name = firstName.substring(0, 3) + lastName.substring(0, 3);

String sql = "INSERT INTO account (first_name, last_name, ssn, password, name) VALUES (?, ?, ?, ?, ?)";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, firstName);
stmt.setString(2, lastName);
stmt.setString(3, ssn);
stmt.setString(4, password);
stmt.setString(5, name);

int affectedRows = stmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("Successfully created an account for " + firstName + " " + lastName);
} else
System.out.println("Failed to create an account.");

} catch (SQLException e) {
throw new RuntimeException("Database operation failed. " + e.getMessage());
}
}

private void updateAccountPassword(Connection connection, Scanner scanner) {
System.out.println("Enter user_id: ");
if (!scanner.hasNextLine()) {
return;
}
String userIdStr = scanner.nextLine().trim();
long userId;
try {
userId = Long.parseLong(userIdStr);
} catch (NumberFormatException e) {
System.out.println("Invalid user_id. Please enter a number");
return;
}

System.out.println("Enter new password: ");
if (!scanner.hasNextLine()) {
return;
}
String newPassword = scanner.nextLine();

String sql = " update account set password = ? where user_id = ? ";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, newPassword);
stmt.setLong(2, userId);

int affectedRows = stmt.executeUpdate();

if (affectedRows > 0) {
System.out.println("Account updated successfully.");
} else {
System.out.println("Failed to update account.");
}

} catch (SQLException e) {
System.err.println("ERROR: Failed to update password.");
throw new RuntimeException("Database operation failed. " + e.getMessage());
}
}
Comment on lines +291 to +328
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

updateAccountPassword: validate user_id, trim inputs, and preserve SQL cause

-        String userId = scanner.nextLine();
+        String userId = scanner.nextLine().trim();
+        try {
+            Long.parseLong(userId);
+        } catch (NumberFormatException e) {
+            System.out.println("Invalid user_id. Please enter a number.");
+            return;
+        }
...
-        String newPassword = scanner.nextLine();
+        String newPassword = scanner.nextLine().trim();
...
-        } catch (SQLException e) {
-            System.err.println("ERROR: Failed to update password.");
-            throw new RuntimeException("Database operation failed. " + e.getMessage());
+        } catch (SQLException e) {
+            System.err.println("ERROR: Failed to update password.");
+            throw new RuntimeException("Database operation failed. " + e.getMessage(), e);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void updateAccountPassword(Connection connection, Scanner scanner) {
System.out.println("Enter user_id: ");
if (!scanner.hasNextLine()) {
return;
}
String userId = scanner.nextLine();
System.out.println("Enter new password: ");
if (!scanner.hasNextLine()) {
return;
}
String newPassword = scanner.nextLine();
String sql = " update account set password = ? where user_id = ? ";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, newPassword);
stmt.setString(2, userId);
int affectedRows = stmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("Account updated successfully.");
} else {
System.out.println("Failed to update account.");
}
} catch (SQLException e) {
System.err.println("ERROR: Failed to update password.");
throw new RuntimeException("Database operation failed. " + e.getMessage());
}
}
private void updateAccountPassword(Connection connection, Scanner scanner) {
System.out.println("Enter user_id: ");
if (!scanner.hasNextLine()) {
return;
}
String userId = scanner.nextLine().trim();
try {
Long.parseLong(userId);
} catch (NumberFormatException e) {
System.out.println("Invalid user_id. Please enter a number.");
return;
}
System.out.println("Enter new password: ");
if (!scanner.hasNextLine()) {
return;
}
String newPassword = scanner.nextLine().trim();
String sql = " update account set password = ? where user_id = ? ";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, newPassword);
stmt.setString(2, userId);
int affectedRows = stmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("Account updated successfully.");
} else {
System.out.println("Failed to update account.");
}
} catch (SQLException e) {
System.err.println("ERROR: Failed to update password.");
throw new RuntimeException("Database operation failed. " + e.getMessage(), e);
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 280 to 309, the
updateAccountPassword method must trim inputs, validate user_id and new
password, and preserve the original SQLException as the cause when rethrowing;
trim userId and newPassword after reading them, check that userId is not blank
and meets the expected format (adjust pattern to your app — e.g. non-empty or
numeric/UUID) and that newPassword is not blank (return early with a clear
message if validation fails), then proceed to prepare and execute the statement
as before, and in the catch block rethrow a RuntimeException that includes the
SQLException as the cause (new RuntimeException("Database operation failed", e))
rather than just concatenating the message.


private void deleteAccount(Connection connection, Scanner scanner) {
System.out.println("Enter user id, that you wish to delete: ");
if (!scanner.hasNextLine()) {
return;
}
String userId = scanner.nextLine();

String sql = " delete from account where user_id = ? ";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, userId);

int affectedRows = stmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("Successfully deleted the account.");
} else {
System.out.println("Failed to delete the account.");
}
} catch (SQLException e) {
throw new RuntimeException("Database operation failed. " + e.getMessage());
}
}
}
Loading