Skip to content

Add user account management system with authentication, CRUD operations and CLI interface#8

Merged
DennSel merged 39 commits into
mainfrom
features/account
Jan 12, 2026
Merged

Add user account management system with authentication, CRUD operations and CLI interface#8
DennSel merged 39 commits into
mainfrom
features/account

Conversation

@DennSel

@DennSel DennSel commented Jan 11, 2026

Copy link
Copy Markdown

Account management feature.

Summary by CodeRabbit

  • New Features

    • Full user management via interactive CLI (Swedish prompts): create, login, update, delete, and session support.
    • Persistent user storage with unique username generation and per-operation persistence handling.
    • Centralized persistence manager for shared EntityManager handling.
    • User display improvements: full name and display name shown in UI/CLI.
  • Tests

    • Comprehensive unit and integration tests covering lifecycle, validation, uniqueness, lookups, and deletion.

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

@coderabbitai

coderabbitai Bot commented Jan 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a JPA-backed user subsystem: a centralized static EMFactory (public final, non-instantiable), JPA UserRepository, UserService with validation and username generation, SessionManager, CLI for user flows, User entity display helpers, and unit + integration tests.

Changes

Cohort / File(s) Summary
Infrastructure & Factory
src/main/java/org/example/EMFactory.java
Adds a public final, non-instantiable EMFactory with private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system"); public static methods init(), getEntityManager(), and close() (static).
User entity helpers
src/main/java/org/example/User.java
Adds public String getFullName() and public String getDisplayName().
Session & CLI
src/main/java/org/example/user/SessionManager.java, src/main/java/org/example/user/UserCLI.java, src/main/java/org/example/user/testUserCli.java
Adds SessionManager with static currentUser API and UserCLI plus a testUserCli main wiring EMFactory, repository, service, and CLI-based menus.
Service layer
src/main/java/org/example/user/UserService.java
New UserService with login, create, update, delete, and find operations; input validation (email regex, password length), username generation ensuring uniqueness, and duplicate-email checks.
Repository interface & JPA impl
src/main/java/org/example/user/repository/UserRepository.java, src/main/java/org/example/user/repository/JpaUserRepository.java
Adds UserRepository interface and JpaUserRepository implementation using EMFactory; per-operation EntityManager usage, transactional create/update/delete, and query methods by id/email/username plus findAllUsers.
Tests — Integration & Unit
src/test/java/org/example/UserIT.java, src/test/java/org/example/UserServiceUnitTests.java
Adds integration tests exercising full user lifecycle and unit tests (Mockito + AssertJ) covering validation, login, deletion, lookups, duplicate-email behavior, and username collision handling.
Persistence config
src/main/resources/META-INF/persistence.xml
Disables SQL logging/formatting and related properties (e.g., show_sql=false, format_sql=false, use_sql_comments=false, generate_statistics=false, highlight_sql=false).

Sequence Diagram(s)

sequenceDiagram
    participant User as EndUser
    participant CLI as UserCLI
    participant Service as UserService
    participant Repo as JpaUserRepository
    participant DB as Database
    participant SM as SessionManager

    User->>CLI: open loginMenu()
    CLI->>User: prompt username/password
    User-->>CLI: submit credentials
    CLI->>Service: login(username,password)
    Service->>Repo: findByUsername(username)
    Repo->>DB: SELECT user WHERE username=...
    DB-->>Repo: user row / null
    Repo-->>Service: User or null
    Service->>Service: validate password
    Service->>SM: login(user)   -- if authenticated
    SM->>SM: set currentUser
    Service-->>CLI: return authenticated User or throw
    CLI->>User: show result
Loading
sequenceDiagram
    participant User as EndUser
    participant CLI as UserCLI
    participant Service as UserService
    participant Repo as JpaUserRepository
    participant DB as Database

    User->>CLI: open createUserMenu()
    CLI->>User: prompt firstName,lastName,email,password
    User-->>CLI: provide details
    CLI->>Service: createUser(firstName,lastName,email,password)
    Service->>Service: validate inputs & email regex
    Service->>Repo: findByEmail(email)
    Repo->>DB: SELECT user WHERE email=...
    DB-->>Repo: null / existing user
    Repo-->>Service: result
    Service->>Service: generateUsername(...) (check uniqueness via findByUsername)
    Service->>Repo: createUser(new User)
    Repo->>DB: INSERT user (transaction)
    DB-->>Repo: persisted entity
    Repo-->>Service: created User
    Service-->>CLI: return created User
    CLI->>User: display username/details
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Configures persistence layer #3 — Adds an EMFactory creating an EntityManagerFactory for the same library_system persistence unit; directly related to persistence initialization here.
  • Features/entity #6 — Adds the User JPA entity and persistence.xml changes; related because this PR extends User with display helpers and integrates repository/service/tests against that entity.

Poem

🐇 I hopped in with a cheerful grin,
Static EMs hum where tests begin,
Usernames sprout from careful names,
Sessions nestle, menus call their games,
Hop on — the rabbit sealed the wins.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add user account management system with authentication, CRUD operations and CLI interface' accurately summarizes the main changes, which include new UserService, UserRepository, UserCLI, SessionManager, and EMFactory classes that collectively implement a complete user account management system with authentication and CRUD operations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In @src/main/java/org/example/user/repository/JpaUserRepository.java:
- Around line 73-82: The findByUsername method currently catches all exceptions
and returns null, which hides real errors; change the try/catch to only catch
javax.persistence.NoResultException (or jakarta.persistence.NoResultException
depending on imports) around the em.createQuery(...).getSingleResult() call and
return null in that catch block, while allowing other exceptions to propagate
(or rethrow) so real errors from EMFactory or the query surface are not
suppressed; ensure imports/reference to NoResultException are added and do not
swallow generic Exception in findByUsername.
- Around line 62-71: The findByEmail method currently catches all exceptions and
returns null, which hides real persistence errors; change the catch to only
handle javax.persistence.NoResultException (or
jakarta.persistence.NoResultException depending on project imports) and return
null in that case, while allowing other exceptions (e.g.,
NonUniqueResultException or PersistenceException) to propagate or be rethrown so
they are not silently suppressed; keep the EntityManager usage and
EMFactory.getEntityManager() as-is and only adjust the try/catch around
em.createQuery(...).getSingleResult() in the findByEmail method.
- Around line 46-53: deleteUserByUsername currently calls findByUsername then
deleteUserById, causing a race condition and extra EntityManager usage; replace
this two-step approach with a single transactional JPQL delete that deletes by
username in one statement. Modify the JpaUserRepository.deleteUserByUsername
method to open one transaction (or use the class' @Transactional), execute a
JPQL delete query like "DELETE FROM User u WHERE u.username = :username" via the
EntityManager (or createQuery) and return whether the updateCount > 0, removing
the separate findByUsername/deleteUserById calls to avoid multiple
EntityManagers and races.

In @src/main/java/org/example/user/UserCLI.java:
- Around line 97-123: createUserMenu() reads password without prompting, causing
the CLI to appear stuck and to capture unintended input; update the method to
print a password prompt (e.g., System.out.print("Lösenord: ");) before calling
scanner.nextLine() to read the password, then pass that password to
userService.createUser(...). Also consider trimming/validating the password
string returned from scanner.nextLine() in the same loop to avoid accepting
empty input.
- Around line 126-148: updateUserMenu reads user input and sends empty strings
to userService.updateUser, which will overwrite existing fields; change the
method to treat blank inputs as "keep existing" by checking each scanner input
(firstName, lastName, email, password) and replacing any empty string with the
corresponding currentUser getter (getFirstName/getLastName/getEmail) or
null/keep-policy for password before calling userService.updateUser, then
continue to call SessionManager.login(updatedUser) as before; ensure the checks
are performed in updateUserMenu (method name) rather than changing
userService.updateUser behavior so only explicit non-empty inputs trigger
updates.

In @src/main/java/org/example/user/UserService.java:
- Around line 69-84: The updateUser method lacks input validation and
duplicate-email checks: add the same validation applied in createUser to
updateUser (null/empty checks for firstName/lastName/email/password, email
format validation, minimum password length) and prevent email collisions by
checking userRepository.findByEmail(email) when the new email differs from the
existing user's email and throwing an exception if another user exists; factor
validation into a shared helper like validateUserInput(...) or reuse the
createUser validation logic, then call it from updateUser before mutating fields
and persisting via updateUser.
- Around line 20-34: The login method (UserService.login) and the User entity
currently rely on plain-text passwords (User.getPassword/setPassword) which is
insecure; instead, ensure passwords are hashed on write (e.g., in
User.setPassword or the registration flow) using a strong algorithm like bcrypt
or Argon2 and store only the hash, and change authentication in
UserService.login to verify via a secure verifier (e.g.,
PasswordEncoder.matches(hash, rawPassword) or Argon2 verifier) rather than
equals; update any places that construct User instances to call the hashing
routine before persistence and remove any code that exposes raw passwords.
🧹 Nitpick comments (13)
src/main/java/org/example/user/repository/UserRepository.java (1)

6-16: Clarify repository null/boolean contracts (not-found vs error).

Right now it’s implied that findBy* returns null when not found and delete* returns false when nothing was deleted—please document that in Javadoc (or add @Nullable / consider Optional<User> if you want the API to be explicit).

src/main/java/org/example/EMFactory.java (1)

7-23: Harden EMFactory lifecycle (utility class + safe close/init).

Consider preventing instantiation, making close() idempotent (emf != null && emf.isOpen()), and either (a) making init() actually perform initialization/lazy-init with a clear failure mode, or (b) removing init() to avoid a misleading API.

Proposed adjustment
 public class EMFactory {
     private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system");
 
+    private EMFactory() {}
+
     public static void init(){
         // Call this once to initialize the factory att app start
     }
@@
     // Close the factory
     public static void close() {
-        emf.close();
+        if (emf.isOpen()) {
+            emf.close();
+        }
     }
 }
src/main/java/org/example/user/SessionManager.java (1)

5-25: Static session state: guard null + be aware of test/thread leakage.

At minimum, consider rejecting null in login(User) (or rename to reflect “setCurrentUser”), and ensure tests/CLI flows consistently logout() so state can’t bleed between runs (and note it’s not thread-safe if tests ever run in parallel).

src/test/java/org/example/UserIT.java (2)

23-31: Cleanup looks good; consider suite-level EMF shutdown if needed.

The @AfterEach transaction cleanup is solid. If you see hanging test JVMs / resource warnings, consider adding a single @AfterAll to call EMFactory.close() (only if no other tests need it afterwards).


57-66: Integration tests are brittle if username generation changes.

Asserting exact usernames like "testes" / "testes1" tightly couples tests to the current username algorithm; if that algorithm isn’t a hard requirement, prefer asserting “non-empty” + “unique” (or document that exact format is part of the spec).

Also applies to: 76-87

src/main/java/org/example/user/UserCLI.java (1)

57-90: Add a “cancel/back” path to avoid trapping users in the login loop.

while (!success) with only exception-handling can force an endless loop for users who want to abort. Consider letting the user type e.g. 3/empty to return.

src/main/java/org/example/user/repository/JpaUserRepository.java (4)

11-19: Add explicit rollback on exception.

The transaction is not explicitly rolled back if an exception occurs during persist(). While the EntityManager will close, leaving the transaction without a rollback can lead to undefined behavior in some JPA implementations.

Additionally, consider returning the managed entity instead of the detached user object to ensure the returned entity includes any generated values (like auto-generated IDs).

♻️ Proposed fix
 @Override
 public User createUser(User user) {
     try (EntityManager em = EMFactory.getEntityManager()) {
-        em.getTransaction().begin();
-        em.persist(user);
-        em.getTransaction().commit();
-        return user;
+        try {
+            em.getTransaction().begin();
+            em.persist(user);
+            em.getTransaction().commit();
+            return user;
+        } catch (Exception e) {
+            if (em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            throw e;
+        }
     }
 }

21-29: Add explicit rollback on exception.

Similar to createUser, add exception handling with rollback to ensure transactions are properly cleaned up on failure.

♻️ Proposed fix
 @Override
 public User updateUser(User user) {
     try (EntityManager em = EMFactory.getEntityManager()) {
-        em.getTransaction().begin();
-        User updatedUser = em.merge(user);
-        em.getTransaction().commit();
-        return updatedUser;
+        try {
+            em.getTransaction().begin();
+            User updatedUser = em.merge(user);
+            em.getTransaction().commit();
+            return updatedUser;
+        } catch (Exception e) {
+            if (em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            throw e;
+        }
     }
 }

31-44: Simplify transaction handling.

The rollback at Line 41 is unnecessary since no modifications were made when the user is not found. Additionally, add exception handling to rollback the transaction if remove() fails.

♻️ Proposed fix
 @Override
 public boolean deleteUserById(Long id) {
     try (EntityManager em = EMFactory.getEntityManager()) {
-        em.getTransaction().begin();
-        User user = em.find(User.class, id);
-        if (user != null) {
-            em.remove(user);
-            em.getTransaction().commit();
-            return true;
+        try {
+            em.getTransaction().begin();
+            User user = em.find(User.class, id);
+            if (user != null) {
+                em.remove(user);
+                em.getTransaction().commit();
+                return true;
+            }
+            return false;
+        } catch (Exception e) {
+            if (em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            throw e;
         }
-        em.getTransaction().rollback();
-        return false;
     }
 }

84-89: Consider pagination for scalability.

Loading all users without pagination could cause performance issues and memory exhaustion with large datasets. Consider adding optional pagination parameters.

💡 Example with pagination support

Add to the UserRepository interface:

List<User> findAllUsers(int page, int pageSize);

Then implement:

@Override
public List<User> findAllUsers(int page, int pageSize) {
    try (EntityManager em = EMFactory.getEntityManager()) {
        return em.createQuery("SELECT u FROM User u", User.class)
                .setFirstResult(page * pageSize)
                .setMaxResults(pageSize)
                .getResultList();
    }
}
src/main/java/org/example/user/UserService.java (2)

12-12: Consider using a more robust email validation.

The current regex pattern lacks validation for top-level domains and may accept invalid email formats. Consider using a well-tested library like Apache Commons Validator or Jakarta Bean Validation.


110-121: Add upper limit to prevent potential infinite loop.

While unlikely, the username generation loop could theoretically run indefinitely if all numeric suffixes are exhausted. Add a reasonable upper limit and throw an exception if reached.

♻️ Proposed fix
 private String generateUsername(String firstName, String lastName) {
     // Create username from first and last name (Dennis Dennisson = denden)
     String concatUsername = (createSubstrings(firstName) + createSubstrings(lastName)).toLowerCase();
     String username = concatUsername;

     int counter = 1;
-    while(userRepository.findByUsername(username) != null) {
+    final int MAX_ATTEMPTS = 1000;
+    while(userRepository.findByUsername(username) != null && counter <= MAX_ATTEMPTS) {
         username = concatUsername + counter;
         counter++;
     }
+    if (counter > MAX_ATTEMPTS) {
+        throw new IllegalStateException("Unable to generate unique username after " + MAX_ATTEMPTS + " attempts");
+    }
     return username;
 }
src/test/java/org/example/UserServiceUnitTests.java (1)

1-276: Consider adding tests for updateUser.

The test suite comprehensively covers most UserService methods but is missing tests for updateUser. Given that the review of UserService identified validation gaps in that method, adding tests would help ensure correct behavior when those validations are added.

Would you like me to generate test cases for the updateUser method?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b053196 and c5f371d.

📒 Files selected for processing (8)
  • src/main/java/org/example/EMFactory.java
  • src/main/java/org/example/user/SessionManager.java
  • src/main/java/org/example/user/UserCLI.java
  • src/main/java/org/example/user/UserService.java
  • src/main/java/org/example/user/repository/JpaUserRepository.java
  • src/main/java/org/example/user/repository/UserRepository.java
  • src/test/java/org/example/UserIT.java
  • src/test/java/org/example/UserServiceUnitTests.java
🧰 Additional context used
🧬 Code graph analysis (3)
src/test/java/org/example/UserServiceUnitTests.java (1)
src/main/java/org/example/user/UserService.java (1)
  • UserService (10-133)
src/main/java/org/example/user/UserCLI.java (1)
src/main/java/org/example/user/SessionManager.java (1)
  • SessionManager (5-26)
src/main/java/org/example/user/repository/JpaUserRepository.java (1)
src/main/java/org/example/EMFactory.java (1)
  • EMFactory (7-24)
🪛 GitHub Actions: Java Maven Build
src/main/java/org/example/user/UserService.java

[error] 39-39: Inga fält får lämnas tomma.


[error] 54-54: En användare med denna emailadress finns redan.


[error] 44-44: Ogiltigt emailformat.


[error] 49-49: Lösenordet måste vara minst 3 karaktärer långt.

🔇 Additional comments (11)
src/main/java/org/example/user/repository/JpaUserRepository.java (1)

55-60: LGTM!

The findById method is correctly implemented. Read operations don't require explicit transactions.

src/main/java/org/example/user/UserService.java (4)

86-92: LGTM!

Both deletion methods correctly delegate to the repository. Consider adding cascade delete handling if users have related entities (e.g., borrowed books, comments).


94-108: LGTM!

The finder methods correctly delegate to the repository layer. The separation of concerns is appropriate.


123-127: LGTM!

The substring helper is correctly implemented with proper bounds checking.


129-131: LGTM!

The validation helper correctly checks for null and blank strings using isBlank(), which handles whitespace appropriately.

src/test/java/org/example/UserServiceUnitTests.java (6)

23-75: LGTM!

The login tests are well-structured and cover the important scenarios (user not found, wrong password, successful login). The error message mismatch with the production code has already been flagged in the UserService review.


77-98: LGTM!

The test correctly mocks the repository behavior and verifies the user creation flow. The transparency about AI assistance in the comment is appreciated.


100-145: LGTM!

The validation tests comprehensively cover the edge cases for user creation. The expected error messages are correct; the mismatch with Swedish messages in the production code has already been flagged.


147-209: LGTM!

The deletion tests appropriately cover both success and failure scenarios, with proper verification of repository interactions.


211-237: LGTM!

The finder tests adequately cover both scenarios and use appropriate assertions.


239-274: LGTM!

The tests for findAllUsers properly cover both populated and empty list scenarios with appropriate assertions.

Comment on lines +46 to +53
@Override
public boolean deleteUserByUsername(String username) {
User user = findByUsername(username);
if (user != null) {
return deleteUserById(user.getUserId());
}
return false;
}

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

Potential race condition and performance concern.

This method performs two separate database operations without a shared transaction, which could lead to a race condition where the user is deleted between the findByUsername and deleteUserById calls. Additionally, this creates two EntityManager instances unnecessarily.

Consider consolidating this into a single transaction with a JPQL delete query.

♻️ Proposed fix
 @Override
 public boolean deleteUserByUsername(String username) {
-    User user = findByUsername(username);
-    if (user != null) {
-        return deleteUserById(user.getUserId());
+    try (EntityManager em = EMFactory.getEntityManager()) {
+        try {
+            em.getTransaction().begin();
+            int deletedCount = em.createQuery("DELETE FROM User u WHERE u.username = :username")
+                    .setParameter("username", username)
+                    .executeUpdate();
+            em.getTransaction().commit();
+            return deletedCount > 0;
+        } catch (Exception e) {
+            if (em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            throw e;
+        }
     }
-    return false;
 }
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/repository/JpaUserRepository.java around
lines 46 - 53, deleteUserByUsername currently calls findByUsername then
deleteUserById, causing a race condition and extra EntityManager usage; replace
this two-step approach with a single transactional JPQL delete that deletes by
username in one statement. Modify the JpaUserRepository.deleteUserByUsername
method to open one transaction (or use the class' @Transactional), execute a
JPQL delete query like "DELETE FROM User u WHERE u.username = :username" via the
EntityManager (or createQuery) and return whether the updateCount > 0, removing
the separate findByUsername/deleteUserById calls to avoid multiple
EntityManagers and races.

Comment thread src/main/java/org/example/user/repository/JpaUserRepository.java
Comment thread src/main/java/org/example/user/repository/JpaUserRepository.java
Comment thread src/main/java/org/example/user/UserCLI.java
Comment thread src/main/java/org/example/user/UserCLI.java
Comment on lines +20 to +34
public User login(String username, String password) {
User user = userRepository.findByUsername(username);

// Check if a user was found
if (user == null) {
throw new IllegalArgumentException("Couldn't find user with username " + username);
}

// Check if the password is correct
if (!user.getPassword().equals(password) ) {
throw new IllegalArgumentException("Invalid password");
}

return user;
}

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find and examine the User entity class
fd User.java --type f

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 111


🏁 Script executed:

# Also search for password-related code in the User class
rg -B 5 -A 10 "password" --type=java -i | head -100

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 8656


🏁 Script executed:

# Check if there are any password hashing or encoding utilities in the project
fd -e java | xargs rg -l "bcrypt|argon2|PasswordEncoder|Hash" --type=java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 60


🏁 Script executed:

# Examine the User entity class to see password field handling
cat src/main/java/org/example/User.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 1376


Critical security vulnerability: Plain text password comparison and storage.

Passwords are stored in plain text in the User entity (line 20 in User.java: private String password;) with no hashing applied in setPassword(). The login() method then compares passwords using plain text equality check on line 29. This is a critical security risk—passwords must be hashed using a secure algorithm (bcrypt, argon2) before storage and verified using appropriate comparison methods.

🤖 Prompt for AI Agents
In @src/main/java/org/example/user/UserService.java around lines 20 - 34, The
login method (UserService.login) and the User entity currently rely on
plain-text passwords (User.getPassword/setPassword) which is insecure; instead,
ensure passwords are hashed on write (e.g., in User.setPassword or the
registration flow) using a strong algorithm like bcrypt or Argon2 and store only
the hash, and change authentication in UserService.login to verify via a secure
verifier (e.g., PasswordEncoder.matches(hash, rawPassword) or Argon2 verifier)
rather than equals; update any places that construct User instances to call the
hashing routine before persistence and remove any code that exposes raw
passwords.

Comment thread src/main/java/org/example/user/UserService.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @src/main/java/org/example/user/UserService.java:
- Around line 29-31: The code in UserService is comparing plaintext passwords;
replace plaintext handling with a secure password-hashing workflow: inject and
use a PasswordEncoder (e.g., Spring Security BCryptPasswordEncoder) into
UserService, update the registration/create flow to call
passwordEncoder.encode(...) before persisting the User so the stored password is
hashed, and replace the equality check in the authentication path (the block
with user.getPassword().equals(password)) with
passwordEncoder.matches(plainPassword, user.getPassword()); ensure
PasswordEncoder is a singleton bean or field in UserService and that all
tests/other callers use the hashed-password semantics.
- Line 64: The password is being stored in plaintext via
newUser.setPassword(password) in UserService; change this to hash the password
before setting it (e.g., use a strong adaptive hashing algorithm like BCrypt via
BCryptPasswordEncoder or similar), call encoder.encode(password) and pass the
resulting hash into newUser.setPassword(...), and update authentication/login
logic to use encoder.matches(rawPassword, storedHash) for verification; ensure
dependency injection/creation of the encoder (e.g., a private final
BCryptPasswordEncoder or bean) is added to UserService and any tests are updated
accordingly.
- Around line 69-84: The updateUser method lacks the same input validations as
createUser; update updateUser (and/or extract a shared validateUserInputs helper
used by createUser) to validate non-null/non-empty firstName/lastName/email,
enforce email format and password length rules, and before calling
userRepository.updateUser check for duplicate email by querying the repository
for an existing user with the same email and ensuring that any found user's id
is not the current id; preserve existing user retrieval via
userRepository.findById and keep throwing IllegalArgumentException when
validations fail.
🧹 Nitpick comments (3)
src/main/java/org/example/user/UserService.java (1)

12-12: Email regex pattern may allow invalid emails.

The regex ^[a-zA-Z0-9_!#$%&'*+/=?{|}~^.-]+@[a-zA-Z0-9.-]+$doesn't enforce a TLD or validate domain structure properly. It would accept emails likeuser@domainwithout a TLD, oruser@.` which are technically invalid. For a production system, consider using a stricter pattern or a validation library.

src/test/java/org/example/UserServiceUnitTests.java (2)

23-35: Minor: Stub setup could be simplified.

The stub on line 30 for "testuser" is unnecessary since the test calls login with "wronguser". Mockito returns null by default for unstubbed calls. Consider removing the unused stub for clarity.

♻️ Suggested simplification
     // Mock the repository
     UserRepository mockRepository = mock(UserRepository.class);
     UserService userService = new UserService(mockRepository);

-    // Tell the mock to return null when trying to find 'testuser'
-    when(mockRepository.findByUsername("testuser")).thenReturn(null);
+    // Mockito returns null by default for unstubbed calls

     assertThatThrownBy(() -> userService.login("wronguser", "a$$word"))

21-275: Consider adding test coverage for missing methods.

The test suite doesn't include tests for:

  • updateUser() - including success, user not found, and validation scenarios
  • findById()
  • findByEmail()

These are important for comprehensive coverage, especially updateUser which has different behavior than createUser.

Would you like me to generate test cases for these missing methods?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5f371d and 5dd8338.

📒 Files selected for processing (3)
  • src/main/java/org/example/user/UserService.java
  • src/test/java/org/example/UserIT.java
  • src/test/java/org/example/UserServiceUnitTests.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/org/example/UserIT.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/org/example/UserServiceUnitTests.java (1)
src/main/java/org/example/user/UserService.java (1)
  • UserService (10-133)
🔇 Additional comments (6)
src/main/java/org/example/user/UserService.java (2)

86-108: LGTM!

The delete and finder methods are straightforward delegations to the repository with appropriate return types.


110-131: LGTM!

The helper methods are well-implemented. The username generation handles collisions appropriately with a counter suffix, and the string utilities are defensive against short names.

src/test/java/org/example/UserServiceUnitTests.java (4)

77-98: LGTM!

Good test coverage for the happy path of user creation, including verification of the repository call.


100-145: LGTM!

Comprehensive validation tests covering invalid email, short password, empty fields, and duplicate email scenarios. Good use of AssertJ's assertThatThrownBy for exception testing.


147-209: LGTM!

Delete tests properly cover both success and not-found scenarios for both deletion methods, with appropriate verification of repository interactions.


211-274: LGTM!

Find-related tests cover the expected scenarios for username lookup and listing all users.

newUser.setFirstName(firstName);
newUser.setLastName(lastName);
newUser.setEmail(email);
newUser.setPassword(password);

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

Security: Password stored in plaintext.

Same concern as login - passwords should be hashed before storage, not stored as plaintext.

🔒 Recommended fix
-        newUser.setPassword(password);
+        newUser.setPasswordHash(passwordEncoder.encode(password));
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/UserService.java at line 64, The password is
being stored in plaintext via newUser.setPassword(password) in UserService;
change this to hash the password before setting it (e.g., use a strong adaptive
hashing algorithm like BCrypt via BCryptPasswordEncoder or similar), call
encoder.encode(password) and pass the resulting hash into
newUser.setPassword(...), and update authentication/login logic to use
encoder.matches(rawPassword, storedHash) for verification; ensure dependency
injection/creation of the encoder (e.g., a private final BCryptPasswordEncoder
or bean) is added to UserService and any tests are updated accordingly.

@DennSel DennSel changed the title Features/account Add user account management system with authentication, CRUD operations and CLI interface Jan 11, 2026
Also added a check in the close method to make sure emf is open when closing it to not cause any unexpected errors.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @src/main/java/org/example/EMFactory.java:
- Around line 7-26: The class EMFactory mixes static state with an instance
AutoCloseable; fix by making it a proper static utility or a singleton: remove
"implements AutoCloseable" and the instance close() method, make the constructor
private, keep emf static and getEntityManager() as static (Option 1), or
alternatively keep emf static but remove AutoCloseable, add a static initializer
that registers a Runtime.getRuntime().addShutdownHook to close emf if open, and
delete the instance close() (Option 2); reference EMFactory, emf,
getEntityManager, and close when updating the class to ensure only static
lifecycle management is used.

In @src/main/java/org/example/user/UserService.java:
- Around line 29-31: UserService currently compares raw passwords using
user.getPassword().equals(password), which stores and checks plaintext; change
to hashing: when creating or updating users (e.g., UserService.createUser /
UserService.updatePassword) hash the plaintext with a secure algorithm (bcrypt
or Argon2) and persist the hash, and when authenticating (where
user.getPassword() is currently compared) replace the equals check with the
library's verify/check method (e.g., BCrypt.checkpw or Argon2 verify) so you
never store or compare plaintext; add the appropriate crypto dependency,
generate salts via the chosen library, and update tests to expect hashed
passwords and verification behavior.
- Line 25: Update the IllegalArgumentException message in UserService where it
currently throws IllegalArgumentException("Kunde inte hitta användarnamn \"" +
username + "\"") to use a colon and space format; change it to
IllegalArgumentException("Kunde inte hitta användarnamn: " + username) so the
message matches the expected "Kunde inte hitta användarnamn: " format in tests.
- Around line 69-84: The updateUser method bypasses the validations applied in
createUser; extract or reuse the same validation logic (empty/blank
firstName/lastName/email, email format, password length) into a shared helper
(e.g., validateUserFields or reuse createUser's validators) and call it from
updateUser before persisting; additionally, add the duplicate-email check to
updateUser by querying userRepository for an existing user with the same email
and ensure you ignore the current user id (so updating to the same email is
allowed), and only then call userRepository.updateUser(user).
🧹 Nitpick comments (3)
src/main/java/org/example/User.java (1)

65-71: Consider null-safety for display methods.

If firstName, lastName, or username is null, these methods will produce strings like "null null" or "Name (null)". Consider adding null checks or using empty strings as fallbacks for cleaner display output.

💡 Suggested null-safe implementation
 public String getFullName() {
-    return firstName + " " + lastName;
+    String first = firstName != null ? firstName : "";
+    String last = lastName != null ? lastName : "";
+    return (first + " " + last).trim();
 }

 public String getDisplayName() {
-    return getFullName() + " (" + username + ")";
+    String uname = username != null ? username : "";
+    return getFullName() + " (" + uname + ")";
 }
src/main/java/org/example/user/UserService.java (1)

20-34: Different error messages enable username enumeration.

Returning distinct messages for "user not found" vs "wrong password" allows attackers to enumerate valid usernames. Consider using a generic message like "Ogiltigt användarnamn eller lösenord" for both cases.

src/main/java/org/example/EMFactory.java (1)

10-12: Minor: Typo in comment.

"att app start" should be "at app start".

-        // Call this once to initialize the factory att app start
+        // Call this once to initialize the factory at app start
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5dd8338 and 4eacd1a.

📒 Files selected for processing (5)
  • src/main/java/org/example/EMFactory.java
  • src/main/java/org/example/User.java
  • src/main/java/org/example/user/SessionManager.java
  • src/main/java/org/example/user/UserCLI.java
  • src/main/java/org/example/user/UserService.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/example/user/SessionManager.java
  • src/main/java/org/example/user/UserCLI.java
🧰 Additional context used
🪛 GitHub Actions: Java Maven Build
src/main/java/org/example/user/UserService.java

[error] 25-25: UserService login threw IllegalArgumentException with message "Kunde inte hitta användarnamn "wronguser""; test expected message to contain "Kunde inte hitta användarnamn: ". This indicates a mismatch in the error message formatting.

🔇 Additional comments (1)
src/main/java/org/example/user/UserService.java (1)

110-131: LGTM!

The username generation handles collisions properly with an incrementing counter, and the helper methods correctly handle edge cases (short names, null/blank values).

Comment on lines +7 to +26
public class EMFactory implements AutoCloseable {
private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system");

public static void init(){
// Call this once to initialize the factory att app start
}

// Use this in try-with
public static EntityManager getEntityManager() {
return emf.createEntityManager();
}


// Close the factory
@Override
public void close() {
if (emf != null && emf.isOpen()) {
emf.close();
}
}

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

Mismatched static/instance design with AutoCloseable.

The class implements AutoCloseable with an instance close() method, but emf is static and all other methods are static. This creates confusion:

  • You must instantiate EMFactory to call close(), but the factory itself is a static utility
  • Multiple instances would all share and potentially close the same emf

Consider one of these approaches:

Option 1: Static utility (remove AutoCloseable)
-public class EMFactory implements AutoCloseable {
+public final class EMFactory {
     private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system");

+    private EMFactory() {} // Prevent instantiation
+
     public static void init(){
         // Call this once to initialize the factory at app start
     }

     public static EntityManager getEntityManager() {
         return emf.createEntityManager();
     }

-    @Override
-    public void close() {
+    public static void close() {
         if (emf != null && emf.isOpen()) {
             emf.close();
         }
     }
 }
Option 2: Proper singleton with shutdown hook
public final class EMFactory {
    private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system");

    static {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (emf != null && emf.isOpen()) {
                emf.close();
            }
        }));
    }

    private EMFactory() {}

    public static EntityManager getEntityManager() {
        return emf.createEntityManager();
    }
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/EMFactory.java around lines 7 - 26, The class
EMFactory mixes static state with an instance AutoCloseable; fix by making it a
proper static utility or a singleton: remove "implements AutoCloseable" and the
instance close() method, make the constructor private, keep emf static and
getEntityManager() as static (Option 1), or alternatively keep emf static but
remove AutoCloseable, add a static initializer that registers a
Runtime.getRuntime().addShutdownHook to close emf if open, and delete the
instance close() (Option 2); reference EMFactory, emf, getEntityManager, and
close when updating the class to ensure only static lifecycle management is
used.

Comment thread src/main/java/org/example/user/UserService.java
Comment thread src/main/java/org/example/user/UserService.java
Comment on lines +69 to +84
public User updateUser(Long id, String firstName, String lastName, String email, String password) {
// Get user by id
User user = userRepository.findById(id);

// Check if a user was found
if (user == null) {
throw new IllegalArgumentException("Kunde inte hitta användare med id: " + id);
}

// Update user
user.setFirstName(firstName);
user.setLastName(lastName);
user.setEmail(email);
user.setPassword(password);
return userRepository.updateUser(user);
}

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

Add validation to updateUser for consistency with createUser.

updateUser bypasses all validation present in createUser: empty field checks, email format validation, password length requirements, and duplicate email checks. This inconsistency allows invalid data to be persisted through updates.

🔧 Suggested validation additions
 public User updateUser(Long id, String firstName, String lastName, String email, String password) {
     // Get user by id
     User user = userRepository.findById(id);

     // Check if a user was found
     if (user == null) {
         throw new IllegalArgumentException("Kunde inte hitta användare med id: " + id);
     }

+    // Validate fields
+    if (isInvalid(firstName) || isInvalid(lastName) || isInvalid(email) || isInvalid(password)) {
+        throw new IllegalArgumentException("Inga fält får lämnas tomma.");
+    }
+
+    if (!EMAIL_PATTERN.matcher(email).matches()) {
+        throw new IllegalArgumentException("Ogiltigt emailformat.");
+    }
+
+    if (password.length() < 3) {
+        throw new IllegalArgumentException("Lösenordet måste vara minst 3 karaktärer långt.");
+    }
+
+    // Check if email is taken by another user
+    User existingUser = userRepository.findByEmail(email);
+    if (existingUser != null && !existingUser.getUserId().equals(id)) {
+        throw new IllegalArgumentException("En användare med denna emailadress finns redan.");
+    }
+
     // Update user
     user.setFirstName(firstName);
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/UserService.java around lines 69 - 84, The
updateUser method bypasses the validations applied in createUser; extract or
reuse the same validation logic (empty/blank firstName/lastName/email, email
format, password length) into a shared helper (e.g., validateUserFields or reuse
createUser's validators) and call it from updateUser before persisting;
additionally, add the duplicate-email check to updateUser by querying
userRepository for an existing user with the same email and ensure you ignore
the current user id (so updating to the same email is allowed), and only then
call userRepository.updateUser(user).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/test/java/org/example/UserServiceUnitTests.java (2)

29-32: Misleading mock setup doesn't match actual method call.

The mock is configured for "testuser" but the assertion calls login with "wronguser". The test passes only because Mockito returns null by default for unstubbed calls, making the explicit stub unnecessary and confusing.

♻️ Proposed fix: Either remove the unused stub or align it with the test
-        // Tell the mock to return null when trying to find 'testuser'
-        when(mockRepository.findByUsername("testuser")).thenReturn(null);
+        // Mockito returns null by default for unstubbed calls, so no explicit stub needed
 
         assertThatThrownBy(() -> userService.login("wronguser", "a$$word"))

Or, to be explicit:

-        // Tell the mock to return null when trying to find 'testuser'
-        when(mockRepository.findByUsername("testuser")).thenReturn(null);
+        // Tell the mock to return null when trying to find 'wronguser'
+        when(mockRepository.findByUsername("wronguser")).thenReturn(null);
 
         assertThatThrownBy(() -> userService.login("wronguser", "a$$word"))

21-27: Consider using @BeforeEach to reduce mock setup repetition.

Each test method creates its own mockRepository and userService. While this ensures isolation, extracting common setup to a @BeforeEach method would reduce boilerplate.

♻️ Optional refactor
 class UserServiceUnitTests {
 
+    private UserRepository mockRepository;
+    private UserService userService;
+
+    @BeforeEach
+    void setUp() {
+        mockRepository = mock(UserRepository.class);
+        userService = new UserService(mockRepository);
+    }
+
     @Test
     void testLoginFailed_WrongUsername_ShowsErrorMessage() {
-        // Mock the repository
-        UserRepository mockRepository = mock(UserRepository.class);
-        UserService userService = new UserService(mockRepository);
-
         // Tell the mock to return null when trying to find 'testuser'
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4eacd1a and dec4c1d.

📒 Files selected for processing (1)
  • src/test/java/org/example/UserServiceUnitTests.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/org/example/UserServiceUnitTests.java (1)
src/main/java/org/example/user/UserService.java (1)
  • UserService (10-133)
🔇 Additional comments (4)
src/test/java/org/example/UserServiceUnitTests.java (4)

37-75: LGTM!

Login tests are well-structured with proper mock setup and clear assertions matching the UserService behavior.


77-145: LGTM!

User creation tests comprehensively cover success scenario and validation edge cases (invalid email, short password, empty fields, duplicate email). The username assertion "tesuse" correctly reflects the generateUsername logic.


147-274: LGTM!

Delete and find tests are well-structured, covering both success and failure scenarios with appropriate mock verification.


1-16: No import needed for User class—both the test and User entity are in the same org.example package.

Classes in the same package do not require explicit imports in Java. The User class is located at src/main/java/org/example/User.java (package org.example), which matches the test class package declared at line 1. The code will compile successfully as-is.

Likely an incorrect or invalid review comment.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @src/main/java/org/example/user/repository/JpaUserRepository.java:
- Around line 64-72: Both findByEmail and findByUsername currently call
getSingleResult() and only catch NoResultException; add handling for
NonUniqueResultException so duplicate rows don't propagate an error. In each
method (findByEmail and findByUsername) catch NonUniqueResultException and
instead return a sensible single value (e.g., fetch the first match via the same
query using getResultList() and return list.stream().findFirst().orElse(null),
or change the query to setMaxResults(1) and return that result), keeping the
existing NoResultException behavior.
- Around line 13-20: The createUser and updateUser transaction blocks lack
explicit rollback handling; wrap the body of both createUser(User user) and
updateUser(User user) in a try-catch around em.getTransaction() handling (while
keeping the try-with-resources for EntityManager), and on any Exception check if
em.getTransaction().isActive() then call em.getTransaction().rollback(); after
rollback rethrow or convert the exception as appropriate and ensure resources
are closed; mirror the rollback/log/rethrow pattern used in deleteUserById to
keep behavior consistent and preserve data integrity.

In @src/main/java/org/example/user/UserCLI.java:
- Around line 186-198: The CLI's deleteUser() ignores the boolean result from
userService.deleteUserById(...) and assumes a current user exists; update
deleteUser() to first check SessionManager.getCurrentUser() != null, then call
userService.deleteUserById(user.getUserId()), capture the returned boolean, and
only call SessionManager.logout() and print the success message when it returns
true; otherwise print a clear failure message indicating deletion failed.
- Around line 147-171: The updateUserMenu sends a blank password to
userService.updateUser which fails validation; change the flow in updateUserMenu
to treat an empty password as "keep existing" by checking if password.isBlank()
and in that case pass null (or omit the password parameter / call an overload
that preserves the password) to userService.updateUser instead of the blank
string, and update the prompt text to indicate leaving the field blank keeps the
current password; keep other fields' blank-handling as-is and still call
SessionManager.login(updatedUser) after a successful update.

In @src/main/java/org/example/user/UserService.java:
- Around line 52-69: The validate(...) call in updateUser(Long id, ...) wrongly
rejects the current user's unchanged email because it checks global email
uniqueness; change validation so email-uniqueness is only enforced when the
provided email differs from the existing user's email: in updateUser fetch the
current user via userRepository.findById(id), compare currentUser.getEmail()
with the incoming email and only call or perform the uniqueness check (e.g.,
userRepository.findByEmail(email)) if they differ, ensuring the lookup excludes
the same user id when checking for collisions; apply the same pattern to the
other update path referenced around lines 115-135.
🧹 Nitpick comments (2)
src/main/java/org/example/user/UserCLI.java (1)

200-210: Avoid RuntimeException("AVBRUTET") + string matching for normal control flow.
This is brittle (message matching) and makes real runtime failures easier to mis-handle.

Proposed refactor (custom exception)
 public class UserCLI {
+    private static final class CancelledOperationException extends RuntimeException {}

     private String prompt(String message) {
         System.out.print(message);
         String input = scanner.nextLine();
         if (input.equalsIgnoreCase("avbryt")) {
-            throw new RuntimeException("AVBRUTET");
+            throw new CancelledOperationException();
         }
         return input;
     }
-            catch (RuntimeException e){
-                if (e.getMessage().equals("AVBRUTET")) {
-                    return;
-                }
-                throw e;
-            }
+            catch (CancelledOperationException e) {
+                return;
+            }

Also applies to: 92-99, 136-143, 176-183

src/main/java/org/example/user/UserService.java (1)

96-107: Username generation is fine for small scale; watch DB roundtrips in the loop.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dec4c1d and bff2351.

📒 Files selected for processing (3)
  • src/main/java/org/example/user/UserCLI.java
  • src/main/java/org/example/user/UserService.java
  • src/main/java/org/example/user/repository/JpaUserRepository.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/org/example/user/repository/JpaUserRepository.java (1)
src/main/java/org/example/EMFactory.java (1)
  • EMFactory (7-27)
src/main/java/org/example/user/UserCLI.java (1)
src/main/java/org/example/user/SessionManager.java (1)
  • SessionManager (5-33)
🔇 Additional comments (4)
src/main/java/org/example/user/repository/JpaUserRepository.java (1)

33-45: Overall structure is clean (per-method EM + small methods).

Also applies to: 56-61, 85-90

src/main/java/org/example/user/UserCLI.java (1)

16-60: Menu structure and prompts are straightforward and readable.

Also applies to: 62-107, 108-145, 212-214

src/main/java/org/example/user/UserService.java (2)

12-18: Service API surface is clean and consistent.

Also applies to: 72-94, 137-139


20-34: and

Comment on lines +13 to +20
public User createUser(User user) {
try (EntityManager em = EMFactory.getEntityManager()) {
em.getTransaction().begin();
em.persist(user);
em.getTransaction().commit();
return user;
}
}

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "JpaUserRepository.java" | head -20

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 142


🏁 Script executed:

cat -n ./src/main/java/org/example/user/repository/JpaUserRepository.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 3430


🏁 Script executed:

# Let me verify the JPA behavior by checking the EntityManager documentation and specification
# Also check if there are any tests that might show expected behavior

# First, check if there are any JUnit tests for this repository
find . -type f -name "*UserRepositoryTest*.java" -o -name "*JpaUserRepositoryTest*.java" 2>/dev/null | head -20

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 58


🏁 Script executed:

# Check for any other transaction handling in the codebase and EMFactory
find . -type f -name "EMFactory.java" | head -5

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 118


🏁 Script executed:

cat -n ./src/main/java/org/example/EMFactory.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 965


🏁 Script executed:

# Check the pom.xml to see Jakarta version and understand AutoCloseable behavior
find . -name "pom.xml" -type f | head -1 | xargs cat -n

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 2452


🏁 Script executed:

# Check if there are other transaction methods that might have the same issue
grep -n "em.getTransaction()" ./src/main/java/org/example/user/repository/JpaUserRepository.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 393


Wrap transaction handling in try-catch with explicit rollback for createUser and updateUser to match deleteUserById pattern.

The implementation lacks explicit rollback handling if persist/merge/commit throws an exception. While Hibernate may auto-rollback in some cases, JPA specification behavior is provider-dependent on EntityManager.close(). The deleteUserById method (line 42) already demonstrates the correct pattern with explicit rollback—apply the same to createUser and updateUser for consistency and data integrity.

Proposed fix
 public class JpaUserRepository implements UserRepository {
     @Override
     public User createUser(User user) {
         try (EntityManager em = EMFactory.getEntityManager()) {
-            em.getTransaction().begin();
-            em.persist(user);
-            em.getTransaction().commit();
-            return user;
+            var tx = em.getTransaction();
+            try {
+                tx.begin();
+                em.persist(user);
+                tx.commit();
+                return user;
+            } catch (RuntimeException e) {
+                if (tx.isActive()) tx.rollback();
+                throw e;
+            }
         }
     }

     @Override
     public User updateUser(User user) {
         try (EntityManager em = EMFactory.getEntityManager()) {
-            em.getTransaction().begin();
-            User updatedUser = em.merge(user);
-            em.getTransaction().commit();
-            return updatedUser;
+            var tx = em.getTransaction();
+            try {
+                tx.begin();
+                User updatedUser = em.merge(user);
+                tx.commit();
+                return updatedUser;
+            } catch (RuntimeException e) {
+                if (tx.isActive()) tx.rollback();
+                throw e;
+            }
         }
     }
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/repository/JpaUserRepository.java around
lines 13 - 20, The createUser and updateUser transaction blocks lack explicit
rollback handling; wrap the body of both createUser(User user) and
updateUser(User user) in a try-catch around em.getTransaction() handling (while
keeping the try-with-resources for EntityManager), and on any Exception check if
em.getTransaction().isActive() then call em.getTransaction().rollback(); after
rollback rethrow or convert the exception as appropriate and ensure resources
are closed; mirror the rollback/log/rethrow pattern used in deleteUserById to
keep behavior consistent and preserve data integrity.

Comment on lines +48 to +54
public boolean deleteUserByUsername(String username) {
User user = findByUsername(username);
if (user != null) {
return deleteUserById(user.getUserId());
}
return false;
}

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

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "JpaUserRepository.java" | head -5

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 142


🏁 Script executed:

find . -type f -name "*.java" -path "*/repository/*" | grep -i user

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 206


🏁 Script executed:

find . -type f -name "*.java" | grep -i repository

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 206


🏁 Script executed:

cat -n ./src/main/java/org/example/user/repository/JpaUserRepository.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 3430


🏁 Script executed:

cat -n ./src/main/java/org/example/user/repository/UserRepository.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 608


🏁 Script executed:

find . -type f -name "EMFactory.java"

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 118


🏁 Script executed:

cat -n ./src/main/java/org/example/EMFactory.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 965


deleteUserByUsername has an atomicity issue due to separate EntityManager instances.
findByUsername() opens and closes an EntityManager (lines 49, 75–83), then deleteUserById() opens a separate EntityManager for the delete (lines 51, 33–45). Between these operations, another thread could delete or modify the user, causing a TOCTOU race condition. The method should execute both lookup and deletion within a single transaction and EntityManager.

Proposed fix
     @Override
     public boolean deleteUserByUsername(String username) {
-        User user = findByUsername(username);
-        if (user != null) {
-            return deleteUserById(user.getUserId());
-        }
-        return false;
+        try (EntityManager em = EMFactory.getEntityManager()) {
+            var tx = em.getTransaction();
+            try {
+                tx.begin();
+                User user;
+                try {
+                    user = em.createQuery(
+                                    "SELECT u FROM User u WHERE u.username = :username", User.class)
+                            .setParameter("username", username)
+                            .getSingleResult();
+                } catch (NoResultException e) {
+                    tx.rollback();
+                    return false;
+                }
+                em.remove(user);
+                tx.commit();
+                return true;
+            } catch (RuntimeException e) {
+                if (tx.isActive()) tx.rollback();
+                throw e;
+            }
+        }
     }

Comment on lines +64 to +72
public User findByEmail(String email) {
try (EntityManager em = EMFactory.getEntityManager()) {
return em.createQuery("SELECT u FROM User u WHERE u.email = :email", User.class)
.setParameter("email", email)
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}

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

🧩 Analysis chain

🏁 Script executed:

find . -name "JpaUserRepository.java" -type f

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 142


🏁 Script executed:

git ls-files | grep -E "(pom.xml|build.gradle)" | head -5

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 83


🏁 Script executed:

wc -l src/main/java/org/example/user/repository/JpaUserRepository.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 143


🏁 Script executed:

cat -n src/main/java/org/example/user/repository/JpaUserRepository.java | sed -n '55,95p'

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 1524


🏁 Script executed:

find . -name "User.java" -type f | grep -E "(entity|model)" | head -5

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 58


🏁 Script executed:

find . -name "*User*.java" | grep -E "(entity|model)" | head -10

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 58


🏁 Script executed:

find . -name "User.java" -type f

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 113


🏁 Script executed:

ast-grep --pattern 'class User {
  $$$
}'

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 58


🏁 Script executed:

cat -n src/main/java/org/example/User.java

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 2125


🏁 Script executed:

find . -type f -name "*migration*" -o -name "*.sql" -o -name "*schema*" | head -20

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 58


🏁 Script executed:

find . -path "*/db/migration*" -o -path "*/sql*" | head -20

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 58


🏁 Script executed:

grep -r "unique.*constraint" --include="*.sql" --include="*.java" . 2>/dev/null | head -20

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 58


🏁 Script executed:

cat pom.xml | grep -A 20 -B 5 "persistence\|hibernate\|jpa" | head -60

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 785


🏁 Script executed:

find . -name "persistence.xml" -o -name "application.properties" -o -name "application.yml"

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 121


🏁 Script executed:

cat -n src/main/resources/META-INF/persistence.xml

Repository: ithsjava25/project-jpa-grupp-6-d

Length of output: 1649


Add NonUniqueResultException handling to both findByEmail() and findByUsername() methods.

Neither email nor username fields have unique constraints defined in the User entity, so duplicates can exist in the database. When getSingleResult() encounters multiple results, it throws NonUniqueResultException, which is not currently caught. This causes the exception to propagate unhandled instead of being gracefully managed.

Applies to: lines 64-72 and 75-83

🤖 Prompt for AI Agents
In @src/main/java/org/example/user/repository/JpaUserRepository.java around
lines 64 - 72, Both findByEmail and findByUsername currently call
getSingleResult() and only catch NoResultException; add handling for
NonUniqueResultException so duplicate rows don't propagate an error. In each
method (findByEmail and findByUsername) catch NonUniqueResultException and
instead return a sensible single value (e.g., fetch the first match via the same
query using getResultList() and return list.stream().findFirst().orElse(null),
or change the query to setMaxResults(1) and return that result), keeping the
existing NoResultException behavior.

Comment on lines +147 to +171
public void updateUserMenu() {
// Make sure user is logged in
if (!SessionManager.isLoggedIn()) return;

User currentUser = SessionManager.getCurrentUser();

try {
System.out.println(appName());
System.out.println("UPPDATERA ANVÄNDARE\n=========================================================");
System.out.println("(Skriv 'avbryt' för att avbryta uppdateringen av användaren)");

String firstName = prompt("Nytt förnamn: ");
String lastName = prompt("Nytt efternamn: ");
String email = prompt("Ny email: ");
String password = prompt("Nytt lösenord: ");

// If user didn't enter anything, keep current value
if (firstName.isBlank()) firstName = currentUser.getFirstName();
if (lastName.isBlank()) lastName = currentUser.getLastName();
if (email.isBlank()) email = currentUser.getEmail();

User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password);
SessionManager.login(updatedUser); // Update session with new user
System.out.println("Användaren har uppdaterats!");
}

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

Update flow: blank password currently can’t “retain existing” (will fail validation).
You keep blanks for first/last/email, but password is always sent (blank → likely rejected).

Proposed fix
             String password = prompt("Nytt lösenord: ");

             // If user didn't enter anything, keep current value
             if (firstName.isBlank()) firstName = currentUser.getFirstName();
             if (lastName.isBlank()) lastName = currentUser.getLastName();
             if (email.isBlank()) email = currentUser.getEmail();
+            if (password.isBlank()) password = currentUser.getPassword();

             User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password);
📝 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
public void updateUserMenu() {
// Make sure user is logged in
if (!SessionManager.isLoggedIn()) return;
User currentUser = SessionManager.getCurrentUser();
try {
System.out.println(appName());
System.out.println("UPPDATERA ANVÄNDARE\n=========================================================");
System.out.println("(Skriv 'avbryt' för att avbryta uppdateringen av användaren)");
String firstName = prompt("Nytt förnamn: ");
String lastName = prompt("Nytt efternamn: ");
String email = prompt("Ny email: ");
String password = prompt("Nytt lösenord: ");
// If user didn't enter anything, keep current value
if (firstName.isBlank()) firstName = currentUser.getFirstName();
if (lastName.isBlank()) lastName = currentUser.getLastName();
if (email.isBlank()) email = currentUser.getEmail();
User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password);
SessionManager.login(updatedUser); // Update session with new user
System.out.println("Användaren har uppdaterats!");
}
public void updateUserMenu() {
// Make sure user is logged in
if (!SessionManager.isLoggedIn()) return;
User currentUser = SessionManager.getCurrentUser();
try {
System.out.println(appName());
System.out.println("UPPDATERA ANVÄNDARE\n=========================================================");
System.out.println("(Skriv 'avbryt' för att avbryta uppdateringen av användaren)");
String firstName = prompt("Nytt förnamn: ");
String lastName = prompt("Nytt efternamn: ");
String email = prompt("Ny email: ");
String password = prompt("Nytt lösenord: ");
// If user didn't enter anything, keep current value
if (firstName.isBlank()) firstName = currentUser.getFirstName();
if (lastName.isBlank()) lastName = currentUser.getLastName();
if (email.isBlank()) email = currentUser.getEmail();
if (password.isBlank()) password = currentUser.getPassword();
User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password);
SessionManager.login(updatedUser); // Update session with new user
System.out.println("Användaren har uppdaterats!");
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/UserCLI.java around lines 147 - 171, The
updateUserMenu sends a blank password to userService.updateUser which fails
validation; change the flow in updateUserMenu to treat an empty password as
"keep existing" by checking if password.isBlank() and in that case pass null (or
omit the password parameter / call an overload that preserves the password) to
userService.updateUser instead of the blank string, and update the prompt text
to indicate leaving the field blank keeps the current password; keep other
fields' blank-handling as-is and still call SessionManager.login(updatedUser)
after a successful update.

Comment on lines +186 to +198
public void deleteUser() {
// Make sure user is logged in
if (!SessionManager.isLoggedIn()) return;

System.out.print("Är du säker på att du vill radera ditt konto? (ja/nej): ");
if (scanner.nextLine().equalsIgnoreCase("ja")) {
userService.deleteUserById(SessionManager.getCurrentUser().getUserId());
SessionManager.logout();
System.out.println("Kontot har raderats och du har loggats ut.");
} else {
System.out.println("Du har valt att behålla kontot.");
}
}

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

Handle delete outcome (and missing current user safety).
deleteUserById(...) returns boolean but the CLI always prints success.

Proposed fix
         System.out.print("Är du säker på att du vill radera ditt konto? (ja/nej): ");
         if (scanner.nextLine().equalsIgnoreCase("ja")) {
-            userService.deleteUserById(SessionManager.getCurrentUser().getUserId());
-            SessionManager.logout();
-            System.out.println("Kontot har raderats och du har loggats ut.");
+            User current = SessionManager.getCurrentUser();
+            boolean deleted = current != null && userService.deleteUserById(current.getUserId());
+            SessionManager.logout();
+            if (deleted) System.out.println("Kontot har raderats och du har loggats ut.");
+            else System.out.println("Kunde inte radera kontot.");
         } else {
             System.out.println("Du har valt att behålla kontot.");
         }
📝 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
public void deleteUser() {
// Make sure user is logged in
if (!SessionManager.isLoggedIn()) return;
System.out.print("Är du säker på att du vill radera ditt konto? (ja/nej): ");
if (scanner.nextLine().equalsIgnoreCase("ja")) {
userService.deleteUserById(SessionManager.getCurrentUser().getUserId());
SessionManager.logout();
System.out.println("Kontot har raderats och du har loggats ut.");
} else {
System.out.println("Du har valt att behålla kontot.");
}
}
public void deleteUser() {
// Make sure user is logged in
if (!SessionManager.isLoggedIn()) return;
System.out.print("Är du säker på att du vill radera ditt konto? (ja/nej): ");
if (scanner.nextLine().equalsIgnoreCase("ja")) {
User current = SessionManager.getCurrentUser();
boolean deleted = current != null && userService.deleteUserById(current.getUserId());
SessionManager.logout();
if (deleted) System.out.println("Kontot har raderats och du har loggats ut.");
else System.out.println("Kunde inte radera kontot.");
} else {
System.out.println("Du har valt att behålla kontot.");
}
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/UserCLI.java around lines 186 - 198, The
CLI's deleteUser() ignores the boolean result from
userService.deleteUserById(...) and assumes a current user exists; update
deleteUser() to first check SessionManager.getCurrentUser() != null, then call
userService.deleteUserById(user.getUserId()), capture the returned boolean, and
only call SessionManager.logout() and print the success message when it returns
true; otherwise print a clear failure message indicating deletion failed.

Comment on lines +52 to +69
public User updateUser(Long id, String firstName, String lastName, String email, String password) {
// Get user by id
User user = userRepository.findById(id);

// Check if a user was found
if (user == null) {
throw new IllegalArgumentException("Kunde inte hitta användare med id: " + id);
}

validate(firstName, lastName, email, password);

// Update user
user.setFirstName(firstName);
user.setLastName(lastName);
user.setEmail(email);
user.setPassword(password);
return userRepository.updateUser(user);

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

Update validation: email uniqueness check blocks “keep same email”.
validate(...) always fails if any user already has that email — including the user being updated.

Proposed fix (check only when email changes, and exclude self)
 public class UserService {
-    public User updateUser(Long id, String firstName, String lastName, String email, String password) {
+    public User updateUser(Long id, String firstName, String lastName, String email, String password) {
         User user = userRepository.findById(id);
         if (user == null) {
             throw new IllegalArgumentException("Kunde inte hitta användare med id: " + id);
         }
-
-        validate(firstName, lastName, email, password);
+        validate(firstName, lastName, email, password);
+        // If email changed, enforce uniqueness excluding current user
+        if (!user.getEmail().equalsIgnoreCase(email)) {
+            User other = userRepository.findByEmail(email);
+            if (other != null && !other.getUserId().equals(id)) {
+                throw new IllegalArgumentException("En användare med denna emailadress finns redan.");
+            }
+        }

         user.setFirstName(firstName);
         user.setLastName(lastName);
         user.setEmail(email);
         user.setPassword(password);
         return userRepository.updateUser(user);
     }

-    private void validate (String firstName, String lastName, String email, String password) {
+    private void validate (String firstName, String lastName, String email, String password) {
         if (isInvalid(firstName) || isInvalid(lastName) || isInvalid(email) || isInvalid(password)) {
             throw new IllegalArgumentException("Inga fält får lämnas tomma.");
         }
         if (!EMAIL_PATTERN.matcher(email).matches()) {
             throw new IllegalArgumentException("Ogiltigt emailformat.");
         }
         if (password.length() < 3) {
             throw new IllegalArgumentException("Lösenordet måste vara minst 3 karaktärer långt.");
         }
-
-        // Check for existing email
-        if (userRepository.findByEmail(email) != null) {
-            throw new IllegalArgumentException("En användare med denna emailadress finns redan.");
-        }
     }

Also applies to: 115-135

🤖 Prompt for AI Agents
In @src/main/java/org/example/user/UserService.java around lines 52 - 69, The
validate(...) call in updateUser(Long id, ...) wrongly rejects the current
user's unchanged email because it checks global email uniqueness; change
validation so email-uniqueness is only enforced when the provided email differs
from the existing user's email: in updateUser fetch the current user via
userRepository.findById(id), compare currentUser.getEmail() with the incoming
email and only call or perform the uniqueness check (e.g.,
userRepository.findByEmail(email)) if they differ, ensuring the lookup excludes
the same user id when checking for collisions; apply the same pattern to the
other update path referenced around lines 115-135.

… private constructor to prevent instantiation

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/main/java/org/example/EMFactory.java:
- Around line 14-16: Fix the typo in the Javadoc/comment inside the
EMFactory.init() method: change "att app start" to "at app start" so the comment
reads correctly; update the comment text in the init() method of class EMFactory
accordingly.
- Around line 24-29: The close() method on EMFactory is an instance method but
the class has a private constructor, so close() can never be called; change the
signature of close() to static (public static void close()) so it can be invoked
without instantiation, remove the unnecessary emf != null check (emf is
initialized in the static initializer) but keep the emf.isOpen() guard and call
emf.close() when open; update any shutdown hook or caller to call
EMFactory.close() accordingly.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bff2351 and a24d51f.

📒 Files selected for processing (1)
  • src/main/java/org/example/EMFactory.java
🔇 Additional comments (2)
src/main/java/org/example/EMFactory.java (2)

7-8: Utility class design is appropriate.

The final class with static factory and private constructor follows the standard utility class pattern for managing a singleton EntityManagerFactory.


18-21: LGTM!

The method correctly returns a new EntityManager instance. The comment appropriately guides callers to use try-with-resources for proper resource management.

Comment thread src/main/java/org/example/EMFactory.java
Comment on lines +24 to +29
// Close the factory
public void close() {
if (emf != null && emf.isOpen()) {
emf.close();
}
}

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 | 🔴 Critical

Instance method on non-instantiable class — close() can never be called.

The close() method is an instance method, but the class has a private constructor preventing instantiation. This makes it impossible to call close(), leaving the EntityManagerFactory open and causing a resource leak on application shutdown.

Additionally, the emf != null check is unnecessary since emf is initialized in the static initializer and would throw at class load time if it failed.

🐛 Proposed fix: make the method static
-    // Close the factory
-    public void close() {
-        if (emf != null && emf.isOpen()) {
+    // Close the factory at application shutdown
+    public static void close() {
+        if (emf.isOpen()) {
             emf.close();
         }
     }
🤖 Prompt for AI Agents
In @src/main/java/org/example/EMFactory.java around lines 24 - 29, The close()
method on EMFactory is an instance method but the class has a private
constructor, so close() can never be called; change the signature of close() to
static (public static void close()) so it can be invoked without instantiation,
remove the unnecessary emf != null check (emf is initialized in the static
initializer) but keep the emf.isOpen() guard and call emf.close() when open;
update any shutdown hook or caller to call EMFactory.close() accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @src/main/java/org/example/user/testUserCli.java:
- Line 12: The main method in class testUserCli is package-private and therefore
not a valid JVM entry point; change the method signature of main (static void
main(String[] args)) to be public so it reads public static void main(String[]
args) to allow the JVM to launch the application. Ensure you update the
declaration for the method named main in class testUserCli only (no other
behavioral changes).
- Around line 41-43: The "2" case currently does nothing when
SessionManager.isLoggedIn() is false, causing silent ignores; update the switch
case handling for option "2" in testUserCli so it explicitly checks
SessionManager.isLoggedIn() and, if false, prints a user-facing message like
"You are not logged in" (or reuse the existing invalid choice message) and only
calls userCli.logout() when isLoggedIn() is true; ensure this change uses the
same output mechanism as other branches so users receive feedback instead of
silent no-ops.

In @src/main/java/org/example/user/UserCLI.java:
- Around line 96-102: In the catch blocks inside UserCLI where RuntimeException
is handled (the blocks that currently do if (e.getMessage().equals("AVBRUTET"))
{ return; }), avoid calling equals on a potentially null message; change the
check to a null-safe comparison (for example call equals on the literal or
explicitly null-check the message) so that exceptions with null messages do not
cause a NullPointerException; apply the same fix to the other two analogous
catch blocks in UserCLI that use e.getMessage().equals("AVBRUTET").
🧹 Nitpick comments (3)
src/main/java/org/example/user/testUserCli.java (1)

9-9: Class name should follow Java naming conventions.

The class name testUserCli starts with a lowercase letter, which violates Java naming conventions. Class names should use PascalCase.

Suggested rename
-public class testUserCli {
+public class TestUserCli {

Also rename the file to TestUserCli.java.

src/main/java/org/example/user/UserCLI.java (1)

207-216: Using exceptions for control flow is an anti-pattern.

Throwing a RuntimeException with message "AVBRUTET" to exit prompts is unconventional and can obscure actual runtime errors. Consider returning an Optional<String> or a sentinel value instead.

Alternative approach using Optional
-    private String prompt(String message) {
-        System.out.print(message);
-        String input = scanner.nextLine();
-        if (input.equalsIgnoreCase("avbryt")) {
-            throw new RuntimeException("AVBRUTET");
-        }
-        return input;
-    }
+    private Optional<String> prompt(String message) {
+        System.out.print(message);
+        String input = scanner.nextLine();
+        if (input.equalsIgnoreCase("avbryt")) {
+            return Optional.empty();
+        }
+        return Optional.of(input);
+    }

Then handle the empty optional in callers to exit the menu gracefully.

src/main/java/org/example/EMFactory.java (1)

14-16: Consider documenting the purpose of init() more explicitly.

The init() method is a no-op that relies on side-effect of class loading to initialize the static emf field. While this pattern works, the empty method body might confuse future maintainers.

Suggested documentation improvement
     public static void init(){
-        // Call this once to initialize the factory at app start
+        // No-op: calling this method triggers class loading, which initializes
+        // the static EntityManagerFactory. Call once at application startup
+        // to "warm up" the JPA subsystem before user interactions begin.
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a24d51f and e6ca94a.

📒 Files selected for processing (4)
  • src/main/java/org/example/EMFactory.java
  • src/main/java/org/example/user/UserCLI.java
  • src/main/java/org/example/user/testUserCli.java
  • src/main/resources/META-INF/persistence.xml
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/org/example/user/UserCLI.java (1)
src/main/java/org/example/user/SessionManager.java (1)
  • SessionManager (5-33)
src/main/java/org/example/user/testUserCli.java (3)
src/main/java/org/example/EMFactory.java (1)
  • EMFactory (7-30)
src/main/java/org/example/user/repository/JpaUserRepository.java (1)
  • JpaUserRepository (10-91)
src/main/java/org/example/user/SessionManager.java (1)
  • SessionManager (5-33)
🔇 Additional comments (5)
src/main/resources/META-INF/persistence.xml (2)

21-26: LGTM!

Disabling SQL logging, formatting, comments, and statistics is appropriate for a CLI application to provide a cleaner user experience.


16-18: This configuration is appropriate for local development. Since this is an educational GitHub Classroom project for learning JPA/Hibernate with a local MySQL instance, hardcoded credentials in persistence.xml are acceptable and standard practice in this context. No changes needed.

src/main/java/org/example/user/UserCLI.java (2)

6-14: LGTM!

Clean constructor injection pattern for dependencies.


168-172: [Your rewritten review comment text here]
[Exactly ONE classification tag]

src/main/java/org/example/EMFactory.java (1)

7-8: LGTM!

Good use of a utility class pattern with final class and static factory. The EntityManagerFactory is thread-safe and appropriate as a static singleton.

public class testUserCli {
private static final Scanner scanner = new Scanner(System.in);

static void main(String[] args) {

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 | 🔴 Critical

Missing public modifier on main method prevents application execution.

The main method is package-private (no access modifier), but it must be public static void main(String[] args) to serve as a valid Java application entry point. The JVM will not recognize this as an executable main method.

Fix
-    static void main(String[] args) {
+    public static void main(String[] args) {
📝 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
static void main(String[] args) {
public static void main(String[] args) {
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/testUserCli.java at line 12, The main method
in class testUserCli is package-private and therefore not a valid JVM entry
point; change the method signature of main (static void main(String[] args)) to
be public so it reads public static void main(String[] args) to allow the JVM to
launch the application. Ensure you update the declaration for the method named
main in class testUserCli only (no other behavioral changes).

Comment on lines +41 to +43
case "2" -> {
if (SessionManager.isLoggedIn()) userCli.logout();
}

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

Option "2" is silently ignored when not logged in.

When not logged in, the menu displays options "1" and "0" only, but if a user enters "2", the switch case executes and does nothing (since isLoggedIn() is false). This falls through without feedback, unlike the default case which prints "Ogiltigt val."

Suggested fix
                 case "2" -> {
-                    if (SessionManager.isLoggedIn()) userCli.logout();
+                    if (SessionManager.isLoggedIn()) {
+                        userCli.logout();
+                    } else {
+                        System.out.println("Ogiltigt val.");
+                    }
                 }
📝 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
case "2" -> {
if (SessionManager.isLoggedIn()) userCli.logout();
}
case "2" -> {
if (SessionManager.isLoggedIn()) {
userCli.logout();
} else {
System.out.println("Ogiltigt val.");
}
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/testUserCli.java around lines 41 - 43, The
"2" case currently does nothing when SessionManager.isLoggedIn() is false,
causing silent ignores; update the switch case handling for option "2" in
testUserCli so it explicitly checks SessionManager.isLoggedIn() and, if false,
prints a user-facing message like "You are not logged in" (or reuse the existing
invalid choice message) and only calls userCli.logout() when isLoggedIn() is
true; ensure this change uses the same output mechanism as other branches so
users receive feedback instead of silent no-ops.

Comment on lines +96 to +102
catch (RuntimeException e){
if (e.getMessage().equals("AVBRUTET")) {
return;
}
// Catch any other exceptions
throw e;
}

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

Potential NullPointerException when checking exception message.

If a RuntimeException is thrown with a null message, calling e.getMessage().equals("AVBRUTET") will throw a NullPointerException.

Suggested fix
             catch (RuntimeException e){
-                if (e.getMessage().equals("AVBRUTET")) {
+                if ("AVBRUTET".equals(e.getMessage())) {
                     return;
                 }
                 // Catch any other exceptions
                 throw e;
             }

The same pattern appears at lines 141-147 and 183-189.

📝 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
catch (RuntimeException e){
if (e.getMessage().equals("AVBRUTET")) {
return;
}
// Catch any other exceptions
throw e;
}
catch (RuntimeException e){
if ("AVBRUTET".equals(e.getMessage())) {
return;
}
// Catch any other exceptions
throw e;
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/user/UserCLI.java around lines 96 - 102, In the
catch blocks inside UserCLI where RuntimeException is handled (the blocks that
currently do if (e.getMessage().equals("AVBRUTET")) { return; }), avoid calling
equals on a potentially null message; change the check to a null-safe comparison
(for example call equals on the literal or explicitly null-check the message) so
that exceptions with null messages do not cause a NullPointerException; apply
the same fix to the other two analogous catch blocks in UserCLI that use
e.getMessage().equals("AVBRUTET").

@DennSel
DennSel merged commit e028d8a into main Jan 12, 2026
2 checks passed
@DennSel
DennSel deleted the features/account branch January 12, 2026 21:29
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