diff --git a/backend/pom.xml b/backend/pom.xml
index fc1b4be5..e0311c9b 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -67,6 +67,34 @@
spring-security-test
test
+
+ org.xerial
+ sqlite-jdbc
+ 3.42.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.hibernate.orm
+ hibernate-community-dialects
+ 6.2.0.Final
+
+
+ org.mockito
+ mockito-core
+
+
+ org.testng
+ testng
+ 6.13.1
+ compile
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
diff --git a/backend/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java b/backend/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java
index 1684fe68..247767d9 100644
--- a/backend/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java
+++ b/backend/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java
@@ -2,65 +2,79 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.CommandLineRunner;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import java.io.IOException;
import java.nio.file.*;
import java.util.Arrays;
import java.util.List;
-@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, SecurityAutoConfiguration.class })
+@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
+@EnableJpaRepositories("com.UoB.AILearningTool.repository")
public class AiLearningToolApplication implements CommandLineRunner {
- public static void main(String[] args) {
- // disable SSL if launched without keystore.p12
- if (!Files.exists(Paths.get("src/main/resources/keystore.p12"))) {
- // Force disable SSL
- System.setProperty("server.ssl.enabled", "false");
- }
- SpringApplication.run(AiLearningToolApplication.class, args);
- }
+ public static void main(String[] args) {
+ // disable SSL if launched without keystore.p12
+ if (!Files.exists(Paths.get("src/main/resources/keystore.p12"))) {
+ // Force disable SSL
+ System.setProperty("server.ssl.enabled", "false");
+ }
+ SpringApplication.run(AiLearningToolApplication.class, args);
+ }
- @Override
- public void run(String... args) throws Exception {
- Path keystorePath = Paths.get("src", "main", "resources", "keystore.p12");
+ @Override
+ public void run(String... args) throws Exception {
+ Path keystorePath = Paths.get("src", "main", "resources", "keystore.p12");
- // If there's no keystore in the working directory, skip the SSL setup.
- if (!Files.exists(keystorePath)) {
- System.out.println("No keystore.p12 found in working directory; skipping SSL copy/overwrite");
- return;
- }
+ // If there's no keystore in the working directory, skip the SSL setup.
+ if (!Files.exists(keystorePath)) {
+ System.out.println("No keystore.p12 found in working directory; skipping SSL copy/overwrite");
+ return;
+ }
- Path applicationProps = Paths.get("src", "main", "resources", "application.properties");
- overwriteApplicationProperties(applicationProps);
- }
+ // Otherwise, proceed with the copy and overwrite
+ Path resourcesDir = Paths.get("src", "main", "resources");
+ Path targetKeystore = resourcesDir.resolve("keystore.p12");
+ copyFile(keystorePath, targetKeystore);
+ System.out.println("keystore.p12 copied to src/main/resources successfully");
- void copyFile(Path source, Path target) throws IOException {
- Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
- }
+ Path applicationProps = Paths.get("src", "main", "resources", "application.properties");
+ overwriteApplicationProperties(applicationProps);
+ System.out.println("application.properties has been updated successfully");
+ }
- private void overwriteApplicationProperties(Path propsFile) throws IOException {
- List lines = Arrays.asList(
- "spring.application.name=AILearningTool",
- "server.port=8080",
- "spring.servlet.multipart.max-file-size=50MB",
- "spring.servlet.multipart.max-request-size=50MB",
- "spring.web.resources.static-locations=classpath:/static/",
- "",
- "spring.http.encoding.charset=UTF-8",
- "spring.http.encoding.enabled=true",
- "spring.http.encoding.force=true",
- "",
- "server.ssl.key-store=classpath:keystore.p12",
- "server.ssl.key-store-password=ailearntool",
- "server.ssl.key-store-type=PKCS12",
- "server.ssl.key-alias=myalias"
- );
- System.out.println("application.properties has been updated successfully");
- Files.write(propsFile, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE );
- }
-}
+ private void copyFile(Path source, Path target) throws IOException {
+ Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ private void overwriteApplicationProperties(Path propsFile) throws IOException {
+ List lines = Arrays.asList(
+ "spring.application.name=AILearningTool",
+ "server.port=8080",
+ "spring.servlet.multipart.max-file-size=50MB",
+ "spring.servlet.multipart.max-request-size=50MB",
+ "spring.web.resources.static-locations=classpath:/static/",
+ "",
+ "spring.http.encoding.charset=UTF-8",
+ "spring.http.encoding.enabled=true",
+ "spring.http.encoding.force=true",
+ "",
+ "server.ssl.key-store=classpath:keystore.p12",
+ "server.ssl.key-store-password=ailearntool",
+ "server.ssl.key-store-type=PKCS12",
+ "server.ssl.key-alias=myalias",
+
+ "spring.datasource.url=jdbc:sqlite:database.db",
+ "spring.datasource.driver-class-name=org.sqlite.JDBC",
+ "spring.datasource.username=",
+ "spring.datasource.password=",
+ "spring.jpa.database-platform=org.hibernate.dialect.SQLiteDialect",
+ "spring.jpa.hibernate.ddl-auto=update"
+ );
+ Files.write(propsFile, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/UoB/AILearningTool/Chat.java b/backend/src/main/java/com/UoB/AILearningTool/Chat.java
deleted file mode 100644
index 1104e867..00000000
--- a/backend/src/main/java/com/UoB/AILearningTool/Chat.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.UoB.AILearningTool;
-
-import org.json.*;
-
-public class Chat {
- final private String owner;
- private final JSONArray messageHistory = new JSONArray();
- private final String threadID;
-
- public Chat(User user, String initialMessage, String threadID) {
- this.owner = user.getID();
- addUserMessage(user.getID(), initialMessage);
- this.threadID = threadID;
- }
-
- public Boolean checkOwner(User user) {
- return this.owner.equals(user.getID());
- }
-
- public String getThreadID() {
- return this.threadID;
- }
-
- public void addUserMessage(String userID, String message) {
- if (this.owner.equals(userID)) {
- JSONObject newMessage = new JSONObject();
- newMessage.put("role", "user");
- newMessage.put("content", message);
- this.messageHistory.put(newMessage);
- System.out.println("User message: " + message + "has been saved.");
- }
- }
-
- public void addAIMessage(String userID, String message) {
- if (this.owner.equals(userID)) {
- JSONObject newMessage = new JSONObject();
- newMessage.put("role", "assistant");
- newMessage.put("content", message);
- this.messageHistory.put(newMessage);
- System.out.println("AI message: " + message + "has been saved.");
- }
- }
-
- public JSONArray getMessageHistory(User user) {
- if (checkOwner(user)) {
- return this.messageHistory;
- } else {return null;}
- }
-}
diff --git a/backend/src/main/java/com/UoB/AILearningTool/DatabaseController.java b/backend/src/main/java/com/UoB/AILearningTool/DatabaseController.java
index b14374a3..dc86fdd6 100644
--- a/backend/src/main/java/com/UoB/AILearningTool/DatabaseController.java
+++ b/backend/src/main/java/com/UoB/AILearningTool/DatabaseController.java
@@ -1,72 +1,102 @@
package com.UoB.AILearningTool;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
+import com.UoB.AILearningTool.model.ChatEntity;
+import com.UoB.AILearningTool.model.UserEntity;
+import com.UoB.AILearningTool.repository.ChatRepository;
+import com.UoB.AILearningTool.repository.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import java.util.Optional;
-// Communication with SQL database.
@Service
public class DatabaseController {
- private Map users = new HashMap<>();
- private Map chats = new HashMap<>();
+ private final UserRepository userRepository;
+ private final ChatRepository chatRepository;
- public User getUser(String userID) {
- return users.get(userID);
+ @Autowired
+ public DatabaseController(UserRepository userRepository, ChatRepository chatRepository) {
+ this.userRepository = userRepository;
+ this.chatRepository = chatRepository;
}
- public DatabaseController() {
- // TODO: Connect to a MariaDB database.
+ public UserEntity getUser(String username) {
+ return userRepository.findById(username).orElse(null);
}
- // Create a new user and return their ID for cookie assignment
- public String addUser(boolean optionalConsent) {
- User user = new User(optionalConsent);
- String id = user.getID();
- // TODO: Add a user profile record to the MariaDB database.
- users.put(id, user);
- return id;
+ // Create a new account
+ public boolean addUser(String username, String password) {
+ if (userRepository.existsById(username)) {
+ return false;
+ }
+
+ UserEntity user = new UserEntity(username, password);
+ userRepository.save(user);
+ return true;
}
- // Remove all data stored about the user (profile, chat, etc.)
- public boolean removeUser(String id) {
- // TODO: Remove a user profile record from the MariaDB database.
- if (users.containsKey(id)) {
- users.remove(id);
+ // Delete existing user account
+ public boolean removeUser(String sessionID) {
+ Optional user = userRepository.findBySessionID(sessionID);
+
+ // TODO: Delete chats as well
+
+ if (user.isPresent()) {
+ userRepository.delete(user.get());
return true;
- } else {return false;}
+ } else {
+ return false;
+ }
}
// Creates a new chat
- public String createChat(User user, String initialMessage, String threadID) {
+ public String createChat(UserEntity user, String initialMessage, String threadID) {
String id = StringTools.RandomString(20);
- chats.put(id, new Chat(user, initialMessage, threadID));
- return id;
+ ChatEntity chat = new ChatEntity(user, initialMessage, threadID);
+ chatRepository.save(chat);
+ return chat.getChatID();
}
+
// Deletes an existing chat
- public Boolean deleteChat(User user, String chatID) {
- Boolean success;
- Chat chat = chats.get(chatID);
- if (chat != null) {
- if (chat.checkOwner(user)) {
- chats.remove(chatID);
- return true;
- }
+ public boolean deleteChat(UserEntity user, String chatID) {
+ Optional chatOpt = chatRepository.findById(chatID);
+ if (chatOpt.isPresent() && chatOpt.get().getOwner().getUsername().equals(user.getUsername())) {
+ chatRepository.deleteById(chatID);
+ return true;
}
return false;
}
- public Chat getChat(User user, String chatID) {
- Chat chat = chats.get(chatID);
- if (chat != null) {
- if (chat.checkOwner(user)) {
- return chat;
- } else {return null;}
- } else {
+ public ChatEntity createChat(String sessionID, String initialMessage) {
+ Optional userOpt = userRepository.findBySessionID(sessionID);
+ if (userOpt.isEmpty()) {
+ return null;
+ }
+
+ UserEntity user = userOpt.get();
+ ChatEntity chat = new ChatEntity(user, initialMessage, sessionID);
+ chatRepository.save(chat);
+
+ return chat;
+ }
+
+ public ChatEntity getChat(String username, String chatID) {
+ Optional userOpt = userRepository.findById(username);
+ if (userOpt.isEmpty()) {
return null;
}
+
+ Optional chatOpt = chatRepository.findById(chatID);
+ return chatOpt.filter(chat -> chat.getOwner().getUsername().equals(username)).orElse(null);
}
-}
+ public boolean deleteChat(String username, String chatID) {
+ Optional chatOpt = chatRepository.findById(chatID);
+ if (chatOpt.isPresent() && chatOpt.get().getOwner().getUsername().equals(username)) {
+ chatRepository.deleteById(chatID);
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java b/backend/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java
index 231c2e8a..e0196e59 100644
--- a/backend/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java
+++ b/backend/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java
@@ -1,5 +1,6 @@
package com.UoB.AILearningTool;
+import com.UoB.AILearningTool.model.ChatEntity;
import org.json.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,7 +84,7 @@ public int runThread(String threadID) {
}
// Check if a run is in progress for a thread
- public boolean isLocked(Chat chat) {
+ public boolean isLocked(ChatEntity chat) {
HttpRequest listMessagesRequest = HttpRequest
.newBuilder(URI.create("https://api.openai.com/v1/threads/" + chat.getThreadID() + "/runs"))
.headers("Content-Type", "application/json",
@@ -138,7 +139,7 @@ public WatsonxResponse getLastThreadMessage(String threadID) {
}
// Add a message to an OpenAI thread.
- public Integer sendUserMessage(Chat chat, String message) {
+ public Integer sendUserMessage(ChatEntity chat, String message) {
// Creating a request body
JSONObject newMessage = new JSONObject();
newMessage.put("role", "user");
diff --git a/backend/src/main/java/com/UoB/AILearningTool/SpringController.java b/backend/src/main/java/com/UoB/AILearningTool/SpringController.java
index d1b40ced..4baca76b 100644
--- a/backend/src/main/java/com/UoB/AILearningTool/SpringController.java
+++ b/backend/src/main/java/com/UoB/AILearningTool/SpringController.java
@@ -1,267 +1,251 @@
package com.UoB.AILearningTool;
-import jakarta.servlet.http.Cookie;
-import jakarta.servlet.http.HttpServletResponse;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.*;
+import com.UoB.AILearningTool.model.ChatEntity;
+import com.UoB.AILearningTool.model.UserEntity;
+import com.UoB.AILearningTool.repository.ChatRepository;
+import com.UoB.AILearningTool.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import java.io.IOException;
-
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.*;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.*;
-@RestController // Use RestController for handling HTTP requests
-@Service @Controller
+@RestController
public class SpringController {
- private final Logger log = LoggerFactory.getLogger(SpringController.class);
+ private static final Logger log = LoggerFactory.getLogger(SpringController.class);
private final DatabaseController DBC;
- private final OpenAIAPIController WXC;
+ private final OpenAIAPIController OAIC;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private ChatRepository chatRepository;
@Autowired
- public SpringController(DatabaseController DBC, OpenAIAPIController WXC) {
+ public SpringController(DatabaseController DBC, OpenAIAPIController OAIC) {
this.DBC = DBC;
- this.WXC = WXC;
+ this.OAIC = OAIC;
}
- private final Map userStore = new ConcurrentHashMap<>();
- @PostMapping("/login")
- public ResponseEntity