Skip to content

Commit

Permalink
Merge pull request #253 from Arquisoft/develop
Browse files Browse the repository at this point in the history
merge changes in backend
  • Loading branch information
Toto-hitori authored Apr 19, 2024
2 parents c075fc0 + 637ea18 commit 67bdfc8
Show file tree
Hide file tree
Showing 20 changed files with 409 additions and 47 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ jobs:
DATABASE_PASSWORD
JWT_SECRET
SSL_PASSWORD
docker-push-prometheus:
runs-on: ubuntu-latest
needs: [ e2e-tests ]
steps:
- uses: actions/checkout@v4
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@v5
with:
name: arquisoft/wiq_en2b/prometheus
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
workdir: api/monitoring/prometheus
docker-push-grafana:
runs-on: ubuntu-latest
needs: [ e2e-tests ]
steps:
- uses: actions/checkout@v4
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@v5
with:
name: arquisoft/wiq_en2b/grafana
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
workdir: api/monitoring/grafana
docker-push-kiwiq:
runs-on: ubuntu-latest
needs: [ e2e-tests ]
Expand Down
14 changes: 14 additions & 0 deletions api/monitoring/grafana/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM grafana/grafana
LABEL authors="dario"
# Define the source and destination directories
COPY_SOURCE = ./provisioning
COPY_DESTINATION = /etc/grafana/provisioning

# Copy the configuration files
COPY ${COPY_SOURCE}/* ${COPY_DESTINATION}

# Expose the default Grafana port
EXPOSE 9091

# Run Grafana in the foreground
CMD ["grafana-server"]
14 changes: 14 additions & 0 deletions api/monitoring/prometheus/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM prom/prometheus
LABEL authors="dario"
# Define the source and destination directories
COPY_SOURCE = ./configuration
COPY_DESTINATION = /etc/prometheus

# Copy the configuration files
COPY ${COPY_SOURCE}/* ${COPY_DESTINATION}

# Expose the default Prometheus port
EXPOSE 9090

# Run Prometheus in the foreground
CMD ["prometheus"]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ scrape_configs:
metrics_path: '/actuator/prometheus'
scrape_interval: 10s
static_configs:
- targets: ['host.docker.internal:8080']
- targets: ['host.docker.internal:8443']
labels:
application: 'WIQ API'
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, Authentication
.cors(Customizer.withDefaults())
.sessionManagement(configuration -> configuration.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.POST,"/questions/**").permitAll()
.requestMatchers(HttpMethod.GET,"/questions/**").permitAll()
.requestMatchers(HttpMethod.GET,"/users/details").authenticated()
.requestMatchers(HttpMethod.GET,"/users","/users/**").permitAll()
.requestMatchers(HttpMethod.GET,"/auth/logout").authenticated()
.requestMatchers(HttpMethod.POST,"/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/swagger/**").permitAll()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package lab.en2b.quizapi.commons.user;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lab.en2b.quizapi.commons.user.dtos.UserResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
Expand All @@ -23,9 +27,35 @@ public class UserController {
* @param authentication the authentication object
* @return the response dto for the user details
*/
@Operation(summary = "Gets the user details", description = "Gets the user details for the given authentication")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@GetMapping("/details")
public ResponseEntity<UserResponseDto> getUserDetails(Authentication authentication) {
return ResponseEntity.ok(userService.getUserDetailsByAuthentication(authentication));
}

@Operation(summary = "Gets all users", description = "Gets all users")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved")
})
@GetMapping
public ResponseEntity<List<UserResponseDto>> getUsers() {
return ResponseEntity.ok(userService.getUsers());
}

@Operation(summary = "Gets a user", description = "Gets a user")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved")
})
@Parameters({
@Parameter(name = "id", description = "The id of the user to get", example = "1")
})
@GetMapping("/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUser(id));
}

}
10 changes: 10 additions & 0 deletions api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -71,4 +73,12 @@ public User getUserByAuthentication(Authentication authentication) {
public UserResponseDto getUserDetailsByAuthentication(Authentication authentication) {
return userResponseDtoMapper.apply(getUserByAuthentication(authentication));
}

public List<UserResponseDto> getUsers() {
return userRepository.findAll().stream().map(userResponseDtoMapper).collect(Collectors.toList());
}

public UserResponseDto getUser(Long id) {
return userResponseDtoMapper.apply(userRepository.findById(id).orElseThrow());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lab.en2b.quizapi.game.Game;
import lab.en2b.quizapi.game.GameMode;
import lab.en2b.quizapi.questions.question.QuestionCategory;
import lab.en2b.quizapi.questions.question.dtos.QuestionCategoryDto;

import java.util.List;

Expand Down Expand Up @@ -54,4 +55,71 @@ public static void setGamemodeParams(Game game){
game.setRoundDuration(30);
}
}

public static List<QuestionCategoryDto> getQuestionCategories(String lang) {
if(lang == null)
lang = "en";
if(lang.equals("en"))
return getQuestionCategoriesEn();
return getQuestionCategoriesEs();
}
private static List<QuestionCategoryDto> getQuestionCategoriesEn(){
return List.of(
QuestionCategoryDto.builder()
.name("Art")
.description("Are you an art expert? Prove it!")
.internalRepresentation(QuestionCategory.ART)
.build(),
QuestionCategoryDto.builder()
.name("Music")
.description("Are you a music lover? Prove it!")
.internalRepresentation(QuestionCategory.MUSIC)
.build(),
QuestionCategoryDto.builder()
.name("Geography")
.description("Are you a geography expert? Prove it!")
.internalRepresentation(QuestionCategory.GEOGRAPHY)
.build(),
QuestionCategoryDto.builder()
.name("Sports")
.description("Are you a sports fanatic? Prove it!")
.internalRepresentation(QuestionCategory.SPORTS)
.build(),
QuestionCategoryDto.builder()
.name("Video Games")
.description("Are you a gamer? Prove it!")
.internalRepresentation(QuestionCategory.VIDEOGAMES)
.build()
);
}

private static List<QuestionCategoryDto> getQuestionCategoriesEs(){
return List.of(
QuestionCategoryDto.builder()
.name("Arte")
.description("¿Eres un experto en arte? ¡Demuéstralo!")
.internalRepresentation(QuestionCategory.ART)
.build(),
QuestionCategoryDto.builder()
.name("Música")
.description("¿Eres un melómano? ¡Demuéstralo!")
.internalRepresentation(QuestionCategory.MUSIC)
.build(),
QuestionCategoryDto.builder()
.name("Geografía")
.description("¿Eres un experto en geografía? ¡Demuéstralo!")
.internalRepresentation(QuestionCategory.GEOGRAPHY)
.build(),
QuestionCategoryDto.builder()
.name("Deportes")
.description("¿Eres un fanático de los deportes? ¡Demuéstralo!")
.internalRepresentation(QuestionCategory.SPORTS)
.build(),
QuestionCategoryDto.builder()
.name("Videojuegos")
.description("¿Eres un gamer? ¡Demuéstralo!")
.internalRepresentation(QuestionCategory.VIDEOGAMES)
.build()
);
}
}
5 changes: 3 additions & 2 deletions api/src/main/java/lab/en2b/quizapi/game/GameController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.validation.Valid;
import lab.en2b.quizapi.game.dtos.*;
import lab.en2b.quizapi.questions.question.QuestionCategory;
import lab.en2b.quizapi.questions.question.dtos.QuestionCategoryDto;
import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -133,8 +134,8 @@ public ResponseEntity<List<GameModeDto>> getQuestionGameModes(){
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content)
})
@GetMapping("/question-categories")
public ResponseEntity<List<QuestionCategory>> getQuestionCategories(){
return ResponseEntity.ok(gameService.getQuestionCategories());
public ResponseEntity<List<QuestionCategoryDto>> getQuestionCategories(@RequestParam(required = false) String lang){
return ResponseEntity.ok(gameService.getQuestionCategories(lang));
}

}
3 changes: 2 additions & 1 deletion api/src/main/java/lab/en2b/quizapi/game/GameRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public interface GameRepository extends JpaRepository<Game,Long> {
@Query(value = "SELECT * FROM Games g WHERE user_id = ?1 AND g.is_game_over = false LIMIT 1", nativeQuery = true)
Optional<Game> findActiveGameForUser(Long userId);

@Query(value = "COUNT(*) FROM Games g WHERE user_id = ?1 AND g.is_game_over = true", nativeQuery = true)
@Query(value = "SELECT COUNT(*) FROM Games g WHERE user_id = ?1 AND g" +
".is_game_over = true", nativeQuery = true)
Long countFinishedGamesForUser(Long userId);
}
6 changes: 4 additions & 2 deletions api/src/main/java/lab/en2b/quizapi/game/GameService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package lab.en2b.quizapi.game;

import lab.en2b.quizapi.commons.user.UserService;
import lab.en2b.quizapi.commons.utils.GameModeUtils;
import lab.en2b.quizapi.game.dtos.*;
import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper;
import lab.en2b.quizapi.questions.question.QuestionCategory;
import lab.en2b.quizapi.questions.question.QuestionService;
import lab.en2b.quizapi.questions.question.dtos.QuestionCategoryDto;
import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto;
import lab.en2b.quizapi.questions.question.mappers.QuestionResponseDtoMapper;
import lab.en2b.quizapi.statistics.Statistics;
Expand Down Expand Up @@ -151,8 +153,8 @@ public GameResponseDto getGameDetails(Long id, Authentication authentication) {
return gameResponseDtoMapper.apply(game);
}

public List<QuestionCategory> getQuestionCategories() {
return Arrays.asList(QuestionCategory.values());
public List<QuestionCategoryDto> getQuestionCategories(String lang) {
return GameModeUtils.getQuestionCategories(lang);
}

private boolean wasGameMeantToBeOver(Game game) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lab.en2b.quizapi.questions.question;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
Expand All @@ -12,6 +13,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/questions")
@RequiredArgsConstructor
Expand All @@ -21,7 +24,6 @@ public class QuestionController {
@Operation(summary = "Sends an answer", description = "Sends the answer dto for a given question id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "404", description = "Not found - There is not a question with that id", content = @io.swagger.v3.oas.annotations.media.Content)
})
@PostMapping("/{questionId}/answer")
Expand All @@ -32,22 +34,32 @@ private ResponseEntity<AnswerCheckResponseDto> answerQuestion(@PathVariable @Pos
@Operation(summary = "Gets a random question", description = "Gets a random question in the language")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "404", description = "Language does not exist or it is misspelled", content = @io.swagger.v3.oas.annotations.media.Content)
})
@GetMapping("/new")
@GetMapping("/random")
private ResponseEntity<QuestionResponseDto> generateQuestion(@RequestParam(required = false) String lang){
return ResponseEntity.ok(questionService.getRandomQuestion(lang));
}

@Operation(summary = "Gets a question", description = "Gets a question given a question id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "404", description = "Not found - There is not a question with that id", content = @io.swagger.v3.oas.annotations.media.Content)
})
@GetMapping("/{id}")
private ResponseEntity<QuestionResponseDto> getQuestionById(@PathVariable @PositiveOrZero Long id){
return ResponseEntity.ok(questionService.getQuestionById(id));
}

@Operation(summary = "Gets a list of questions", description = "Gets a list of questions given a page number")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "404", description = "Not found - There are no questions", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "400", description = "Bad request - The page number is invalid", content = @io.swagger.v3.oas.annotations.media.Content)
})
@Parameter(name = "page", description = "The page number. Cannot be lower or equal to 0.", required = true)
@GetMapping
private ResponseEntity<List<QuestionResponseDto>> getQuestions(@RequestParam Long page){
return ResponseEntity.ok(questionService.getQuestionsWithPage(page));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,33 @@ public QuestionResponseDto getQuestionById(Long id) {
return questionResponseDtoMapper.apply(q);
}

/**
* Get a list of questions with a page
* @param page The page number
* @return the list of questions
*/
public List<QuestionResponseDto> getQuestionsWithPage(Long page){
if (page < 1)
throw new IllegalArgumentException("Invalid page number");
List<QuestionResponseDto> result = questionRepository.findAll().stream()
.map(questionResponseDtoMapper).toList();
return getPage(result, page);
}

private List<QuestionResponseDto> getPage(List<QuestionResponseDto> result, Long page) {
try{
int QUESTION_PAGE_SIZE = 100;
int startIndex = Math.toIntExact((page-1)* QUESTION_PAGE_SIZE);
if(startIndex > result.size())
throw new IllegalArgumentException("Invalid page number, maximum page is "+(result.size()/ QUESTION_PAGE_SIZE +1) + " and you requested page "+page);
if (result.size() < page* QUESTION_PAGE_SIZE)
return result.subList(startIndex,result.size());
return result.subList(startIndex, Math.toIntExact(page* QUESTION_PAGE_SIZE));
} catch (ArithmeticException e) {
throw new IllegalArgumentException("Invalid page number");
}

}

/**
* Load the answers for a question (The distractors and the correct one)
Expand Down
Loading

0 comments on commit 67bdfc8

Please sign in to comment.