Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
314 changes: 292 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,293 @@ 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());
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.


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
Copy Markdown

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. Please enter a number");
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
Copy Markdown

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;
}
int year = Integer.parseInt(scanner.nextLine());

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);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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) { throw new IllegalArgumentException("First name must be at least 3 characters long."); }
System.out.print("Enter last name: ");
if (!scanner.hasNextLine()) {return;}
String lastName = scanner.nextLine().trim();
if (lastName.length() < 3) { throw new IllegalArgumentException("First name must be at least 3 characters long."); }
System.out.print("Enter ssn: ");
String ssn = scanner.nextLine().trim();
System.out.print("Enter password: ");
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());
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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());
}
}
Comment on lines +291 to +328
Copy link
Copy Markdown

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());
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Loading