-
Notifications
You must be signed in to change notification settings - Fork 303
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Communication: Add FAQs to Artemis (#9325)
- Loading branch information
Showing
55 changed files
with
2,507 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
src/main/java/de/tum/cit/aet/artemis/communication/domain/Faq.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package de.tum.cit.aet.artemis.communication.domain; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
import jakarta.persistence.CollectionTable; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.ElementCollection; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.persistence.Table; | ||
|
||
import org.hibernate.annotations.Cache; | ||
import org.hibernate.annotations.CacheConcurrencyStrategy; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
||
import de.tum.cit.aet.artemis.core.domain.AbstractAuditingEntity; | ||
import de.tum.cit.aet.artemis.core.domain.Course; | ||
|
||
/** | ||
* A FAQ. | ||
*/ | ||
@Entity | ||
@Table(name = "faq") | ||
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) | ||
@JsonInclude(JsonInclude.Include.NON_EMPTY) | ||
public class Faq extends AbstractAuditingEntity { | ||
|
||
@Column(name = "question_title") | ||
private String questionTitle; | ||
|
||
@Column(name = "question_answer") | ||
private String questionAnswer; | ||
|
||
@ElementCollection(fetch = FetchType.EAGER) | ||
@CollectionTable(name = "faq_category", joinColumns = @JoinColumn(name = "faq_id")) | ||
@Column(name = "category") | ||
private Set<String> categories = new HashSet<>(); | ||
|
||
@Enumerated(EnumType.STRING) | ||
@Column(name = "faq_state") | ||
private FaqState faqState; | ||
|
||
@ManyToOne | ||
@JsonIgnoreProperties(value = { "faqs" }, allowSetters = true) | ||
private Course course; | ||
|
||
public String getQuestionTitle() { | ||
return questionTitle; | ||
} | ||
|
||
public void setQuestionTitle(String questionTitle) { | ||
this.questionTitle = questionTitle; | ||
} | ||
|
||
public String getQuestionAnswer() { | ||
return questionAnswer; | ||
} | ||
|
||
public void setQuestionAnswer(String questionAnswer) { | ||
this.questionAnswer = questionAnswer; | ||
} | ||
|
||
public Course getCourse() { | ||
return course; | ||
} | ||
|
||
public void setCourse(Course course) { | ||
this.course = course; | ||
} | ||
|
||
public Set<String> getCategories() { | ||
return categories; | ||
} | ||
|
||
public void setCategories(Set<String> categories) { | ||
this.categories = categories; | ||
} | ||
|
||
public FaqState getFaqState() { | ||
return faqState; | ||
} | ||
|
||
public void setFaqState(FaqState faqState) { | ||
this.faqState = faqState; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "Faq{" + "id=" + getId() + ", questionTitle='" + getQuestionTitle() + "'" + ", questionAnswer='" + getQuestionAnswer() + "'" + ", faqState='" + getFaqState() + "}"; | ||
} | ||
|
||
} |
5 changes: 5 additions & 0 deletions
5
src/main/java/de/tum/cit/aet/artemis/communication/domain/FaqState.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package de.tum.cit.aet.artemis.communication.domain; | ||
|
||
public enum FaqState { | ||
ACCEPTED, REJECTED, PROPOSED | ||
} |
17 changes: 17 additions & 0 deletions
17
src/main/java/de/tum/cit/aet/artemis/communication/dto/FaqDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package de.tum.cit.aet.artemis.communication.dto; | ||
|
||
import java.util.Set; | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
||
import de.tum.cit.aet.artemis.communication.domain.Faq; | ||
import de.tum.cit.aet.artemis.communication.domain.FaqState; | ||
|
||
@JsonInclude(JsonInclude.Include.NON_EMPTY) | ||
public record FaqDTO(Long id, String questionTitle, String questionAnswer, Set<String> categories, FaqState faqState) { | ||
|
||
public FaqDTO(Faq faq) { | ||
this(faq.getId(), faq.getQuestionTitle(), faq.getQuestionAnswer(), faq.getCategories(), faq.getFaqState()); | ||
} | ||
|
||
} |
37 changes: 37 additions & 0 deletions
37
src/main/java/de/tum/cit/aet/artemis/communication/repository/FaqRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package de.tum.cit.aet.artemis.communication.repository; | ||
|
||
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; | ||
|
||
import java.util.Set; | ||
|
||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.data.jpa.repository.Modifying; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.query.Param; | ||
import org.springframework.stereotype.Repository; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import de.tum.cit.aet.artemis.communication.domain.Faq; | ||
import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; | ||
|
||
/** | ||
* Spring Data repository for the Faq entity. | ||
*/ | ||
@Profile(PROFILE_CORE) | ||
@Repository | ||
public interface FaqRepository extends ArtemisJpaRepository<Faq, Long> { | ||
|
||
Set<Faq> findAllByCourseId(Long courseId); | ||
|
||
@Query(""" | ||
SELECT DISTINCT faq.categories | ||
FROM Faq faq | ||
WHERE faq.course.id = :courseId | ||
""") | ||
Set<String> findAllCategoriesByCourseId(@Param("courseId") Long courseId); | ||
|
||
@Transactional | ||
@Modifying | ||
void deleteAllByCourseId(Long courseId); | ||
|
||
} |
194 changes: 194 additions & 0 deletions
194
src/main/java/de/tum/cit/aet/artemis/communication/web/FaqResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
package de.tum.cit.aet.artemis.communication.web; | ||
|
||
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.PutMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import de.tum.cit.aet.artemis.communication.domain.Faq; | ||
import de.tum.cit.aet.artemis.communication.dto.FaqDTO; | ||
import de.tum.cit.aet.artemis.communication.repository.FaqRepository; | ||
import de.tum.cit.aet.artemis.core.domain.Course; | ||
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; | ||
import de.tum.cit.aet.artemis.core.repository.CourseRepository; | ||
import de.tum.cit.aet.artemis.core.security.Role; | ||
import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastInstructor; | ||
import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent; | ||
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; | ||
import de.tum.cit.aet.artemis.core.util.HeaderUtil; | ||
|
||
/** | ||
* REST controller for managing Faqs. | ||
*/ | ||
@Profile(PROFILE_CORE) | ||
@RestController | ||
@RequestMapping("api/") | ||
public class FaqResource { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(FaqResource.class); | ||
|
||
private static final String ENTITY_NAME = "faq"; | ||
|
||
@Value("${jhipster.clientApp.name}") | ||
private String applicationName; | ||
|
||
private final CourseRepository courseRepository; | ||
|
||
private final AuthorizationCheckService authCheckService; | ||
|
||
private final FaqRepository faqRepository; | ||
|
||
public FaqResource(CourseRepository courseRepository, AuthorizationCheckService authCheckService, FaqRepository faqRepository) { | ||
|
||
this.courseRepository = courseRepository; | ||
this.authCheckService = authCheckService; | ||
this.faqRepository = faqRepository; | ||
} | ||
|
||
/** | ||
* POST /courses/:courseId/faqs : Create a new faq. | ||
* | ||
* @param faq the faq to create * | ||
* @param courseId the id of the course the faq belongs to | ||
* @return the ResponseEntity with status 201 (Created) and with body the new faq, or with status 400 (Bad Request) | ||
* if the faq has already an ID or if the faq course id does not match with the path variable | ||
* @throws URISyntaxException if the Location URI syntax is incorrect | ||
*/ | ||
@PostMapping("courses/{courseId}/faqs") | ||
@EnforceAtLeastInstructor | ||
public ResponseEntity<FaqDTO> createFaq(@RequestBody Faq faq, @PathVariable Long courseId) throws URISyntaxException { | ||
log.debug("REST request to save Faq : {}", faq); | ||
if (faq.getId() != null) { | ||
throw new BadRequestAlertException("A new faq cannot already have an ID", ENTITY_NAME, "idExists"); | ||
} | ||
|
||
if (faq.getCourse() == null || !faq.getCourse().getId().equals(courseId)) { | ||
throw new BadRequestAlertException("Course ID in path and FAQ do not match", ENTITY_NAME, "courseIdMismatch"); | ||
} | ||
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, faq.getCourse(), null); | ||
|
||
Faq savedFaq = faqRepository.save(faq); | ||
FaqDTO dto = new FaqDTO(savedFaq); | ||
return ResponseEntity.created(new URI("/api/courses/" + courseId + "/faqs/" + savedFaq.getId())).body(dto); | ||
} | ||
|
||
/** | ||
* PUT /courses/:courseId/faqs/:faqId : Updates an existing faq. | ||
* | ||
* @param faq the faq to update | ||
* @param faqId id of the faq to be updated * | ||
* @param courseId the id of the course the faq belongs to | ||
* @return the ResponseEntity with status 200 (OK) and with body the updated faq, or with status 400 (Bad Request) | ||
* if the faq is not valid or if the faq course id does not match with the path variable | ||
*/ | ||
@PutMapping("courses/{courseId}/faqs/{faqId}") | ||
@EnforceAtLeastInstructor | ||
public ResponseEntity<FaqDTO> updateFaq(@RequestBody Faq faq, @PathVariable Long faqId, @PathVariable Long courseId) { | ||
log.debug("REST request to update Faq : {}", faq); | ||
if (faqId == null || !faqId.equals(faq.getId())) { | ||
throw new BadRequestAlertException("Id of FAQ and path must match", ENTITY_NAME, "idNull"); | ||
} | ||
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, faq.getCourse(), null); | ||
Faq existingFaq = faqRepository.findByIdElseThrow(faqId); | ||
if (!Objects.equals(existingFaq.getCourse().getId(), courseId)) { | ||
throw new BadRequestAlertException("Course ID of the FAQ provided courseID must match", ENTITY_NAME, "idNull"); | ||
} | ||
Faq updatedFaq = faqRepository.save(faq); | ||
FaqDTO dto = new FaqDTO(updatedFaq); | ||
return ResponseEntity.ok().body(dto); | ||
} | ||
|
||
/** | ||
* GET /courses/:courseId/faqs/:faqId : get the faq with the id faqId. | ||
* | ||
* @param faqId the faqId of the faq to retrieve * | ||
* @param courseId the id of the course the faq belongs to | ||
* @return the ResponseEntity with status 200 (OK) and with body the faq, or with status 404 (Not Found) | ||
*/ | ||
@GetMapping("courses/{courseId}/faqs/{faqId}") | ||
@EnforceAtLeastStudent | ||
public ResponseEntity<FaqDTO> getFaq(@PathVariable Long faqId, @PathVariable Long courseId) { | ||
log.debug("REST request to get faq {}", faqId); | ||
Faq faq = faqRepository.findByIdElseThrow(faqId); | ||
if (faq.getCourse() == null || !faq.getCourse().getId().equals(courseId)) { | ||
throw new BadRequestAlertException("Course ID in path and FAQ do not match", ENTITY_NAME, "courseIdMismatch"); | ||
} | ||
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, faq.getCourse(), null); | ||
FaqDTO dto = new FaqDTO(faq); | ||
return ResponseEntity.ok(dto); | ||
} | ||
|
||
/** | ||
* DELETE /courses/:courseId/faqs/:faqId : delete the "id" faq. | ||
* | ||
* @param faqId the id of the faq to delete | ||
* @param courseId the id of the course the faq belongs to | ||
* @return the ResponseEntity with status 200 (OK) | ||
*/ | ||
@DeleteMapping("courses/{courseId}/faqs/{faqId}") | ||
@EnforceAtLeastInstructor | ||
public ResponseEntity<Void> deleteFaq(@PathVariable Long faqId, @PathVariable Long courseId) { | ||
|
||
log.debug("REST request to delete faq {}", faqId); | ||
Faq existingFaq = faqRepository.findByIdElseThrow(faqId); | ||
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, existingFaq.getCourse(), null); | ||
if (!Objects.equals(existingFaq.getCourse().getId(), courseId)) { | ||
throw new BadRequestAlertException("Course ID of the FAQ provided courseID must match", ENTITY_NAME, "idNull"); | ||
} | ||
faqRepository.deleteById(faqId); | ||
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, faqId.toString())).build(); | ||
} | ||
|
||
/** | ||
* GET /courses/:courseId/faqs : get all the faqs of a course | ||
* | ||
* @param courseId the courseId of the course for which all faqs should be returned | ||
* @return the ResponseEntity with status 200 (OK) and the list of faqs in body | ||
*/ | ||
@GetMapping("courses/{courseId}/faqs") | ||
@EnforceAtLeastStudent | ||
public ResponseEntity<Set<FaqDTO>> getFaqForCourse(@PathVariable Long courseId) { | ||
log.debug("REST request to get all Faqs for the course with id : {}", courseId); | ||
|
||
Course course = courseRepository.findByIdElseThrow(courseId); | ||
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, null); | ||
Set<Faq> faqs = faqRepository.findAllByCourseId(courseId); | ||
Set<FaqDTO> faqDTOS = faqs.stream().map(FaqDTO::new).collect(Collectors.toSet()); | ||
return ResponseEntity.ok().body(faqDTOS); | ||
} | ||
|
||
/** | ||
* GET /courses/:courseId/faq-categories : get all the faq categories of a course | ||
* | ||
* @param courseId the courseId of the course for which all faq categories should be returned | ||
* @return the ResponseEntity with status 200 (OK) and the list of faqs in body | ||
*/ | ||
@GetMapping("courses/{courseId}/faq-categories") | ||
@EnforceAtLeastStudent | ||
public ResponseEntity<Set<String>> getFaqCategoriesForCourse(@PathVariable Long courseId) { | ||
log.debug("REST request to get all Faq Categories for the course with id : {}", courseId); | ||
|
||
Course course = courseRepository.findByIdElseThrow(courseId); | ||
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, null); | ||
Set<String> faqs = faqRepository.findAllCategoriesByCourseId(courseId); | ||
|
||
return ResponseEntity.ok().body(faqs); | ||
} | ||
} |
Oops, something went wrong.