Skip to content

Commit a953335

Browse files
committed
feat(question): add DTOs, Redis cache, and new endpoints
- Added DTOs for Question and Answer entities - Removed @JsonIgnore annotation on Question and replaced with DTO usage - Implemented endpoints: - GET /questions/random – get random question - GET /questions/cached – get cached question from Redis - GET /questions/search?keyword= – get questions by title keyword - DELETE /questions?keyword= – delete questions by title keyword - GET /questions/{id}/answers – get answers by question ID
1 parent 15e8161 commit a953335

File tree

12 files changed

+248
-54
lines changed

12 files changed

+248
-54
lines changed

pom.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,24 @@
4747
<artifactId>spring-boot-starter-test</artifactId>
4848
<scope>test</scope>
4949
</dependency>
50+
<dependency>
51+
<groupId>org.projectlombok</groupId>
52+
<artifactId>lombok</artifactId>
53+
<version>1.18.34</version>
54+
<scope>provided</scope>
55+
</dependency>
56+
<dependency>
57+
<groupId>redis.clients</groupId>
58+
<artifactId>jedis</artifactId>
59+
<version>5.1.0</version>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.apache.commons</groupId>
63+
<artifactId>commons-pool2</artifactId>
64+
<version>2.12.0</version>
65+
</dependency>
66+
67+
5068
</dependencies>
5169

5270
<build>
@@ -55,6 +73,14 @@
5573
<groupId>org.springframework.boot</groupId>
5674
<artifactId>spring-boot-maven-plugin</artifactId>
5775
</plugin>
76+
<plugin>
77+
<groupId>org.apache.maven.plugins</groupId>
78+
<artifactId>maven-compiler-plugin</artifactId>
79+
<configuration>
80+
<source>13</source>
81+
<target>13</target>
82+
</configuration>
83+
</plugin>
5884
</plugins>
5985
</build>
6086

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.example.postgresdemo.DTO;
2+
3+
import com.example.postgresdemo.model.Question;
4+
import com.fasterxml.jackson.annotation.JsonIgnore;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
import org.hibernate.annotations.OnDelete;
9+
import org.hibernate.annotations.OnDeleteAction;
10+
11+
import javax.persistence.Column;
12+
import javax.persistence.FetchType;
13+
import javax.persistence.JoinColumn;
14+
import javax.persistence.ManyToOne;
15+
import javax.validation.constraints.NotBlank;
16+
import javax.validation.constraints.Size;
17+
18+
@Data
19+
@NoArgsConstructor
20+
@AllArgsConstructor
21+
public class DtoAnswer {
22+
private String text;
23+
private String title;
24+
private String description;
25+
26+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.example.postgresdemo.DTO;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
import javax.validation.constraints.NotBlank;
8+
import javax.validation.constraints.Size;
9+
10+
@Data
11+
@NoArgsConstructor
12+
@AllArgsConstructor
13+
public class DtoQuestion {
14+
private String title;
15+
private String description;
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.example.postgresdemo.Service;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import redis.clients.jedis.JedisPool;
6+
import redis.clients.jedis.JedisPoolConfig;
7+
8+
@Configuration
9+
public class RedisConfig {
10+
11+
@Bean
12+
public JedisPool jedisPool() {
13+
JedisPoolConfig config = new JedisPoolConfig();
14+
config.setMaxTotal(8);
15+
config.setJmxEnabled(false); // <-- Это отключает регистрацию MBean
16+
return new JedisPool(config, "localhost", 6379);
17+
}
18+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.example.postgresdemo.Service;
2+
3+
import com.example.postgresdemo.DTO.DtoQuestion;
4+
import com.example.postgresdemo.model.Question;
5+
import com.example.postgresdemo.repository.QuestionRepository;
6+
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.databind.JsonMappingException;
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.stereotype.Service;
11+
import redis.clients.jedis.Jedis;
12+
import redis.clients.jedis.JedisPool;
13+
14+
import java.util.Random;
15+
16+
@Service
17+
public class ServiceQuestion {
18+
19+
private final QuestionRepository questionRepository;
20+
21+
@Autowired
22+
private JedisPool jedisPool;
23+
ObjectMapper mapper = new ObjectMapper();
24+
25+
private static final int TTL = 3600;
26+
27+
@Autowired
28+
public ServiceQuestion(QuestionRepository questionRepository) {
29+
this.questionRepository = questionRepository;
30+
}
31+
32+
public DtoQuestion randomQuestion() {
33+
long count = questionRepository.count();
34+
if (count == 0){
35+
return null;
36+
}
37+
long min = 1200;
38+
long max = 1251;
39+
40+
long random = min + (long) (Math.random() * ((max - min) + 1));
41+
return getCachedQuestion(random);
42+
}
43+
44+
public DtoQuestion getQuestion(Long id) {
45+
return questionRepository.findById(id).
46+
map(qst -> new DtoQuestion(qst.getTitle(),
47+
qst.getDescription())).orElse(null);
48+
}
49+
50+
public DtoQuestion getCachedQuestion(Long id) {
51+
try(Jedis jedis = jedisPool.getResource()) {
52+
53+
String key = String.format("article:%d",id);
54+
String raw = jedis.get(key);
55+
if(raw != null) {
56+
return mapper.readValue(raw,DtoQuestion.class);
57+
}
58+
var article = getQuestion(id);
59+
if(article == null) {
60+
return null;
61+
}
62+
63+
jedis.setex(key,TTL , mapper.writeValueAsString(article));
64+
return article;
65+
} catch (JsonMappingException e) {
66+
throw new RuntimeException(e);
67+
} catch (JsonProcessingException e) {
68+
throw new RuntimeException(e);
69+
}
70+
}
71+
}

src/main/java/com/example/postgresdemo/controller/AnswerController.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.postgresdemo.controller;
22

3+
import com.example.postgresdemo.DTO.DtoAnswer;
34
import com.example.postgresdemo.exception.ResourceNotFoundException;
45
import com.example.postgresdemo.model.Answer;
56
import com.example.postgresdemo.repository.AnswerRepository;
@@ -8,7 +9,9 @@
89
import org.springframework.http.ResponseEntity;
910
import org.springframework.web.bind.annotation.*;
1011
import javax.validation.Valid;
12+
import java.util.ArrayList;
1113
import java.util.List;
14+
import java.util.stream.Collectors;
1215

1316
@RestController
1417
public class AnswerController {
@@ -20,8 +23,15 @@ public class AnswerController {
2023
private QuestionRepository questionRepository;
2124

2225
@GetMapping("/questions/{questionId}/answers")
23-
public List<Answer> getAnswersByQuestionId(@PathVariable Long questionId) {
24-
return answerRepository.findByQuestionId(questionId);
26+
public List<DtoAnswer> getAnswersByQuestionId(@PathVariable Long questionId) {
27+
List<Answer> answer = answerRepository.findByQuestionId(questionId);
28+
29+
List<DtoAnswer> dtoAnswers = answer.stream().
30+
map(ans -> new DtoAnswer(ans.getText(),
31+
ans.getQuestion().getDescription(),
32+
ans.getQuestion().getDescription())).
33+
collect(Collectors.toList());
34+
return dtoAnswers;
2535
}
2636

2737
@PostMapping("/questions/{questionId}/answers")
@@ -63,4 +73,6 @@ public ResponseEntity<?> deleteAnswer(@PathVariable Long questionId,
6373
}).orElseThrow(() -> new ResourceNotFoundException("Answer not found with id " + answerId));
6474

6575
}
76+
77+
6678
}

src/main/java/com/example/postgresdemo/controller/QuestionController.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
package com.example.postgresdemo.controller;
22

3+
import com.example.postgresdemo.DTO.DtoQuestion;
4+
import com.example.postgresdemo.Service.ServiceQuestion;
35
import com.example.postgresdemo.exception.ResourceNotFoundException;
6+
import com.example.postgresdemo.model.Answer;
47
import com.example.postgresdemo.model.Question;
8+
import com.example.postgresdemo.repository.AnswerRepository;
59
import com.example.postgresdemo.repository.QuestionRepository;
610
import org.springframework.beans.factory.annotation.Autowired;
711
import org.springframework.data.domain.Page;
812
import org.springframework.data.domain.Pageable;
913
import org.springframework.http.ResponseEntity;
1014
import org.springframework.web.bind.annotation.*;
1115
import javax.validation.Valid;
16+
import java.util.List;
1217

1318
@RestController
1419
public class QuestionController {
1520

1621
@Autowired
1722
private QuestionRepository questionRepository;
1823

24+
@Autowired
25+
private AnswerRepository answerRepository;
26+
27+
@Autowired
28+
private ServiceQuestion serviceQuestion;
29+
1930
@GetMapping("/questions")
2031
public Page<Question> getQuestions(Pageable pageable) {
2132
return questionRepository.findAll(pageable);
@@ -47,4 +58,41 @@ public ResponseEntity<?> deleteQuestion(@PathVariable Long questionId) {
4758
return ResponseEntity.ok().build();
4859
}).orElseThrow(() -> new ResourceNotFoundException("Question not found with id " + questionId));
4960
}
61+
62+
@GetMapping("/getAllAnswer/{questionId}")
63+
public List<Answer> getAllAnswersOnQuestion(
64+
@PathVariable Long questionId) {
65+
return answerRepository.findAllByQuestionId(questionId);
66+
}
67+
68+
@GetMapping("/getQuestionByTitle")
69+
public List<Question> getQuestionByTitle(
70+
@RequestParam String keyword) {
71+
return questionRepository.findByTitleContainingIgnoreCase(keyword);
72+
}
73+
74+
@DeleteMapping("/deleteQuestionByTitle")
75+
public ResponseEntity<?> deleteQuestionByTitle(
76+
@RequestParam String keyword) {
77+
return questionRepository.findByTitleContainingIgnoreCase(keyword)
78+
.stream()
79+
.peek(questionRepository::delete)
80+
.findAny()
81+
.map(question -> ResponseEntity.ok().build())
82+
.orElseThrow(() ->
83+
new ResourceNotFoundException
84+
("Question not found with key Word " + keyword));
85+
}
86+
87+
@GetMapping("/randomQuestion")
88+
public DtoQuestion getRandomQuestion() {
89+
return serviceQuestion.randomQuestion();
90+
}
91+
92+
93+
@GetMapping("/questionCached/{id}")
94+
public DtoQuestion getCachedQuestion(@PathVariable Long id) {
95+
return serviceQuestion.getCachedQuestion(id);
96+
}
97+
5098
}
Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.example.postgresdemo.model;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import lombok.Data;
45
import org.hibernate.annotations.OnDelete;
56
import org.hibernate.annotations.OnDeleteAction;
67

78
import javax.persistence.*;
9+
import javax.validation.constraints.NotBlank;
10+
import javax.validation.constraints.Size;
811

912
@Entity
1013
@Table(name = "answers")
14+
@Data
1115
public class Answer extends AuditModel {
1216
@Id
1317
@GeneratedValue(generator = "answer_generator")
@@ -19,35 +23,13 @@ public class Answer extends AuditModel {
1923
private Long id;
2024

2125
@Column(columnDefinition = "text")
26+
@NotBlank(message = "Description is mandatory")
27+
@Size(min = 10, max = 500, message = "Description must be between 10 and 500 characters")
2228
private String text;
2329

2430
@ManyToOne(fetch = FetchType.LAZY, optional = false)
2531
@JoinColumn(name = "question_id", nullable = false)
2632
@OnDelete(action = OnDeleteAction.CASCADE)
27-
@JsonIgnore
2833
private Question question;
2934

30-
public Long getId() {
31-
return id;
32-
}
33-
34-
public void setId(Long id) {
35-
this.id = id;
36-
}
37-
38-
public String getText() {
39-
return text;
40-
}
41-
42-
public void setText(String text) {
43-
this.text = text;
44-
}
45-
46-
public Question getQuestion() {
47-
return question;
48-
}
49-
50-
public void setQuestion(Question question) {
51-
this.question = question;
52-
}
5335
}

0 commit comments

Comments
 (0)