Skip to content

Commit

Permalink
Communication: Accept messaging code of conduct (#7154)
Browse files Browse the repository at this point in the history
  • Loading branch information
nityanandaz authored Oct 14, 2023
1 parent 3900fea commit 19a81aa
Show file tree
Hide file tree
Showing 43 changed files with 923 additions and 39 deletions.
65 changes: 65 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/domain/ConductAgreement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package de.tum.in.www1.artemis.domain;

import java.util.Objects;

import javax.persistence.*;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* A user's agreement of a course's code of conduct.
*/
@Entity
@Table(name = "conduct_agreement")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@IdClass(ConductAgreementId.class)
public class ConductAgreement {

@Id
@ManyToOne
@JoinColumn(name = "course_id")
private Course course;

@Id
@ManyToOne
@JoinColumn(name = "user_id")
private User user;

public Course getCourse() {
return course;
}

public void setCourse(Course course) {
this.course = course;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConductAgreement that = (ConductAgreement) o;
return course.equals(that.course) && user.equals(that.user);
}

@Override
public int hashCode() {
return Objects.hash(course, user);
}

@Override
public String toString() {
return "ConductAgreement{" + "course=" + course + ", user=" + user + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package de.tum.in.www1.artemis.domain;

import java.io.Serializable;
import java.util.Objects;

/**
* The primary key for ConductAgreement
*/
public class ConductAgreementId implements Serializable {

private Long course;

private Long user;

ConductAgreementId(Long course, Long user) {
this.course = course;
this.user = user;
}

ConductAgreementId() {
// Needed for JPA
}

public Long getCourse() {
return course;
}

public void setCourse(Long course) {
this.course = course;
}

public Long getUser() {
return user;
}

public void setUser(Long user) {
this.user = user;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConductAgreementId that = (ConductAgreementId) o;
return course.equals(that.course) && user.equals(that.user);
}

@Override
public int hashCode() {
return Objects.hash(course, user);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package de.tum.in.www1.artemis.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import de.tum.in.www1.artemis.domain.ConductAgreement;
import de.tum.in.www1.artemis.domain.ConductAgreementId;

/**
* Spring Data repository for the Code of Conduct Agreement entity.
*/
@Repository
public interface ConductAgreementRepository extends JpaRepository<ConductAgreement, ConductAgreementId> {

/**
* Find the user's agreement to a course's code of conduct.
*
* @param courseId the ID of the code of conduct's course
* @param userId the user's ID
* @return the user's agreement to the course's code of conduct
*/
Optional<ConductAgreement> findByCourseIdAndUserId(Long courseId, Long userId);

/**
* Delete all users' agreements to a course's code of conduct.
*
* @param courseId the ID of the code of conduct's course
*/
@Transactional // ok because of delete
@Modifying
void deleteByCourseId(Long courseId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package de.tum.in.www1.artemis.service;

import org.springframework.stereotype.Service;

import de.tum.in.www1.artemis.domain.ConductAgreement;
import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.repository.ConductAgreementRepository;

/**
* Service Implementation for managing a user's agreement to a course's code of conduct.
*/
@Service
public class ConductAgreementService {

private final ConductAgreementRepository conductAgreementRepository;

ConductAgreementService(ConductAgreementRepository conductAgreementRepository) {
this.conductAgreementRepository = conductAgreementRepository;
}

/**
* Fetches if a user agreed to a course's code of conduct.
*
* @param user the user in the course
* @param course the code of conduct's course
* @return if the user agreed to the course's code of conduct
*/
public boolean fetchUserAgreesToCodeOfConductInCourse(User user, Course course) {
var codeOfConduct = course.getCourseInformationSharingMessagingCodeOfConduct();
if (codeOfConduct == null || codeOfConduct.isEmpty()) {
return true;
}
return conductAgreementRepository.findByCourseIdAndUserId(course.getId(), user.getId()).isPresent();
}

/**
* A user agrees to a course's code of conduct.
*
* @param user the user in the course
* @param course the code of conduct's course
*/
public void setUserAgreesToCodeOfConductInCourse(User user, Course course) {
ConductAgreement conductAgreement = new ConductAgreement();
conductAgreement.setCourse(course);
conductAgreement.setUser(user);
conductAgreementRepository.save(conductAgreement);
}

/**
* Reset all agreements to a course's code of conduct.
*
* @param course the code of conduct's course
*/
public void resetUsersAgreeToCodeOfConductInCourse(Course course) {
conductAgreementRepository.deleteByCourseId(course.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.tum.in.www1.artemis.service.dto;

/**
* A DTO representing a course's responsible user, i.e., a person to report misconduct to.
*/
public record ResponsibleUserDTO(String name, String email) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ public class CourseResource {

private final GradingScaleRepository gradingScaleRepository;

private final ConductAgreementService conductAgreementService;

@Value("${artemis.course-archives-path}")
private String courseArchivesDirPath;

Expand All @@ -116,7 +118,8 @@ public CourseResource(UserRepository userRepository, CourseService courseService
TutorParticipationRepository tutorParticipationRepository, SubmissionService submissionService, Optional<VcsUserManagementService> optionalVcsUserManagementService,
AssessmentDashboardService assessmentDashboardService, ExerciseRepository exerciseRepository, Optional<CIUserManagementService> optionalCiUserManagementService,
FileService fileService, TutorialGroupsConfigurationService tutorialGroupsConfigurationService, GradingScaleService gradingScaleService,
CourseScoreCalculationService courseScoreCalculationService, GradingScaleRepository gradingScaleRepository, LearningPathService learningPathService) {
CourseScoreCalculationService courseScoreCalculationService, GradingScaleRepository gradingScaleRepository, LearningPathService learningPathService,
ConductAgreementService conductAgreementService) {
this.courseService = courseService;
this.courseRepository = courseRepository;
this.exerciseService = exerciseService;
Expand All @@ -136,6 +139,7 @@ public CourseResource(UserRepository userRepository, CourseService courseService
this.courseScoreCalculationService = courseScoreCalculationService;
this.gradingScaleRepository = gradingScaleRepository;
this.learningPathService = learningPathService;
this.conductAgreementService = conductAgreementService;
}

/**
Expand Down Expand Up @@ -234,6 +238,10 @@ public ResponseEntity<Course> updateCourse(@PathVariable Long courseId, @Request
}
}

if (!Objects.equals(courseUpdate.getCourseInformationSharingMessagingCodeOfConduct(), existingCourse.getCourseInformationSharingMessagingCodeOfConduct())) {
conductAgreementService.resetUsersAgreeToCodeOfConductInCourse(existingCourse);
}

courseUpdate.setId(courseId); // Don't persist a wrong ID
Course result = courseRepository.save(courseUpdate);

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/web/rest/FileResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,20 @@ public ResponseEntity<byte[]> getCourseIcon(@PathVariable Long courseId) {
return responseEntityForFilePath(filePathService.actualPathForPublicPath(URI.create(course.getCourseIcon())));
}

/**
* GET /files/templates/code-of-conduct : Get the Code of Conduct template
*
* @return The requested file, 403 if the logged-in user is not allowed to access it, or 404 if the file doesn't exist
*/
@GetMapping("files/templates/code-of-conduct")
@EnforceAtLeastInstructor
public ResponseEntity<byte[]> getCourseCodeOfConduct() throws IOException {
var templatePath = Path.of("templates", "codeofconduct", "README.md");
log.debug("REST request to get template : {}", templatePath);
var resource = resourceLoaderService.getResource(templatePath);
return ResponseEntity.ok(resource.getInputStream().readAllBytes());
}

/**
* GET /files/exam-user/signatures/:examUserId/:filename : Get the exam user signature
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package de.tum.in.www1.artemis.web.rest.metis.conversation;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -22,6 +20,8 @@
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.ConductAgreementService;
import de.tum.in.www1.artemis.service.dto.ResponsibleUserDTO;
import de.tum.in.www1.artemis.service.dto.UserPublicInfoDTO;
import de.tum.in.www1.artemis.service.metis.conversation.ConversationService;
import de.tum.in.www1.artemis.service.metis.conversation.ConversationService.ConversationMemberSearchFilters;
Expand All @@ -46,13 +46,17 @@ public class ConversationResource extends ConversationManagementResource {

private final UserRepository userRepository;

private final ConductAgreementService conductAgreementService;

public ConversationResource(ConversationService conversationService, ChannelAuthorizationService channelAuthorizationService,
AuthorizationCheckService authorizationCheckService, UserRepository userRepository, CourseRepository courseRepository) {
AuthorizationCheckService authorizationCheckService, UserRepository userRepository, CourseRepository courseRepository,
ConductAgreementService conductAgreementService) {
super(courseRepository);
this.conversationService = conversationService;
this.channelAuthorizationService = channelAuthorizationService;
this.authorizationCheckService = authorizationCheckService;
this.userRepository = userRepository;
this.conductAgreementService = conductAgreementService;
}

/**
Expand Down Expand Up @@ -124,6 +128,61 @@ public ResponseEntity<Boolean> hasUnreadMessages(@PathVariable Long courseId) {
return ResponseEntity.ok(conversationService.userHasUnreadMessages(courseId, requestingUser));
}

/**
* GET /api/courses/:courseId/code-of-conduct/agreement : Checks if the user agrees to the code of conduct
*
* @param courseId the course's ID
* @return ResponseEntity with status 200 (Ok) and body is true if the user agreed to the course's code of conduct
*/
@GetMapping("/{courseId}/code-of-conduct/agreement")
@EnforceAtLeastStudent
public ResponseEntity<Boolean> isCodeOfConductAccepted(@PathVariable Long courseId) {
checkMessagingEnabledElseThrow(courseId);
var course = courseRepository.findByIdElseThrow(courseId);
var requestingUser = userRepository.getUserWithGroupsAndAuthorities();
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, requestingUser);
return ResponseEntity.ok(conductAgreementService.fetchUserAgreesToCodeOfConductInCourse(requestingUser, course));
}

/**
* PATCH /api/courses/:courseId/code-of-conduct/agreement : Accept the course's code of conduct
*
* @param courseId the course's ID
* @return ResponseEntity with status 200 (Ok)
*/
@PatchMapping("/{courseId}/code-of-conduct/agreement")
@EnforceAtLeastStudent
public ResponseEntity<Void> acceptCodeOfConduct(@PathVariable Long courseId) {
checkMessagingEnabledElseThrow(courseId);
var course = courseRepository.findByIdElseThrow(courseId);
var requestingUser = userRepository.getUserWithGroupsAndAuthorities();
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, requestingUser);
conductAgreementService.setUserAgreesToCodeOfConductInCourse(requestingUser, course);
return ResponseEntity.ok().build();
}

/**
* GET /api/courses/:courseId/code-of-conduct/responsible-users : Users responsible for the course
*
* @param courseId the course's ID
* @return ResponseEntity with the status 200 (Ok) and a list of users responsible for the course
*/
@GetMapping("/{courseId}/code-of-conduct/responsible-users")
@EnforceAtLeastStudent
public ResponseEntity<List<ResponsibleUserDTO>> getResponsibleUsersForCodeOfConduct(@PathVariable Long courseId) {
checkMessagingEnabledElseThrow(courseId);

var requestingUser = userRepository.getUserWithGroupsAndAuthorities();

var course = courseRepository.findByIdElseThrow(courseId);
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, requestingUser);

var responsibleUsers = userRepository.searchAllByLoginOrNameInGroups(Pageable.unpaged(), "", Set.of(course.getInstructorGroupName()))
.map((user) -> new ResponsibleUserDTO(user.getName(), user.getEmail())).toList();

return ResponseEntity.ok(responsibleUsers);
}

/**
* GET /api/courses/:courseId/conversations/:conversationId/members/search: Searches for members of a conversation
*
Expand Down
Loading

0 comments on commit 19a81aa

Please sign in to comment.