Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
bef35a2
Adds AccountRepository
DennSel Jan 8, 2026
067c402
Implemented Entity Manager Factory
DennSel Jan 8, 2026
b102ca3
Implemented JPA account repository
DennSel Jan 8, 2026
9dae2e7
Implemented account business logic
DennSel Jan 8, 2026
60b6a19
Replaced deleteAccount
DennSel Jan 9, 2026
6a02015
Added username generation logic
DennSel Jan 9, 2026
bcbd434
Added checks to account creation
DennSel Jan 9, 2026
148d3ec
Created AccountService unit tests
DennSel Jan 9, 2026
8339c00
Moved into package "account"
DennSel Jan 10, 2026
071cefb
SessionManager handling user login,
DennSel Jan 10, 2026
c65394f
Changed names from account to user
DennSel Jan 10, 2026
282a1e9
IT for handling a full user lifecycle
DennSel Jan 10, 2026
0f99774
IT for handling username collisions
DennSel Jan 10, 2026
23d3547
IT for duplicate email exception handling
DennSel Jan 10, 2026
01ce920
IT for finding user by email (JPQL)
DennSel Jan 11, 2026
5ed1039
IT for finding user by username (JPQL)
DennSel Jan 11, 2026
41cf253
IT for deleting wrong user
DennSel Jan 11, 2026
417f5ef
IT for finding all users (JPQL)
DennSel Jan 11, 2026
b271b79
Added initialization method
DennSel Jan 11, 2026
faf72f7
Changed methods to public for better accessibility
DennSel Jan 11, 2026
9cc15b4
Changed exception messages to Swedish
DennSel Jan 11, 2026
c5f371d
Added UserCLI class with user management functionalities
DennSel Jan 11, 2026
5dd8338
Fixed wrong errormessages between UserService and tests
DennSel Jan 11, 2026
986c38d
Added constructor to EMFactory to block instantiation
DennSel Jan 11, 2026
e7bddfb
Added getFullName and getDisplayName methods to User
DennSel Jan 11, 2026
f5671e4
Added loggedInDisplayName method to SessionManager
DennSel Jan 11, 2026
e3d476b
Improved UserCLI menus with enhanced prompts, streamlined flow, and a…
DennSel Jan 11, 2026
7494f7b
Refined exception message in UserService for consistency
DennSel Jan 11, 2026
7616e3b
Made EMFactory implement AutoCloseable
DennSel Jan 11, 2026
4eacd1a
Improved UserCLI workflows by refining menu conditions and removing u…
DennSel Jan 11, 2026
dec4c1d
Fixed error in test
DennSel Jan 12, 2026
114d261
Changed error handling in JpaUserRepository
DennSel Jan 12, 2026
9d144a2
Allow blank inputs in UserCLI to retain existing user details during …
DennSel Jan 12, 2026
bff2351
Added validation to updateUser
DennSel Jan 12, 2026
a24d51f
Made EMFactory final, removed AutoCloseable implementation, and added…
DennSel Jan 12, 2026
fd0c8f0
Made `close` method static in EMFactory and fixed typos in comments
DennSel Jan 12, 2026
840f901
Improved UserCLI menus by displaying logged-in user details and refin…
DennSel Jan 12, 2026
6e5d99a
Added testUserCli class to provide a command-line interface for user …
DennSel Jan 12, 2026
e6ca94a
Disabled Hibernate SQL logging
DennSel Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/main/java/org/example/EMFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.example;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

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

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


// Close the factory
public static void close() {
if (emf.isOpen()) {
emf.close();
}
}
Comment on lines +24 to +29

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.

}
8 changes: 8 additions & 0 deletions src/main/java/org/example/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getFullName() {
return firstName + " " + lastName;
}

public String getDisplayName() {
return getFullName() + " (" + username + ")";
}

public Long getUserId() {
return userId;
}
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/org/example/user/SessionManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.example.user;

import org.example.User;

public class SessionManager {
private static User currentUser = null;

// Log in user by setting currentUser to the passed in user
public static void login(User user) {
currentUser = user;
}

// Log out user by setting currentUser to null
public static void logout() {
currentUser = null;
}

public static User getCurrentUser() {
return currentUser;
}

public static String loggedInDisplayName() {
if (currentUser != null)
return currentUser.getDisplayName();
else
return "Gäst";
}

// Returns true if a user is logged in
public static boolean isLoggedIn() {
return currentUser != null;
}
}
222 changes: 222 additions & 0 deletions src/main/java/org/example/user/UserCLI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package org.example.user;

import org.example.User;
import java.util.Scanner;

public class UserCLI {
private final UserService userService;
private final Scanner scanner;

// Constructor
public UserCLI(UserService userService, Scanner scanner) {
this.userService = userService;
this.scanner = scanner;
}

public void userMenu() {
// Make sure user is not already logged in
if (SessionManager.isLoggedIn()) return;

boolean closeMenu = false;
while (!closeMenu && !SessionManager.isLoggedIn()) {
System.out.println(appName());
System.out.println("Logga in/Skapa användare Inloggad som: " + SessionManager.loggedInDisplayName());
System.out.println("========================================================");
System.out.println("1. Logga in | 2. Skapa användare | 3. Tillbaka");
System.out.print("Menyval: ");
String choice = scanner.nextLine();
switch (choice) {
case "1" -> loginMenu();
case "2" -> createUserMenu();
case "3" -> closeMenu = true;
default -> System.out.println("Ogiltigt val.");
}

// Check if user is logged in, if so, close menu
if (SessionManager.isLoggedIn()) {
closeMenu = true;
}
}
}

public void manageUserMenu() {
// Make sure user is logged in
if (!SessionManager.isLoggedIn()) return;

boolean closeMenu = false;
while (!closeMenu && SessionManager.isLoggedIn()) {
System.out.println(appName());
System.out.println("Hantera användare Inloggad som: " + SessionManager.loggedInDisplayName());
System.out.println("========================================================");
System.out.println("1. Uppdatera användare | 2. Radera användare | 3. Logga ut | 4. Tillbaka");
System.out.print("Menyval: ");
String choice = scanner.nextLine();
switch (choice) {
case "1" -> updateUserMenu();
case "2" -> deleteUser();
case "3" -> logout();
case "4" -> closeMenu = true;
default -> System.out.println("Ogiltigt val.");
}
}
}

public void loginMenu() {
// Make sure user is not already logged in
if (SessionManager.isLoggedIn()) return;

while (!SessionManager.isLoggedIn()) {
try {
System.out.println(appName());
System.out.println("Inloggningsuppgifter Inloggad som: " + SessionManager.loggedInDisplayName());
System.out.println("========================================================");
System.out.println("(Skriv 'avbryt' för att avbryta inloggningen)");
// Ask for credentials, prompt to enable exiting from menu
String username = prompt("Användarnamn: ");
String password = prompt("Lösenord: ");

// Check credentials with database
User user = userService.login(username, password);

// Tell SessionManager to remember user
SessionManager.login(user);

if (SessionManager.isLoggedIn()){
System.out.println("Inloggningen lyckades. Välkommen " + user.getFirstName() + "!");
}
else {
System.out.println("Inloggningen misslyckades.");
}
}
// Catch validation errors
catch (IllegalArgumentException e) {
System.out.println("Felmeddelande: " + e.getMessage());
}
// Catch our "exit" exception"
catch (RuntimeException e){
if (e.getMessage().equals("AVBRUTET")) {
return;
}
// Catch any other exceptions
throw e;
}
Comment on lines +96 to +102

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").

}
}

public void logout() {
SessionManager.logout();
System.out.println("Du har loggats ut.");
}

public void createUserMenu() {
// Make sure user is not already logged in
if (SessionManager.isLoggedIn()) return;

boolean success = false;

while (!success) {
try {
System.out.println(appName());
System.out.println("Skapa användare Inloggad som: " + SessionManager.loggedInDisplayName());
System.out.println("========================================================");
System.out.println("(Skriv 'avbryt' för att avbryta skapandet av ny användare)");

String firstName = prompt("Förnamn: ");
String lastName = prompt("Efternamn: ");
String email = prompt("Email: ");
String password = prompt("Lösenord: ");

// Create user
User user = userService.createUser(firstName, lastName, email, password);

System.out.println("Välkommen " + user.getFirstName() + "!");
System.out.println("Ditt användarnamn är: " + user.getUsername());
success = true;
}
// Catch validation errors
catch (IllegalArgumentException e) {
System.out.println("Felmeddelande: " + e.getMessage());
}
// Catch our "exit" exception"
catch (RuntimeException e){
if (e.getMessage().equals("AVBRUTET")) {
return;
}
// Catch any other exceptions
throw e;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

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 Inloggad som: " + SessionManager.loggedInDisplayName());
System.out.println("========================================================");
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!");
}
Comment on lines +151 to +177

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.

// Catch validation errors
catch (IllegalArgumentException e) {
System.out.println("Kunde inte uppdatera: " + e.getMessage());
}
// Catch our "exit" exception"
catch (RuntimeException e){
if (e.getMessage().equals("AVBRUTET")) {
return;
}
// Catch any other exceptions
throw e;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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 din användare? (ja/nej): ");
if (scanner.nextLine().equalsIgnoreCase("ja")) {
userService.deleteUserById(SessionManager.getCurrentUser().getUserId());
SessionManager.logout();
System.out.println("Användaren har raderats och du har loggats ut.");
} else {
System.out.println("Du har valt att behålla användaren.");
}
}
Comment on lines +192 to +204

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.


// Method to enable user to exit menu
private String prompt(String message) {
// Print the passed argument
System.out.print(message);
String input = scanner.nextLine();
// Exit from current menu if "avbryt" is entered
if (input.equalsIgnoreCase("avbryt")) {
throw new RuntimeException("AVBRUTET");
}
return input;
}

private String appName() {
return "\n\nBIBLIOTEKSSYSTEMET";
}

}
Loading