diff --git a/backend/Dockerfile b/backend/Dockerfile index fc70432..fd634e1 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -16,11 +16,22 @@ ENV USER=dev RUN groupadd -g ${GID} ${USER} \ && useradd -m -u ${UID} -g ${USER} ${USER} USER ${USER} -EXPOSE 8080 +EXPOSE 9090 CMD ["tail","-f","/dev/null"] #---Building +FROM base AS build +COPY . . +RUN mvn clean package -DskipTests +#--Testing +FROM build AS testing +RUN mvn clean test - -#---Production \ No newline at end of file +#---Production +# Pulled again to get rid of maven, to decrease the size +FROM openjdk:17-jdk-slim AS production +WORKDIR /app +COPY --from=build /app/target/*.jar app.jar +EXPOSE 9090 +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/backend/src/main/java/com/ninjaws/quiz/controllers/QuizController.java b/backend/src/main/java/com/ninjaws/quiz/controllers/QuizController.java index 69b89b8..b9f592e 100644 --- a/backend/src/main/java/com/ninjaws/quiz/controllers/QuizController.java +++ b/backend/src/main/java/com/ninjaws/quiz/controllers/QuizController.java @@ -23,28 +23,46 @@ public class QuizController { @Autowired private QuizService quizService; + /** + * Start a session + * @param amount The amount of questsions + * @param category The desired category (or all) + * @param difficulty The desired difficulty (or all) + * @param type The desired type (or both) + * @return The sessionId, or statusCode 5 when the queue is full + */ @GetMapping("/questions") public String startSession( - @RequestParam(required = true) Integer 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 ) { QuizSettings quizSettings = new QuizSettings(amount, category, difficulty, type); Optional result = quizService.createSession(quizSettings); - if(result.isPresent()) { + if (result.isPresent()) { String jsonString = String.format("{\"statusCode\":0,\"sessionId\":\"%s\"}", result.get()); return jsonString; } - String jsonString = String.format("{\"statusCode\":5}"); - return jsonString; - } + String jsonString = String.format("{\"statusCode\":5}"); + return jsonString; + } + /** + * Checks if the questions have been retrieved + * @param sessionId The session that holds the questions + * @return The Session, or nothing if they are not ready yet + */ @GetMapping("/status/{sessionId}") public Optional checkStatus(@PathVariable String sessionId) { return quizService.checkSession(sessionId); } + /** + * Receive the chosen answers, calculate the score, and return it + * @param answers The chosen answers + * @return The score + */ @PostMapping("/score") public String checkAnswers(@RequestBody Answers answers) { String jsonString = String.format("{\"score\":\"%s\"}", quizService.getScore(answers)); diff --git a/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java b/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java index b91bf88..7918f4f 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java @@ -48,4 +48,21 @@ public class ApiService { logger.log(System.Logger.Level.ERROR, "Critical failure", e); } } + + /** + * Collect a chuck of 50 items + * Store them in the DataService + */ + public void collectBulk() { + String url = apiUrl + "?amount=50"; + try { + ResponseEntity response = restTemplate.getForEntity(url, String.class); + String json = response.getBody(); + ApiResponse apiResponse = cleanerService.extractApiResponse(json); + responseHandler.handleResponse(apiResponse); + + } catch (RestClientException | IOException e) { + logger.log(System.Logger.Level.ERROR, "Critical failure", e); + } + } } diff --git a/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java b/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java index e642039..af391f0 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java @@ -26,7 +26,7 @@ public class ResponseHandlingService { switch (response.getResponseCode()) { case 0: - // Nothing + // Success updateSessionInCache(session, response); break; case 1: @@ -58,7 +58,7 @@ public class ResponseHandlingService { /** * If the request was made by the scheduler during queue-downtime, there is no session */ - public void switchInternal(ApiResponse response) { + public void handleResponse(ApiResponse response) { switch (response.getResponseCode()) { case 0: // TODO: Store in DB diff --git a/backend/src/test/java/com/ninjaws/quiz/services/backupApiServiceTest.txt b/backend/src/test/java/com/ninjaws/quiz/services/backupApiServiceTest.txt index 56d112b..1b3f44e 100644 --- a/backend/src/test/java/com/ninjaws/quiz/services/backupApiServiceTest.txt +++ b/backend/src/test/java/com/ninjaws/quiz/services/backupApiServiceTest.txt @@ -30,7 +30,7 @@ public class ApiServiceTest { @Test public void testRequestQuestions() throws Exception { QuizSettings settings = new QuizSettings(); - String jsonResponse = apiService.requestQuestions(settings); + String jsonResponse = apiService.handleRequest(settings); JsonNode jsonNode = objectMapper.readTree(jsonResponse); // Check if the response code is as expected diff --git a/docker-compose.yml b/docker-compose.yml index 9e0016c..16dd208 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,4 +7,13 @@ services: image: quiz-frontend-prod container_name: quiz-frontend-prod-cont ports: - - "4200:80" \ No newline at end of file + - "4200:80" + backend: + build: + context: ./backend + dockerfile: Dockerfile + target: production + image: quiz-backend-prod + container_name: quiz-backend-prod-cont + ports: + - "9090:9090" \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 619be8d..b59de8b 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -22,7 +22,7 @@ COPY package*.json ./ RUN npm install # Copied here, since the the Development setup uses a bind volume instead COPY . . -RUN npm run build --prod +RUN ng build --configuration production #--- Production FROM nginx:alpine AS production diff --git a/frontend/angular.json b/frontend/angular.json index 6b938b9..c9654d3 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -50,6 +50,12 @@ "maximumError": "4kB" } ], + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], "outputHashing": "all" }, "development": { diff --git a/frontend/src/app/Quiz/services/quiz.service.ts b/frontend/src/app/Quiz/services/quiz.service.ts index 67d1da2..5be7448 100644 --- a/frontend/src/app/Quiz/services/quiz.service.ts +++ b/frontend/src/app/Quiz/services/quiz.service.ts @@ -18,12 +18,13 @@ import { Answers } from '../models/answers'; import { StorageService } from './storage.service'; import { CleanerService } from './cleaner.service'; import { ResponseHandlerService } from './response-handler.service'; +import { environment } from '../../../environments/environment'; @Injectable({ providedIn: 'root', }) export class QuizService { - private apiUrl = 'http://localhost:9090/quiz'; + private apiUrl = environment.apiUrl + '/quiz'; constructor( private http: HttpClient, diff --git a/frontend/src/environments/environment.prod.ts b/frontend/src/environments/environment.prod.ts new file mode 100644 index 0000000..b605977 --- /dev/null +++ b/frontend/src/environments/environment.prod.ts @@ -0,0 +1,6 @@ +export const environment = { + production: true, + /** Connecting through the docker compose network is not working for now, so I'll have to use the localhost */ + // apiUrl: 'http://backend:9090', + apiUrl: 'http://localhost:9090', + }; \ No newline at end of file diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts new file mode 100644 index 0000000..53a7aba --- /dev/null +++ b/frontend/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + production: false, + apiUrl: 'http://localhost:9090' + }; \ No newline at end of file diff --git a/scripts/LoadTestMultiple.sh b/scripts/LoadTestMultiple.sh new file mode 100755 index 0000000..9122900 --- /dev/null +++ b/scripts/LoadTestMultiple.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +start_time=$(date +%s) + +for i in {1..6}; do + ./LoadTestOne.sh & + sleep 0.1 +done + +wait # Wait for all background jobs to finish + +end_time=$(date +%s) # Capture the end time + +# Calculate and print the elapsed time +elapsed_time=$((end_time - start_time)) +echo "Done!" +echo "Total time taken: $elapsed_time seconds" diff --git a/scripts/LoadTestOne.sh b/scripts/LoadTestOne.sh new file mode 100755 index 0000000..1137ee1 --- /dev/null +++ b/scripts/LoadTestOne.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Step 1: Get the sessionId from the questions endpoint +sessionIdJson=$(curl -s http://localhost:9090/quiz/questions?amount=5) + +statusCode=$(echo "$sessionIdJson" | grep -o '"statusCode":[0-9]*' | sed 's/"statusCode"://') + +if [ "$statusCode" -ne 0 ]; then + echo "Exiting: statusCode is not 0." + exit 1 +fi + +sessionId=$(echo "$sessionIdJson" | grep -o '"sessionId":"[^"]*"' | sed 's/"sessionId":"//;s/"//') + +if [ -z "$sessionId" ]; then + echo "Failed to get sessionId" + exit 1 +fi + +echo "Received sessionId: $sessionId" + +# Step 2: Poll the status endpoint every 2 seconds +while true; do + status_response=$(curl -s http://localhost:9090/quiz/status/"$sessionId") + + if [ $? -eq 0 ]; then + # Check if the response is JSON (starts with '{') + if [[ $status_response == \{* ]]; then + echo "$sessionId: done!" + break + fi + else + echo "Status check failed, retrying..." + fi + + sleep 2 +done diff --git a/scripts/benchmarks/basic.png b/scripts/benchmarks/basic.png new file mode 100644 index 0000000..8d600d1 Binary files /dev/null and b/scripts/benchmarks/basic.png differ