Conversation
WalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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:mainmust bepublic static.
As written (static void main), running the class viajava ... com.example.Mainwill 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: Avoidjava.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
📒 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]
| 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); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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 missingORDER BYin 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 (
setStringshould besetIntfor 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 1instead ofSELECT *for the login check was already suggested in the previous review. This is a minor performance optimization.
114-130: Use correct parameter type formission_id.Line 117 uses
setStringformission_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
📒 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:namecolumn now populated correctly.The
INSERTstatement now properly includes thenamecolumn, which resolves the critical login issue flagged in the previous review. The use ofPreparedStatementand informing the user of their generated username is well-handled.
| String generatedName = ""; | ||
| if (first.length() >= 3 && last.length() >= 3) { | ||
| generatedName = first.substring(0, 3) + last.substring(0, 3); | ||
| } else { | ||
| generatedName = first + last; | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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).
| 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."); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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."); | ||
| } | ||
| } |
There was a problem hiding this comment.
Critical: Missing authorization check and confirmation for destructive operation.
This method has two critical issues:
- Any authenticated user can delete any account by providing the
user_id(privilege escalation). - 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.
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.