Skip to content

Feature/jdbc cli#18

Open
Ericthilen wants to merge 10 commits intomainfrom
feature/jdbc-cli
Open

Feature/jdbc cli#18
Ericthilen wants to merge 10 commits intomainfrom
feature/jdbc-cli

Conversation

@Ericthilen
Copy link

@Ericthilen Ericthilen commented Dec 14, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added interactive login system with user authentication.
    • Introduced account management: create accounts, update passwords, and delete accounts.
    • Added command-line menu for mission exploration: list all missions, search by ID, and filter by year.
    • Improved user experience with persistent authenticated menu and graceful error handling.
  • Chores

    • Updated logging dependency configuration.
    • Enhanced README with additional documentation badge.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • databas-jdbc-Ericthilen.zip is excluded by !**/*.zip

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR introduces a data-access layer using the repository pattern with two new repository classes (AccountRepository, MoonMissionRepository) and a custom DataSource implementation. The Main class is refactored to instantiate these repositories and provide an interactive CLI for user login and database operations. A logging dependency is added to pom.xml.

Changes

Cohort / File(s) Summary
Documentation & Dependencies
README.md, pom.xml
Added review assignment badge to README; added slf4j-nop v2.0.17 dependency to Maven.
Data Access Layer
src/main/java/com/example/AccountRepository.java, src/main/java/com/example/MoonMissionRepository.java, src/main/java/com/example/SimpleDriverManagerDataSource.java
Introduced repository pattern: AccountRepository provides login, createAccount (with retry on duplicate username), updatePassword, and deleteAccount; MoonMissionRepository provides listMissions, getMissionById, and countByYear; SimpleDriverManagerDataSource implements javax.sql.DataSource for connection management via DriverManager.
Application Entry Point
src/main/java/com/example/Main.java
Made main method public, added DataSource setup using SimpleDriverManagerDataSource, instantiated repository objects, replaced direct DB calls with repository-based operations, and introduced interactive CLI with login prompt and persistent menu (list/fetch missions, count by year, account management, user switching).
Miscellaneous
src/main/java/com/example/DevDatabaseInitializer.java, src/test/com/example/com/example/CliAppIT.java
Minor formatting changes: trailing newline adjustments (no functional changes).

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as Main (CLI)
    participant AcctRepo as AccountRepository
    participant DataSource as SimpleDriverManagerDataSource
    participant DB as Database
    participant MissionRepo as MoonMissionRepository

    User->>CLI: Start application
    CLI->>CLI: Initialize SimpleDriverManagerDataSource
    CLI->>User: Display login prompt
    User->>CLI: Enter username & password
    CLI->>AcctRepo: login(username, password)
    AcctRepo->>DataSource: getConnection()
    DataSource->>DB: DriverManager.getConnection()
    DB-->>DataSource: Connection
    DataSource-->>AcctRepo: Connection
    AcctRepo->>DB: SELECT * FROM accounts (via PreparedStatement)
    DB-->>AcctRepo: ResultSet or empty
    AcctRepo-->>CLI: true/false
    
    alt Login Successful
        CLI->>User: Display main menu
        User->>CLI: Select operation (e.g., list missions)
        CLI->>MissionRepo: listMissions()
        MissionRepo->>DataSource: getConnection()
        DataSource->>DB: DriverManager.getConnection()
        DB-->>DataSource: Connection
        DataSource-->>MissionRepo: Connection
        MissionRepo->>DB: SELECT spacecraft FROM moon_mission
        DB-->>MissionRepo: ResultSet
        MissionRepo->>User: Print missions to stdout
        MissionRepo-->>CLI: Complete
        CLI->>User: Redisplay menu
    else Login Failed
        CLI->>User: Display error message
        CLI->>CLI: Exit or retry
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Main.java: Significant refactoring with new interactive CLI loop, Scanner usage, and error handling across multiple paths
  • AccountRepository.java: Retry logic in createAccount with SQL state/message checking for duplicate constraints; ensure error handling is robust
  • SimpleDriverManagerDataSource.java: Verify DataSource interface compliance and that unsupported operations appropriately raise UnsupportedOperationException
  • Repository resource management: Confirm all Connection, PreparedStatement, and ResultSet are properly managed via try-with-resources across all three repository classes

Poem

🐰 A hop through code, so neat and clean,
Repositories reign where once was seen,
Direct SQL calls now tucked away,
DataSource flows and menus play!
Login, query, switch with glee—
A CLI dance for all to see! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.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 'Feature/jdbc cli' is vague and uses a generic prefix format that doesn't clearly describe the main change; it lacks specificity about what functionality was added or modified. Use a more descriptive title that highlights the primary change, such as 'Add JDBC-based CLI for account and mission management' or 'Implement repository pattern with interactive CLI interface'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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/test/com/example/com/example/CliAppIT.java (1)

1-1: Fix test directory structure to follow Maven conventions.

The file is located at src/test/com/example/com/example/CliAppIT.java with duplicate com/example segments. Maven expects integration tests under src/test/java/, so this should be moved to src/test/java/com/example/CliAppIT.java. There is no custom testSourceDirectory configuration in pom.xml to justify this non-standard structure.

🧹 Nitpick comments (8)
pom.xml (1)

54-58: Consider adding <scope>test</scope> to slf4j-nop.

The slf4j-nop dependency is typically used to silence SLF4J logging warnings from Testcontainers during tests. If it's not needed at runtime, scope it to test to avoid including it in production artifacts.

         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-nop</artifactId>
             <version>2.0.17</version>
+            <scope>test</scope>
         </dependency>

Note: If DevDatabaseInitializer runs at application startup in dev mode and triggers SLF4J warnings, then the current (compile) scope may be intentional.

src/main/java/com/example/MoonMissionRepository.java (2)

13-22: Repository methods should return data, not print to console.

The repository layer directly writes to System.out, coupling data access to presentation. A cleaner design returns data (e.g., List<String>) and lets the caller handle output. This improves testability and reusability.

For a simple CLI assignment this may be acceptable, but consider:

-    public void listMissions() throws SQLException {
+    public List<String> listMissions() throws SQLException {
         String sql = "SELECT spacecraft FROM moon_mission ORDER BY mission_id";
+        List<String> missions = new ArrayList<>();
         try (Connection conn = ds.getConnection();
              PreparedStatement ps = conn.prepareStatement(sql);
              ResultSet rs = ps.executeQuery()) {
             while (rs.next()) {
-                System.out.println(rs.getString("spacecraft"));
+                missions.add(rs.getString("spacecraft"));
             }
         }
+        return missions;
     }

24-37: No feedback when mission is not found.

getMissionById silently returns nothing if the ID doesn't exist, leaving the user with no indication that the query succeeded but found no results. Consider printing a "Mission not found" message or returning an Optional.

             try (ResultSet rs = ps.executeQuery()) {
                 if (rs.next()) {
                     System.out.println("Mission ID: " + rs.getLong("mission_id"));
                     System.out.println("Spacecraft: " + rs.getString("spacecraft"));
                     System.out.println("Launch Date: " + rs.getDate("launch_date"));
+                } else {
+                    System.out.println("Mission not found for ID: " + id);
                 }
             }
src/main/java/com/example/Main.java (2)

156-156: Avoid closing Scanner wrapping System.in.

Calling scanner.close() also closes the underlying System.in stream, which cannot be reopened. If the application or tests attempt to read from System.in afterward, it will fail. For a CLI that terminates after the loop, this is typically harmless, but consider removing this line for safety.

-        scanner.close();
+        // Intentionally not closing scanner to avoid closing System.in

94-105: Plaintext password storage.

Passwords are stored in plaintext via accountRepo.createAccount(). For a learning exercise this is acceptable, but production systems should hash passwords (e.g., using BCrypt).

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

10-19: Simple DataSource implementation is appropriate for learning purposes.

The implementation is thread-safe with immutable fields. Consider adding null checks in the constructor for production use.

For production environments, consider:

  • Using a connection pool library like HikariCP
  • Adding validation for null/empty parameters
 public SimpleDriverManagerDataSource(String url, String user, String pass) {
+    if (url == null || url.trim().isEmpty()) {
+        throw new IllegalArgumentException("URL cannot be null or empty");
+    }
     this.url = url;
     this.user = user;
     this.pass = pass;
 }
src/main/java/com/example/AccountRepository.java (2)

38-38: Remove redundant setAutoCommit call.

Setting autoCommit to true is redundant since it's the default value for new connections.

     try (Connection conn = ds.getConnection()) {
-        conn.setAutoCommit(true);
-
         String candidate = baseName;

53-57: Replace System.out with proper logging.

Using System.out.println for application logging is not recommended. Consider using SLF4J (already added to dependencies).

Add a logger field to the class:

+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 public class AccountRepository {
+    private static final Logger logger = LoggerFactory.getLogger(AccountRepository.class);
     private final DataSource ds;

Replace the System.out calls:

                 try (ResultSet keys = ps.getGeneratedKeys()) {
                     if (keys.next()) {
-                        System.out.println("Account created with user_id=" + keys.getLong(1)
-                                + " and username=" + candidate);
+                        logger.info("Account created with user_id={} and username={}", 
+                                keys.getLong(1), candidate);
                     } else {
-                        System.out.println("Account created with username=" + candidate);
+                        logger.info("Account created with username={}", candidate);
                     }
                 }
📜 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 f2f5940.

📒 Files selected for processing (8)
  • README.md (1 hunks)
  • pom.xml (2 hunks)
  • src/main/java/com/example/AccountRepository.java (1 hunks)
  • src/main/java/com/example/DevDatabaseInitializer.java (1 hunks)
  • src/main/java/com/example/Main.java (2 hunks)
  • src/main/java/com/example/MoonMissionRepository.java (1 hunks)
  • src/main/java/com/example/SimpleDriverManagerDataSource.java (1 hunks)
  • src/test/com/example/com/example/CliAppIT.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/example/Main.java (1)
src/main/java/com/example/DevDatabaseInitializer.java (1)
  • DevDatabaseInitializer (6-24)
🔇 Additional comments (12)
src/main/java/com/example/DevDatabaseInitializer.java (1)

24-24: LGTM!

No functional changes – only EOF formatting adjustment.

src/test/com/example/com/example/CliAppIT.java (1)

341-341: LGTM!

No functional changes – only EOF formatting adjustment.

README.md (1)

1-1: LGTM!

GitHub Classroom assignment badge added appropriately.

pom.xml (1)

49-53: Verify intended scope for testcontainers mysql.

The testcontainers:mysql dependency has no explicit scope (defaults to compile), meaning it will be bundled in production artifacts. This appears intentional for the dev-mode DevDatabaseInitializer, but verify this is the desired behavior.

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

6-11: LGTM!

Constructor injection of DataSource follows good dependency injection practices, making the repository testable and decoupled from connection configuration.

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

45-51: Login failure exits without consuming remaining input.

After an invalid login, the method returns immediately at line 48. The test provides "0" after invalid credentials (line 69-70 of CliAppIT), which remains unconsumed. This works because the test doesn't require processing that input, but the behavior could be confusing. The comment acknowledges this, which is good.


29-31: Good use of repository pattern with DataSource injection.

Clean initialization of the data-access layer with dependency injection, making the repositories testable and decoupled from configuration resolution.


76-153: Menu implementation handles errors appropriately.

The switch-case structure with outer try-catch for SQLException and NumberFormatException provides resilient error handling that prevents crashes on invalid input.

src/main/java/com/example/SimpleDriverManagerDataSource.java (2)

21-29: Connection methods correctly delegate to DriverManager.

Both overloads properly implement the DataSource contract. Ensure callers close connections in try-with-resources blocks to prevent resource leaks.


31-64: Unsupported operations are appropriately stubbed.

The implementation correctly signals unsupported operations. This is acceptable for a minimal DataSource wrapper.

src/main/java/com/example/AccountRepository.java (2)

76-85: Update implementation is correct; ensure password hashing.

The method correctly uses prepared statements and returns success status. However, the newPassword parameter should be hashed before storage (related to the critical security issue flagged in the login method).


88-96: No cascade concerns needed—tables are independent.

The deleteAccount method is correctly implemented. The database schema contains no foreign key constraints or relationships between the account and moon_mission tables, so no cascade handling or explicit cleanup is required.

Comment on lines +14 to +24
public boolean login(String username, String password) throws SQLException {
String sql = "SELECT 1 FROM account WHERE name=? AND password=?";
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, username);
ps.setString(2, password);
try (ResultSet rs = ps.executeQuery()) {
return rs.next();
}
}
}
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: Plaintext password storage and comparison.

The login method compares passwords in plaintext, which is a severe security vulnerability. Passwords must be hashed using a secure algorithm like BCrypt or Argon2.

This affects the entire account system:

  • Line 15: Query retrieves plaintext passwords
  • Line 19: Compares unhashed password parameter
  • Related: createAccount (line 45) and updatePassword (line 80) also handle plaintext passwords

Recommended fix:

  1. Hash passwords before storing (in createAccount)
  2. Hash the login password and compare hashes
  3. Update updatePassword to hash new passwords

Example using BCrypt:

import org.mindrot.jbcrypt.BCrypt;

public boolean login(String username, String password) throws SQLException {
    String sql = "SELECT password FROM account WHERE name=?";
    try (Connection conn = ds.getConnection();
         PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setString(1, username);
        try (ResultSet rs = ps.executeQuery()) {
            if (rs.next()) {
                String hashedPassword = rs.getString("password");
                return BCrypt.checkpw(password, hashedPassword);
            }
            return false;
        }
    }
}

Add BCrypt dependency to pom.xml:

<dependency>
    <groupId>org.mindrot</groupId>
    <artifactId>jbcrypt</artifactId>
    <version>0.4</version>
</dependency>
🤖 Prompt for AI Agents
In src/main/java/com/example/AccountRepository.java around lines 14 to 24, the
login method currently retrieves and compares plaintext passwords; change it to
retrieve the stored hashed password for the user and verify the provided
password with a secure password-hashing library (e.g., BCrypt.checkpw). Also
update createAccount (around line 45) to hash passwords before inserting into
the DB and update updatePassword (around line 80) to hash new passwords before
saving; add the BCrypt dependency to pom.xml and ensure any migration plan is in
place to re-hash existing plaintext passwords or force password resets. Ensure
prepared statements still use parameter binding and do not log raw passwords.

Comment on lines +42 to +71
while (true) {
try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, candidate);
ps.setString(2, password); // medvetet: lösenord trimmas inte
ps.setString(3, base);
ps.setString(4, sur);
ps.setString(5, ssnTrim);
ps.executeUpdate();

try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) {
System.out.println("Account created with user_id=" + keys.getLong(1)
+ " and username=" + candidate);
} else {
System.out.println("Account created with username=" + candidate);
}
}
return candidate;
} catch (SQLException e) {
// SQLState 23000 = integrity constraint violation (includes unique constraint)
String sqlState = e.getSQLState();
if ("23000".equals(sqlState) || e.getMessage().toLowerCase().contains("duplicate") || e.getMessage().toLowerCase().contains("unique")) {
suffix++;
candidate = baseName + suffix;
// prova igen med nytt kandidatnamn
continue;
}
throw e;
}
}
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

Add safety limit to prevent infinite retry loop.

The retry logic could loop indefinitely if a non-duplicate constraint violation occurs repeatedly (e.g., invalid SSN format, NULL constraint on another field).

Add a maximum retry limit:

     String candidate = baseName;
     int suffix = 0;
+    int maxRetries = 100;
+    int attempts = 0;
-    while (true) {
+    while (attempts < maxRetries) {
+        attempts++;
         try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
             ps.setString(1, candidate);
             ps.setString(2, password);
             ps.setString(3, base);
             ps.setString(4, sur);
             ps.setString(5, ssnTrim);
             ps.executeUpdate();
             
             try (ResultSet keys = ps.getGeneratedKeys()) {
                 if (keys.next()) {
                     System.out.println("Account created with user_id=" + keys.getLong(1)
                             + " and username=" + candidate);
                 } else {
                     System.out.println("Account created with username=" + candidate);
                 }
             }
             return candidate;
         } catch (SQLException e) {
             String sqlState = e.getSQLState();
             if ("23000".equals(sqlState) || e.getMessage().toLowerCase().contains("duplicate") || e.getMessage().toLowerCase().contains("unique")) {
                 suffix++;
                 candidate = baseName + suffix;
                 continue;
             }
             throw e;
         }
     }
+    throw new SQLException("Failed to create account after " + maxRetries + " attempts");

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