Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge changes in backend #253

Merged
merged 37 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
81b42a0
feat: getUsers controller
sergioqfeg1 Apr 16, 2024
932a4eb
feat: getUsers service
sergioqfeg1 Apr 16, 2024
43b1777
feat: getUser controller
sergioqfeg1 Apr 16, 2024
6074008
feat: getUser service
sergioqfeg1 Apr 16, 2024
e58f66e
chore: swagger documentation
sergioqfeg1 Apr 16, 2024
d5ea74c
chore: swagger documentation
sergioqfeg1 Apr 16, 2024
7881f4c
Merge remote-tracking branch 'origin/feat/userEndpoints' into feat/us…
sergioqfeg1 Apr 16, 2024
7b1b285
fix: names of tests
sergioqfeg1 Apr 17, 2024
762b4ed
fix: swagger documentation
sergioqfeg1 Apr 17, 2024
0ddf27c
test: user controller
sergioqfeg1 Apr 17, 2024
d8c6099
fix: test names
sergioqfeg1 Apr 17, 2024
b0fe1e9
test: getUsers service tests
sergioqfeg1 Apr 17, 2024
874265f
test: getUser service tests
sergioqfeg1 Apr 17, 2024
856dd44
Merge pull request #245 from Arquisoft/feat/userEndpoints
Toto-hitori Apr 17, 2024
1cb4cdd
fix: user endpoints now are open
Toto-hitori Apr 17, 2024
1ee8ad9
fix: question endpoints now are open
Toto-hitori Apr 17, 2024
efa1d3a
Merge pull request #246 from Arquisoft/feat/open-endpoints
sergioqfeg1 Apr 17, 2024
5e0f61e
feat: getQuestions controller
sergioqfeg1 Apr 17, 2024
68cccf8
feat: getQuestions service
sergioqfeg1 Apr 17, 2024
5029cfa
test: controller tests (200)
sergioqfeg1 Apr 17, 2024
5f0155b
test: controller tests (400)
sergioqfeg1 Apr 17, 2024
d598719
fix: getQuestions method needs to take into account other sizes
sergioqfeg1 Apr 17, 2024
7f06937
test: getQuestionsWithPage
sergioqfeg1 Apr 17, 2024
40f7e01
test: getQuestionsWithInvalidPage + getQuestionsWithNoQuestions
sergioqfeg1 Apr 17, 2024
8f840df
fix: pattern breaking when running app
Toto-hitori Apr 17, 2024
a4e67d4
fix: now checking if initial page is greater than size
Toto-hitori Apr 17, 2024
e78504d
fix:check for integer overflow
Toto-hitori Apr 17, 2024
c81b41d
fix: missing SELECT from query
jjgancfer Apr 17, 2024
f9e97f1
Merge pull request #247 from Arquisoft/feat/questionsEndpoints
UO283615 Apr 17, 2024
304e990
feat: question cat has a proper dto
Toto-hitori Apr 18, 2024
c896537
Merge pull request #251 from Arquisoft/feat/question-cat-lang
UO283615 Apr 18, 2024
c1b4924
Merge pull request #249 from Arquisoft/fix/api/missing-select
UO283615 Apr 18, 2024
4465a5f
feat: graphana and docker now use own docker image
Toto-hitori Apr 18, 2024
921e165
feat: dockerfiles for grafana and prometheus
Toto-hitori Apr 18, 2024
f4cd1e9
feat: grafana in release.yml
Toto-hitori Apr 18, 2024
2be4075
fix: port changed to 8443
Toto-hitori Apr 18, 2024
637ea18
Merge pull request #252 from Arquisoft/monitoring/graphana-prometheus…
UO283615 Apr 18, 2024
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
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