Skip to content

Dev#15

Open
gurkvatten wants to merge 8 commits intomainfrom
dev
Open

Dev#15
gurkvatten wants to merge 8 commits intomainfrom
dev

Conversation

@gurkvatten
Copy link

@gurkvatten gurkvatten commented Dec 12, 2025

Summary by CodeRabbit

  • New Features

    • User authentication with login flow.
    • Interactive menu-driven CLI for mission data and account management.
    • Mission operations: list missions, fetch by ID, count by year.
    • Account operations: create accounts, update passwords, delete accounts.
  • Documentation

    • Added a badge to the README.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Walkthrough

Adds an interactive CLI to Main.java: a login prompt, a persistent text menu, and multiple database-backed operations for moon missions and account CRUD; also adds a badge to README.md. No public API or exported declarations were changed.

Changes

Cohort / File(s) Summary
Documentation
README.md
Inserted a markdown badge image/link at the top of the file.
CLI, Auth & DB logic
src/main/java/com/example/Main.java
Replaced specific SQL imports with java.sql.*, added java.util.Scanner, implemented a login loop (isValidLogin), a persistent menu (printMenu) and CLI handlers: listMoonMissions, getMoonMissionById, countMissionsByYear, createAccount, updateAccountPassword, deleteAccount. These methods run SQL queries against account and moon_mission tables and integrate with the Scanner-based input loop.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as Main (CLI)
    participant DB as Database

    User->>CLI: Start app
    CLI->>User: Prompt username/password
    User->>CLI: Enter credentials
    CLI->>DB: SELECT account WHERE name=? AND password=?
    DB-->>CLI: account row / no row
    alt Auth success
        CLI-->>User: Login accepted
        loop Menu loop
            CLI->>User: Show menu
            User->>CLI: Choose operation
            alt List missions
                CLI->>DB: SELECT * FROM moon_mission
                DB-->>CLI: mission rows
            else Get mission by id
                CLI->>DB: SELECT * FROM moon_mission WHERE mission_id=?
                DB-->>CLI: mission row
            else Count by year
                CLI->>DB: SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date)=?
                DB-->>CLI: count
            else Create/Update/Delete account
                CLI->>DB: INSERT/UPDATE/DELETE account ...
                DB-->>CLI: success / affected rows
            end
            CLI->>User: Display result
        end
    else Auth failed
        CLI-->>User: Prompt again or exit
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to SQL injection risk where Scanner input is interpolated into queries.
  • Verify password handling (plaintext vs hashed) and authentication edge cases.
  • Ensure JDBC resources (Connection, Statement, ResultSet) and Scanner are closed in all paths.
  • Check input validation and error handling consistency across the new methods.

Poem

🐰 I hopped into the CLI night,
typed a name and typed a key,
I fetched the moons and counted lights,
created users—one, two, three!
A badge above, the menu bright—hop on, explore with me. 🎋

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Dev' is vague and generic, providing no meaningful information about the actual changes in the pull request. Replace with a descriptive title that summarizes the main change, such as 'Add user authentication and database CLI menu system' or 'Implement login flow and CRUD operations for moon missions database'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/example/Main.java (1)

9-14: Fix JVM entry point: main must be public static.
As written (static void main), running the class via java ... com.example.Main will fail.

-    static void main(String[] args) {
+    public static void main(String[] args) {
         if (isDevMode(args)) {
             DevDatabaseInitializer.start();
         }
         new Main().run();
     }
🧹 Nitpick comments (1)
src/main/java/com/example/Main.java (1)

3-6: Avoid java.sql.* wildcard import unless your style guide prefers it.
Not blocking, but explicit imports reduce accidental name collisions as the file grows.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e629e94 and 28d8d43.

📒 Files selected for processing (2)
  • README.md (1 hunks)
  • src/main/java/com/example/Main.java (3 hunks)
🔇 Additional comments (2)
README.md (1)

1-1: Badge addition looks fine.
No functional/behavior change; ok to merge.

src/main/java/com/example/Main.java (1)

28-85: [rewritten comment]
[classification tag]

Comment on lines +103 to 143
private void listMoonMissions(Connection conn) throws SQLException {
String query = "SELECT * FROM moon_mission";
try (PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {

while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
}
}
}

private void getMoonMissionById(Connection conn, String id) throws SQLException {
String query = "SELECT * FROM moon_mission WHERE mission_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
String dateStr = rs.getString("launch_date");
String year = (dateStr != null && dateStr.length() >= 4) ? dateStr.substring(0, 4) : "N/A";

System.out.println("Mission: " + rs.getString("spacecraft") +
", Year: " + year);
} else {
System.out.println("Mission not found.");
}
}
}
}

private void countMissionsByYear(Connection conn, String year) throws SQLException {
String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, year);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int count = rs.getInt(1);
System.out.println("Missions in " + year + ": " + count);
}
}
}
}
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

Make mission queries deterministic + avoid SELECT *.
SELECT * and missing ORDER BY can make outputs flaky (and unnecessarily reads columns).

-        String query = "SELECT * FROM moon_mission";
+        String query = "SELECT spacecraft FROM moon_mission ORDER BY mission_id";
@@
-        String query = "SELECT * FROM moon_mission WHERE mission_id = ?";
+        String query = "SELECT spacecraft, YEAR(launch_date) AS launch_year FROM moon_mission WHERE mission_id = ?";
@@
-                    String dateStr = rs.getString("launch_date");
-                    String year = (dateStr != null && dateStr.length() >= 4) ? dateStr.substring(0, 4) : "N/A";
-
-                    System.out.println("Mission: " + rs.getString("spacecraft") +
-                            ", Year: " + year);
+                    String year = rs.getString("launch_year");
+                    System.out.println("Mission: " + rs.getString("spacecraft") + ", Year: " + (year == null ? "N/A" : year));
@@
-        String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
+        String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
         try (PreparedStatement stmt = conn.prepareStatement(query)) {
-            stmt.setString(1, year);
+            stmt.setInt(1, Integer.parseInt(year.trim()));

(If the tests feed non-numeric years, wrap the parseInt with a friendly error message instead of throwing.)

📝 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 listMoonMissions(Connection conn) throws SQLException {
String query = "SELECT * FROM moon_mission";
try (PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
}
}
}
private void getMoonMissionById(Connection conn, String id) throws SQLException {
String query = "SELECT * FROM moon_mission WHERE mission_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
String dateStr = rs.getString("launch_date");
String year = (dateStr != null && dateStr.length() >= 4) ? dateStr.substring(0, 4) : "N/A";
System.out.println("Mission: " + rs.getString("spacecraft") +
", Year: " + year);
} else {
System.out.println("Mission not found.");
}
}
}
}
private void countMissionsByYear(Connection conn, String year) throws SQLException {
String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, year);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int count = rs.getInt(1);
System.out.println("Missions in " + year + ": " + count);
}
}
}
}
private void listMoonMissions(Connection conn) throws SQLException {
String query = "SELECT spacecraft FROM moon_mission ORDER BY mission_id";
try (PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
}
}
}
private void getMoonMissionById(Connection conn, String id) throws SQLException {
String query = "SELECT spacecraft, YEAR(launch_date) AS launch_year FROM moon_mission WHERE mission_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
String year = rs.getString("launch_year");
System.out.println("Mission: " + rs.getString("spacecraft") + ", Year: " + (year == null ? "N/A" : year));
} else {
System.out.println("Mission not found.");
}
}
}
}
private void countMissionsByYear(Connection conn, String year) throws SQLException {
String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setInt(1, Integer.parseInt(year.trim()));
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int count = rs.getInt(1);
System.out.println("Missions in " + year + ": " + count);
}
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 103 to 143, replace the
three uses of "SELECT *" with explicit column lists (e.g., mission_id,
spacecraft, launch_date) and change listMoonMissions to include a deterministic
ORDER BY (e.g., ORDER BY mission_id or ORDER BY launch_date) so results are
stable; update result-set access to use those explicit column names instead of
relying on *; for countMissionsByYear change the query to accept an integer year
(e.g., "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?") but
validate/parse the input year first with a try/catch that prints a friendly
error if non-numeric, then call stmt.setInt(1, parsedYear); ensure all
PreparedStatements/ResultSets still use try-with-resources.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
src/main/java/com/example/Main.java (4)

103-112: Past review feedback not yet addressed.

The issues with SELECT * and missing ORDER BY in this method were already flagged in the previous review. Please refer to the earlier comment for the recommended fixes.


132-143: Past review feedback not yet addressed.

The type mismatch issue (setString should be setInt for year parameter) was already flagged in the previous review. Please refer to the earlier comment for the recommended fix.


232-243: Past review feedback not yet addressed.

The optimization to use SELECT 1 instead of SELECT * for the login check was already suggested in the previous review. This is a minor performance optimization.


114-130: Use correct parameter type for mission_id.

Line 117 uses setString for mission_id, which is likely an integer column. This may work due to implicit type conversion but is semantically incorrect and can fail with strict type checking.

Additionally, the issues with SELECT * and manual year extraction were already flagged in the previous review.

Apply this diff to fix the type issue:

     private void getMoonMissionById(Connection conn, String id) throws SQLException {
         String query = "SELECT * FROM moon_mission WHERE mission_id = ?";
         try (PreparedStatement stmt = conn.prepareStatement(query)) {
-            stmt.setString(1, id);
+            stmt.setInt(1, Integer.parseInt(id.trim()));
             try (ResultSet rs = stmt.executeQuery()) {

Note: Add input validation with try-catch to handle non-numeric input gracefully.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28d8d43 and 68302ff.

📒 Files selected for processing (1)
  • src/main/java/com/example/Main.java (3 hunks)
🔇 Additional comments (1)
src/main/java/com/example/Main.java (1)

162-172: Good fix: name column now populated correctly.

The INSERT statement now properly includes the name column, which resolves the critical login issue flagged in the previous review. The use of PreparedStatement and informing the user of their generated username is well-handled.

Comment on lines +155 to +160
String generatedName = "";
if (first.length() >= 3 && last.length() >= 3) {
generatedName = first.substring(0, 3) + last.substring(0, 3);
} else {
generatedName = first + last;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Simplify name generation to match init.sql pattern.

The conditional logic creates inconsistent usernames for short names. When either name is shorter than 3 characters, it concatenates the full names, which differs from the init.sql pattern that always takes up to 3 characters.

Apply this diff to ensure consistent behavior:

-        String generatedName = "";
-        if (first.length() >= 3 && last.length() >= 3) {
-            generatedName = first.substring(0, 3) + last.substring(0, 3);
-        } else {
-            generatedName = first + last;
-        }
+        String generatedName = first.substring(0, Math.min(3, first.length())) + 
+                               last.substring(0, Math.min(3, last.length()));

This ensures names like "Al Smith" generate "AlSmi" instead of "AlSmith".

📝 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
String generatedName = "";
if (first.length() >= 3 && last.length() >= 3) {
generatedName = first.substring(0, 3) + last.substring(0, 3);
} else {
generatedName = first + last;
}
String generatedName = first.substring(0, Math.min(3, first.length())) +
last.substring(0, Math.min(3, last.length()));
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 155 to 160, the conditional
currently concatenates full names when either part is shorter than 3 chars,
which diverges from init.sql’s pattern; replace the if/else with logic that
always takes up to the first three characters of each name (e.g., use
substring(0, Math.min(3, first.length())) and substring(0, Math.min(3,
last.length()))), concatenate those two parts, and assign to generatedName
(ensure you trim inputs and handle null/empty safely if needed).

Comment on lines +175 to +189
private void updateAccountPassword(Connection conn, Scanner sc) throws SQLException {
System.out.print("Enter user_id: ");
String id = sc.nextLine();
System.out.print("Enter new password: ");
String pass = sc.nextLine();

String query = "UPDATE account SET password = ? WHERE user_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, pass);
stmt.setString(2, id);
int rows = stmt.executeUpdate();
if (rows > 0) System.out.println("Password updated.");
else System.out.println("User not found.");
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing authorization check allows privilege escalation.

Any authenticated user can change anyone's password by providing any user_id. This is a severe security vulnerability.

At minimum, verify the logged-in user is modifying their own account:

     private void updateAccountPassword(Connection conn, Scanner sc) throws SQLException {
         System.out.print("Enter user_id: ");
         String id = sc.nextLine();
         System.out.print("Enter new password: ");
         String pass = sc.nextLine();
 
-        String query = "UPDATE account SET password = ? WHERE user_id = ?";
+        // TODO: Pass the authenticated username to this method and verify user_id matches
+        String query = "UPDATE account SET password = ? WHERE user_id = ? AND name = ?";
         try (PreparedStatement stmt = conn.prepareStatement(query)) {
             stmt.setString(1, pass);
             stmt.setString(2, id);
+            stmt.setString(3, authenticatedUsername); // Verify ownership
             int rows = stmt.executeUpdate();

Alternatively, remove the user_id prompt entirely and derive it from the authenticated session.

Additionally, user_id is likely an integer—use setInt(2, Integer.parseInt(id)) instead of setString.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 175 to 189, the
updateAccountPassword method allows any authenticated user to change any account
by accepting a user_id input and using it directly; to fix this, remove or
ignore the user_id input and derive the target user_id from the authenticated
session (or at minimum validate the supplied id equals the currently
authenticated user's id) before performing the update, and change the
PreparedStatement parameter for user_id to use setInt with Integer.parseInt(id)
(with proper NumberFormatException handling) so the type matches the DB; also
add an authorization failure message and return early when the session user does
not match the target.

Comment on lines +190 to +201
private void deleteAccount(Connection conn, Scanner sc) throws SQLException {
System.out.print("Enter user_id: ");
String id = sc.nextLine();

String query = "DELETE FROM account WHERE user_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
int rows = stmt.executeUpdate();
if (rows > 0) System.out.println("Account deleted.");
else System.out.println("User not found.");
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing authorization check and confirmation for destructive operation.

This method has two critical issues:

  1. Any authenticated user can delete any account by providing the user_id (privilege escalation).
  2. No confirmation prompt for this destructive operation.

Add authorization and confirmation:

     private void deleteAccount(Connection conn, Scanner sc) throws SQLException {
         System.out.print("Enter user_id: ");
         String id = sc.nextLine();
+        
+        System.out.print("Are you sure you want to delete this account? (yes/no): ");
+        String confirm = sc.nextLine();
+        if (!confirm.equalsIgnoreCase("yes")) {
+            System.out.println("Delete cancelled.");
+            return;
+        }
 
-        String query = "DELETE FROM account WHERE user_id = ?";
+        // TODO: Pass authenticated username and verify ownership
+        String query = "DELETE FROM account WHERE user_id = ? AND name = ?";
         try (PreparedStatement stmt = conn.prepareStatement(query)) {
-            stmt.setString(1, id);
+            stmt.setInt(1, Integer.parseInt(id));
+            stmt.setString(2, authenticatedUsername); // Verify ownership
             int rows = stmt.executeUpdate();

Consider whether account deletion should even be self-service, or if it should require admin privileges.

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant