Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.wise.portal.dao.teacherreflection;

import java.util.List;

import org.wise.portal.dao.SimpleDao;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.vle.domain.teacherreflection.TeacherReflection;

public interface TeacherReflectionDao<T extends TeacherReflection> extends SimpleDao<T> {

List<T> getQuestions(Run run, Group period, String nodeId, String componentId);

T getQuestion(Run run, Group period, String nodeId, String componentId, String questionId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.wise.portal.dao.teacherreflection.impl;

import java.util.List;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;

import org.springframework.stereotype.Repository;
import org.wise.portal.dao.impl.AbstractHibernateDao;
import org.wise.portal.dao.teacherreflection.TeacherReflectionDao;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.vle.domain.teacherreflection.TeacherReflection;

@Repository("teacherReflectionDao")
public class HibernateTeacherReflectionDao extends AbstractHibernateDao<TeacherReflection>
implements TeacherReflectionDao<TeacherReflection> {

@Override
protected Class<? extends TeacherReflection> getDataObjectClass() {
return TeacherReflection.class;
}

@Override
public List<TeacherReflection> getQuestions(Run run, Group period, String nodeId,
String componentId) {
CriteriaBuilder cb = getCriteriaBuilder();
CriteriaQuery<TeacherReflection> cq = cb.createQuery(TeacherReflection.class);
Root<TeacherReflection> root = cq.from(TeacherReflection.class);
cq.select(root).where(cb.equal(root.get("run"), run), cb.equal(root.get("period"), period),
cb.equal(root.get("nodeId"), nodeId), cb.equal(root.get("componentId"), componentId))
.orderBy(cb.asc(root.get("id")));
return entityManager.createQuery(cq).getResultList();
}

@Override
public TeacherReflection getQuestion(Run run, Group period, String nodeId, String componentId,
String questionId) {
CriteriaBuilder cb = getCriteriaBuilder();
CriteriaQuery<TeacherReflection> cq = cb.createQuery(TeacherReflection.class);
Root<TeacherReflection> root = cq.from(TeacherReflection.class);
cq.select(root).where(cb.equal(root.get("run"), run), cb.equal(root.get("period"), period),
cb.equal(root.get("nodeId"), nodeId), cb.equal(root.get("componentId"), componentId),
cb.equal(root.get("questionId"), questionId));
List<TeacherReflection> results = entityManager.createQuery(cq).getResultList();
return results.isEmpty() ? null : results.get(0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.wise.portal.presentation.web.controllers.teacher;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.wise.portal.dao.ObjectNotFoundException;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.user.User;
import org.wise.portal.domain.workgroup.Workgroup;
import org.wise.portal.service.run.RunService;
import org.wise.portal.service.teacherreflection.TeacherReflectionService;
import org.wise.portal.service.user.UserService;
import org.wise.portal.service.workgroup.WorkgroupService;
import org.wise.vle.domain.teacherreflection.TeacherReflection;

/**
* REST API for TeacherReflection: viewing and answering teacher reflection questions
* for a component in a period of a run.
*/
@RestController
@RequestMapping("/api/teacher/reflection")
@PreAuthorize("hasRole('ROLE_TEACHER')")
public class TeacherReflectionAPIController {

@Autowired
private TeacherReflectionService teacherReflectionService;

@Autowired
private RunService runService;

@Autowired
private UserService userService;

@Autowired
private WorkgroupService workgroupService;

public record AnswerRequest(Long runId, Long periodId, String nodeId, String componentId,
String componentType, String questionId, String questionText, String answerText,
Long answeredByWorkgroupId) {
}

public record TeacherReflectionResponse(Long id, Long runId, Long periodId, String nodeId,
String componentId, String componentType, String questionId, String questionText,
String answerText, Long answeredByWorkgroupId, Long createdAt, Long updatedAt) {
}

@GetMapping
public List<TeacherReflectionResponse> getQuestions(Authentication auth, @RequestParam Long runId,
@RequestParam Long periodId, @RequestParam String nodeId, @RequestParam String componentId)
throws ObjectNotFoundException {
Run run = runService.retrieveById(runId);
User user = userService.retrieveUserByUsername(auth.getName());
if (!runService.isAllowedToViewStudentWork(run, user)) {
throw new AccessDeniedException("Not permitted");
}
Group period = getPeriod(run, periodId);
List<TeacherReflection> reflections = teacherReflectionService.getQuestions(run, period, nodeId,
componentId);
List<TeacherReflectionResponse> response = new ArrayList<>();
for (TeacherReflection reflection : reflections) {
response.add(toResponse(reflection));
}
return response;
}

@PutMapping("/answer")
public TeacherReflectionResponse saveAnswer(Authentication auth,
@RequestBody AnswerRequest request) throws ObjectNotFoundException {
Run run = runService.retrieveById(request.runId());
User user = userService.retrieveUserByUsername(auth.getName());
if (!runService.isAllowedToGradeStudentWork(run, user)) {
throw new AccessDeniedException("Not permitted");
}
Group period = getPeriod(run, request.periodId());
if (request.answeredByWorkgroupId() == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"answeredByWorkgroupId cannot be null");
}
Workgroup teacherWorkgroup = workgroupService.retrieveById(request.answeredByWorkgroupId());
if (teacherWorkgroup != null && !teacherWorkgroup.isTeacherWorkgroup()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Workgroup is not a teacher workgroup");
}
if (teacherWorkgroup != null && !run.equals(teacherWorkgroup.getRun())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Workgroup does not belong to the run");
}
try {
TeacherReflection reflection = teacherReflectionService.saveAnswer(run, period,
request.nodeId(), request.componentId(), request.componentType(), request.questionId(),
request.questionText(), request.answerText(), teacherWorkgroup);
return toResponse(reflection);
} catch (IllegalArgumentException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}

private Group getPeriod(Run run, Long periodId) {
for (Group period : run.getPeriods()) {
if (period.getId().equals(periodId)) {
return period;
}
}
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Period does not belong to the run");
}

private TeacherReflectionResponse toResponse(TeacherReflection reflection) {
return new TeacherReflectionResponse(reflection.getId(), reflection.getRun().getId(),
reflection.getPeriod().getId(), reflection.getNodeId(), reflection.getComponentId(),
reflection.getComponentType(), reflection.getQuestionId(), reflection.getQuestionText(),
reflection.getAnswerText(), reflection.getAnsweredByWorkgroup().getId(),
reflection.getCreatedAt().getTime(), reflection.getUpdatedAt().getTime());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.wise.portal.service.teacherreflection;

import java.util.List;

import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.workgroup.Workgroup;
import org.wise.vle.domain.teacherreflection.TeacherReflection;

public interface TeacherReflectionService {

List<TeacherReflection> getQuestions(Run run, Group period, String nodeId,
String componentId);

TeacherReflection saveAnswer(Run run, Group period, String nodeId,
String componentId, String componentType, String questionId, String questionText,
String answerText, Workgroup teacherWorkgroup);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.wise.portal.service.teacherreflection.impl;

import java.sql.Timestamp;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wise.portal.dao.teacherreflection.TeacherReflectionDao;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.workgroup.Workgroup;
import org.wise.portal.service.teacherreflection.TeacherReflectionService;
import org.wise.vle.domain.teacherreflection.TeacherReflection;

@Service
public class TeacherReflectionServiceImpl implements TeacherReflectionService {

private static final int MAX_ID_LENGTH = 30;
private static final int MAX_QUESTION_TEXT_LENGTH = 5000;
private static final int MAX_ANSWER_TEXT_LENGTH = 20000;

@Autowired
private TeacherReflectionDao<TeacherReflection> teacherReflectionDao;

@Override
@Transactional(readOnly = true)
public List<TeacherReflection> getQuestions(Run run, Group period, String nodeId,
String componentId) {
return teacherReflectionDao.getQuestions(run, period, nodeId, componentId);
}

@Override
@Transactional
public TeacherReflection saveAnswer(Run run, Group period, String nodeId, String componentId,
String componentType, String questionId, String questionText, String answerText,
Workgroup teacherWorkgroup) {
validateComponent(nodeId, componentId, componentType);
if (questionId == null || questionId.isBlank() || questionId.length() > MAX_ID_LENGTH) {
throw new IllegalArgumentException("Invalid questionId");
}
if (questionText == null || questionText.length() > MAX_QUESTION_TEXT_LENGTH) {
throw new IllegalArgumentException("Invalid questionText");
}
if (answerText == null || answerText.length() > MAX_ANSWER_TEXT_LENGTH) {
throw new IllegalArgumentException("Invalid answerText");
}
if (teacherWorkgroup == null) {
throw new IllegalArgumentException("teacherWorkgroup cannot be null");
}

Timestamp now = new Timestamp(System.currentTimeMillis());
TeacherReflection reflection = teacherReflectionDao.getQuestion(run, period, nodeId,
componentId, questionId);
if (reflection == null) {
reflection = new TeacherReflection();
reflection.setRun(run);
reflection.setPeriod(period);
reflection.setNodeId(nodeId);
reflection.setComponentId(componentId);
reflection.setComponentType(componentType);
reflection.setQuestionId(questionId);
reflection.setCreatedAt(now);
}
reflection.setQuestionText(questionText);
reflection.setAnswerText(answerText);
reflection.setAnsweredByWorkgroup(teacherWorkgroup);
reflection.setUpdatedAt(now);
teacherReflectionDao.save(reflection);
return reflection;
}

private void validateComponent(String nodeId, String componentId, String componentType) {
if (nodeId == null || nodeId.isBlank() || nodeId.length() > MAX_ID_LENGTH) {
throw new IllegalArgumentException("Invalid nodeId");
}
if (componentId == null || componentId.isBlank() || componentId.length() > MAX_ID_LENGTH) {
throw new IllegalArgumentException("Invalid componentId");
}
if (componentType == null || componentType.isBlank()
|| componentType.length() > MAX_ID_LENGTH) {
throw new IllegalArgumentException("Invalid componentType");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.wise.vle.domain.teacherreflection;

import java.sql.Timestamp;

import com.fasterxml.jackson.annotation.JsonIgnore;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.Getter;
import lombok.Setter;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.group.impl.PersistentGroup;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.run.impl.RunImpl;
import org.wise.portal.domain.workgroup.Workgroup;
import org.wise.portal.domain.workgroup.impl.WorkgroupImpl;

/**
* A teacher's reflection and answer for a component in a period of a run.
* Stored in teacher_reflections.
*/
@Entity(name = "teacherReflection")
@Table(name = "teacher_reflections", uniqueConstraints = @UniqueConstraint(name = "teacherReflectionsUniqueKey", columnNames = {
"runId", "periodId", "nodeId", "componentId",
"questionId" }), indexes = @Index(columnList = "runId", name = "teacherReflectionsRunIdIndex"))
@Getter
@Setter
public class TeacherReflection {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(targetEntity = RunImpl.class, fetch = FetchType.LAZY)
@JoinColumn(name = "runId", nullable = false)
@JsonIgnore
private Run run;

@ManyToOne(targetEntity = PersistentGroup.class, fetch = FetchType.LAZY)
@JoinColumn(name = "periodId", nullable = false)
@JsonIgnore
private Group period;

@Column(name = "nodeId", length = 30, nullable = false)
private String nodeId;

@Column(name = "componentId", length = 30, nullable = false)
private String componentId;

@Column(name = "componentType", length = 30, nullable = false)
private String componentType;

@Column(name = "questionId", length = 30, nullable = false)
private String questionId;

@Column(name = "questionText", length = 65536, columnDefinition = "text", nullable = false)
private String questionText;

@Column(name = "answerText", length = 65536, columnDefinition = "text", nullable = false)
private String answerText;

@ManyToOne(targetEntity = WorkgroupImpl.class, fetch = FetchType.LAZY)
@JoinColumn(name = "answeredByWorkgroupId", nullable = false)
@JsonIgnore
private Workgroup answeredByWorkgroup;

@Column(name = "createdAt", nullable = false)
private Timestamp createdAt;

@Column(name = "updatedAt", nullable = false)
private Timestamp updatedAt;
}
Loading
Loading