[Added] Most of the frontend, split up backend services
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package com.ninjaws.quiz.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**") // Allow all endpoints
|
||||
.allowedOrigins("http://localhost:4200") // Allow specific origin
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // Allow specific methods
|
||||
.allowedHeaders("*") // Allow all headers
|
||||
.allowCredentials(true); // Allow credentials
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.ninjaws.quiz.models.Answers;
|
||||
import com.ninjaws.quiz.models.QuizSettings;
|
||||
import com.ninjaws.quiz.models.Session;
|
||||
import com.ninjaws.quiz.services.QuizService;
|
||||
|
||||
@@ -24,21 +25,24 @@ public class QuizController {
|
||||
|
||||
@GetMapping("/questions")
|
||||
public String startSession(
|
||||
@RequestParam(required = true) String amount,
|
||||
@RequestParam(required = false) Integer category,
|
||||
@RequestParam(required = false) String difficulty,
|
||||
@RequestParam(required = false) String type
|
||||
@RequestParam(required = true) Integer amount,
|
||||
@RequestParam(required = false) Integer category,
|
||||
@RequestParam(required = false) String difficulty,
|
||||
@RequestParam(required = false) String type
|
||||
) {
|
||||
return quizService.createSession(quizService.jsonToQuizSettings("todo"));
|
||||
}
|
||||
QuizSettings quizSettings = new QuizSettings(amount, category, difficulty, type);
|
||||
String jsonString = String.format("{\"sessionId\":\"%s\"}", quizService.createSession(quizSettings));
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
@GetMapping("/status/{sessionId}")
|
||||
public Optional<Session> checkStatus(@PathVariable String sessionId) {
|
||||
return quizService.checkSession(sessionId);
|
||||
}
|
||||
|
||||
@PostMapping("/checkanswers")
|
||||
@PostMapping("/score")
|
||||
public String checkAnswers(@RequestBody Answers answers) {
|
||||
return quizService.getScore(answers);
|
||||
String jsonString = String.format("{\"score\":\"%s\"}", quizService.getScore(answers));
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import lombok.Setter;
|
||||
@Getter
|
||||
@Setter
|
||||
public class Answers {
|
||||
private final String sessionId;
|
||||
private String sessionId;
|
||||
private List<String> answers;
|
||||
|
||||
public Answers() {}
|
||||
|
||||
public Answers(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ninjaws.quiz.models;
|
||||
|
||||
import com.ninjaws.quiz.models.QuizSettings.Category;
|
||||
import com.ninjaws.quiz.models.QuizSettings.Difficulty;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
@@ -54,6 +55,21 @@ public class QuizSettings {
|
||||
private Category category = Category.ANY;
|
||||
|
||||
|
||||
public QuizSettings(){}
|
||||
|
||||
/** Inverse of toApiQuery */
|
||||
public QuizSettings(int amount, Integer category, String difficulty, String type) {
|
||||
System.out.println(amount);
|
||||
System.out.print(category);
|
||||
System.out.print(difficulty);
|
||||
System.out.print(type);
|
||||
|
||||
this.amount = amount;
|
||||
this.category = category == null ? Category.ANY : Category.values()[category];
|
||||
this.difficulty = difficulty == null ? Difficulty.ANY : Difficulty.valueOf(difficulty.toUpperCase());
|
||||
this.type = type == null ? Type.ANY : type.equals("boolean") ? Type.BOOLEAN : Type.MULTIPLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the value is between 1 and 50
|
||||
* @Valid doesn't seem to be enforcing it, so this ensures the value is legal
|
||||
|
||||
@@ -10,6 +10,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class Session {
|
||||
private final String id;
|
||||
private int statusCode = 0;
|
||||
private List<Question> questions;
|
||||
|
||||
public Session() {
|
||||
|
||||
@@ -12,48 +12,18 @@ import jakarta.validation.Valid;
|
||||
@Service
|
||||
public class ApiService {
|
||||
|
||||
// @Autowired
|
||||
// private ObjectMapper objectMapper;
|
||||
private final RestTemplate restTemplate;
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
private final String apiUrl = "https://opentdb.com/api.php";
|
||||
|
||||
// private String sessionToken = "";
|
||||
|
||||
@Autowired
|
||||
public ApiService(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
// try {
|
||||
// sessionToken = requestSessionToken();
|
||||
// } catch (Exception e) {
|
||||
// // Failed to get a session token, setting default to none
|
||||
// sessionToken = "";
|
||||
|
||||
// }
|
||||
public ApiService() {
|
||||
}
|
||||
|
||||
private String buildRequestString(QuizSettings quizSettings) {
|
||||
return apiUrl + quizSettings.toApiQuery();
|
||||
}
|
||||
|
||||
// private int getResponseCode(String json) throws Exception {
|
||||
// JsonNode jsonNode = objectMapper.readTree(json);
|
||||
// return jsonNode.get("response_code").asInt();
|
||||
// }
|
||||
|
||||
// public String requestSessionToken() throws Exception {
|
||||
// String sessionTokenRequestUrl = "https://opentdb.com/api_token.php?command=request";
|
||||
// ResponseEntity<String> response = restTemplate.getForEntity(sessionTokenRequestUrl, String.class);
|
||||
// JsonNode jsonNode = objectMapper.readTree(response.getBody());
|
||||
// if (jsonNode.get("response_code").asInt() == 0) {
|
||||
// sessionToken = jsonNode.get("token").asText();
|
||||
// }else {
|
||||
// // Failed to get a session token, setting default to none
|
||||
// sessionToken = "";
|
||||
// }
|
||||
// return sessionToken;
|
||||
// }
|
||||
|
||||
public String requestQuestions(@Valid QuizSettings quizSettings) {
|
||||
String url = buildRequestString(quizSettings);
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||
@@ -71,4 +41,8 @@ public class ApiService {
|
||||
// }
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
public void handleStatusCode(int statusCode) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.ninjaws.quiz.models.Session;
|
||||
|
||||
@Service
|
||||
public class CacheService {
|
||||
|
||||
/**
|
||||
* Stores all active sessions
|
||||
* Gets added to when a user requests data
|
||||
* Gets removed from when a user requests their score (by sending their answers)
|
||||
*/
|
||||
private final Cache<String,Session> sessions;
|
||||
|
||||
public CacheService() {
|
||||
sessions = (Caffeine.newBuilder()
|
||||
.expireAfterWrite(1,TimeUnit.HOURS)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void addToCache(String key, Session session) {
|
||||
sessions.put(key, session);
|
||||
}
|
||||
|
||||
public Session getFromCache(String sessionId) {
|
||||
return sessions.getIfPresent(sessionId);
|
||||
}
|
||||
|
||||
public void removeFromCache(String sessionId) {
|
||||
sessions.invalidate(sessionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ninjaws.quiz.models.QuizSettings;
|
||||
|
||||
@Service
|
||||
public class CleanerService {
|
||||
|
||||
public CleanerService() {}
|
||||
|
||||
public QuizSettings jsonToQuizSettings(String json){
|
||||
QuizSettings quizSettings = new QuizSettings();
|
||||
return quizSettings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ninjaws.quiz.models.Request;
|
||||
|
||||
@Service
|
||||
public class QueueService {
|
||||
|
||||
/**
|
||||
* Stores all user requests for data
|
||||
* Executes them in order, one every 5 seconds (the limit of the external API)
|
||||
* Removes them from the Queue when the request was succesful
|
||||
*/
|
||||
private final BlockingQueue<Request> requests = new LinkedBlockingQueue<>();
|
||||
|
||||
public void addToQueue(Request request) {
|
||||
requests.add(request);
|
||||
}
|
||||
|
||||
public Request popFromQueue() {
|
||||
return requests.poll();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,11 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.ninjaws.quiz.models.Answers;
|
||||
import com.ninjaws.quiz.models.ApiResponse;
|
||||
import com.ninjaws.quiz.models.Question;
|
||||
import com.ninjaws.quiz.models.QuizSettings;
|
||||
import com.ninjaws.quiz.models.Request;
|
||||
import com.ninjaws.quiz.models.Session;
|
||||
@@ -26,40 +15,9 @@ import com.ninjaws.quiz.models.Session;
|
||||
public class QuizService {
|
||||
|
||||
@Autowired
|
||||
private ApiService apiService;
|
||||
private CacheService cacheService;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* Stores all active sessions
|
||||
* Gets added to when a user requests data
|
||||
* Gets removed from when a user requests their score (by sending their answers)
|
||||
*/
|
||||
// private final Map<String,Session> sessions = new ConcurrentHashMap<>();
|
||||
private final Cache<String,Session> sessions;
|
||||
|
||||
/**
|
||||
* Stores all user requests for data
|
||||
* Executes them in order, one every 5 seconds (the limit of the external API)
|
||||
* Removes them from the Queue when the request was succesful
|
||||
*/
|
||||
private final BlockingQueue<Request> requests = new LinkedBlockingQueue<>();
|
||||
|
||||
|
||||
public QuizService() {
|
||||
sessions = (Caffeine.newBuilder()
|
||||
.expireAfterWrite(1,TimeUnit.HOURS)
|
||||
.build());
|
||||
}
|
||||
|
||||
public QuizService(Cache<String, Session> cache) {
|
||||
sessions = cache;
|
||||
}
|
||||
|
||||
public QuizSettings jsonToQuizSettings(String json){
|
||||
QuizSettings quizSettings = new QuizSettings();
|
||||
return quizSettings;
|
||||
}
|
||||
private QueueService queueService;
|
||||
|
||||
/**
|
||||
* Actions:
|
||||
@@ -71,11 +29,8 @@ public class QuizService {
|
||||
*/
|
||||
public String createSession(QuizSettings quizSettings) {
|
||||
Session session = new Session();
|
||||
try {
|
||||
requests.put(new Request(session.getId(), quizSettings));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
cacheService.addToCache(session.getId(), session);
|
||||
queueService.addToQueue(new Request(session.getId(), quizSettings));
|
||||
|
||||
return session.getId();
|
||||
}
|
||||
@@ -88,7 +43,7 @@ public class QuizService {
|
||||
* @return The data if it's available, Optional.empty() if not, and an Exception if the session is invalid
|
||||
*/
|
||||
public Optional<Session> checkSession(String sessionId) throws IllegalArgumentException {
|
||||
Session session = sessions.getIfPresent(sessionId);
|
||||
Session session = cacheService.getFromCache(sessionId);
|
||||
|
||||
if(session == null) {
|
||||
throw new IllegalArgumentException("Invalid session ID: " + sessionId);
|
||||
@@ -97,41 +52,9 @@ public class QuizService {
|
||||
if (session.getQuestions() == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
System.out.print(session.getQuestions().get(0).getIncorrectAnswers().get(0));
|
||||
return Optional.of(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every 5 seconds:
|
||||
* - Take the top request from the queue (removes it)
|
||||
* - Perform the request
|
||||
* - Wait for the data
|
||||
* - Find the matching session from the cache
|
||||
* - Add the data to the session in the cache
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000)
|
||||
protected void processQueue() {
|
||||
System.out.print("Schedule!");
|
||||
Request request = requests.poll();
|
||||
if (request != null) {
|
||||
System.out.print("----------------An item!");
|
||||
String json = apiService.requestQuestions(request.getQuizSettings());
|
||||
System.out.print(json);
|
||||
try {
|
||||
List<Question> questions = extractQuestions(json);
|
||||
Session session = sessions.getIfPresent(request.getSessionId());
|
||||
session.setQuestions(questions);
|
||||
sessions.put(request.getSessionId(), session);
|
||||
} catch (IOException e) {
|
||||
//TODO: Log this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Question> extractQuestions(String json) throws IOException {
|
||||
return objectMapper.readValue(json, ApiResponse.class).getResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives: sessionId and answers
|
||||
*
|
||||
@@ -142,16 +65,25 @@ public class QuizService {
|
||||
* - Calculates score
|
||||
* Returns: calculated score
|
||||
* */
|
||||
public String getScore(Answers answers) {
|
||||
Session session = sessions.getIfPresent(answers.getSessionId());
|
||||
sessions.invalidate(session.getId());
|
||||
public int getScore(Answers answers) {
|
||||
Session session = cacheService.getFromCache(answers.getSessionId());
|
||||
cacheService.removeFromCache(session.getId());
|
||||
return calculateScore(session, answers);
|
||||
}
|
||||
|
||||
private String calculateScore(Session session, Answers answers) {
|
||||
System.out.print(session);
|
||||
System.out.print(answers);
|
||||
return "You did great!";
|
||||
}
|
||||
|
||||
private int calculateScore(Session session, Answers answers) {
|
||||
int amountCorrect = 0;
|
||||
for(int i = 0; i < session.getQuestions().size(); i++) {
|
||||
String correctAnswer = session.getQuestions().get(i).getCorrectAnswer();
|
||||
String userAnswer = answers.getAnswers().get(i);
|
||||
// for(String answer : session.getQuestions().get(i).getAnswers()) {
|
||||
// System.out.print(answer);
|
||||
// }
|
||||
|
||||
if (correctAnswer.equalsIgnoreCase(userAnswer)) {
|
||||
amountCorrect++;
|
||||
}
|
||||
}
|
||||
return amountCorrect;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ninjaws.quiz.models.ApiResponse;
|
||||
import com.ninjaws.quiz.models.Question;
|
||||
import com.ninjaws.quiz.models.Request;
|
||||
import com.ninjaws.quiz.models.Session;
|
||||
|
||||
@Service
|
||||
public class SchedulerService {
|
||||
|
||||
@Autowired
|
||||
private QueueService queueService;
|
||||
@Autowired
|
||||
private ApiService apiService;
|
||||
@Autowired
|
||||
private CacheService cacheService;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private static final System.Logger logger = System.getLogger(SchedulerService.class.getName());
|
||||
|
||||
public SchedulerService() {}
|
||||
|
||||
/**
|
||||
* Every 5 seconds:
|
||||
* - Take the top request from the queue (removes it)
|
||||
* - Perform the request
|
||||
* - Wait for the data
|
||||
* - Find the matching session from the cache
|
||||
* - Add the data to the session in the cache
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000)
|
||||
protected void processQueue() {
|
||||
Request request = queueService.popFromQueue();
|
||||
if (request != null) {
|
||||
String json = apiService.requestQuestions(request.getQuizSettings());
|
||||
try {
|
||||
List<Question> questions = extractQuestions(json);
|
||||
Session session = cacheService.getFromCache(request.getSessionId());
|
||||
session.setQuestions(questions);
|
||||
cacheService.addToCache(request.getSessionId(), session);
|
||||
} catch (IOException | NullPointerException e) {
|
||||
logger.log(System.Logger.Level.ERROR, "Critical failure", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Question> extractQuestions(String json) throws IOException {
|
||||
return objectMapper.readValue(json, ApiResponse.class).getResults();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
public class StorageService {
|
||||
|
||||
}
|
||||
+1
-2
@@ -1,11 +1,10 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
// @EnableScheduling
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
// @TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class QuizServiceTest {
|
||||
|
||||
// @InjectMocks
|
||||
Reference in New Issue
Block a user