Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
afe02ce
Create new account for survey unit link
Aaron-Detre May 15, 2025
8ff88e4
Store isSurvey in RunImpl table of database (not working)
Aaron-Detre May 20, 2025
53baf97
Give survey students new role to differentiate from real student acco…
Aaron-Detre May 20, 2025
95c0189
Fixed isSurvey issue in database
Aaron-Detre May 21, 2025
e3c753f
Finished survey student role
Aaron-Detre May 21, 2025
4d2bd2d
Send isSurvey data back to the client with the rest of the run data
Aaron-Detre May 21, 2025
228339e
Hard limit on number of workgroups for survey units
Aaron-Detre May 23, 2025
7813e72
Redirect to log out page if logged in
Aaron-Detre May 27, 2025
c6c18ef
Don't create two workgroups per student
Aaron-Detre May 27, 2025
e6d358c
Clean code
Aaron-Detre May 28, 2025
d43a28d
Working on tests
Aaron-Detre May 29, 2025
085d1f5
Fix compile issues
hirokiterashima May 29, 2025
a729c91
Simplify path to launch survey to /run-survey/code
hirokiterashima May 29, 2025
4ffa497
Add ROLE_SURVEY_STUDENT initial value
hirokiterashima May 29, 2025
a60729f
Merge branch 'develop' into survey-units
hirokiterashima May 29, 2025
6a3dcbe
Add isSurvey flag
hirokiterashima May 29, 2025
39c78b4
Log out logged in users automatically + allow survey students already…
Aaron-Detre May 30, 2025
1a73072
Don't allow custom periods for surveys
Aaron-Detre May 30, 2025
367fc9c
Change isSurvey from Boolean to boolean.
hirokiterashima Jun 2, 2025
b9ef218
Fixed tests
hirokiterashima Jun 2, 2025
5020256
Redirect to homepage if run is not active.
hirokiterashima Jun 2, 2025
fc1fe6e
Change password from null to random string. Set firstname to be more …
hirokiterashima Jun 3, 2025
481e64b
Add isSurvey to run config
breity Jun 4, 2025
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
30 changes: 20 additions & 10 deletions src/main/java/org/wise/portal/domain/run/Run.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public interface Run extends Persistable {

/**
* Returns the period with periodName that is associated with this run
*
*
* @param periodName
* @return Group the period with the periodName that is associated with this run
* @throws <code>PeriodNotFoundException</code>
Expand Down Expand Up @@ -214,32 +214,42 @@ public interface Run extends Persistable {

/**
* Sets whether or not student asset uploading is enabled for this run.
*
*
* @return
*/
void setStudentAssetUploaderEnabled(boolean isStudentAssetUploaderEnabled);

/**
* Returns whether or not student asset uploading is enabled for this run.
*
*
* @return
*/
boolean isStudentAssetUploaderEnabled();

/**
* Sets whether or not idea manager is enabled for this run.
*
*
* @return
*/
void setIdeaManagerEnabled(boolean isIdeaManagerEnabled);

/**
* Returns whether or not idea manager is enabled for this run.
*
*
* @return
*/
boolean isIdeaManagerEnabled();

/**
* @return Whether or not the run a survey
*/
boolean isSurvey();

/**
* @param isSurvey
*/
void setIsSurvey(boolean isSurvey);

/**
* @return <code>Integer</code> maxWorkgroupSize
*/
Expand Down Expand Up @@ -341,7 +351,7 @@ public interface Run extends Persistable {

/**
* sets student attendance for this run
*
*
* @param studentAttendance
*/
void setStudentAttendance(List<StudentAttendance> studentAttendance);
Expand All @@ -353,29 +363,29 @@ public interface Run extends Persistable {

/**
* Gets private notes for this run
*
*
* @return String private notes for this run
*/
String getPrivateNotes();

/**
* Sets private notes for this run
*
*
* @param privateNotes
* private notes for this run
*/
void setPrivateNotes(String privateNotes);

/**
* Gets survey for this run
*
*
* @return String survey for this run
*/
String getSurvey();

/**
* Sets survey for this run
*
*
* @return String survey for this run
*/
void setSurvey(String survey);
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/org/wise/portal/domain/run/impl/RunImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ public class RunImpl implements Run {
@Transient
private static final String COLUMN_NAME_EXTRAS = "extras";

@Transient
private static final String COLUMN_NAME_IS_SURVEY = "isSurvey";

@Transient
private static final String COLUMN_NAME_MAX_WORKGROUP_SIZE = "maxWorkgroupSize";

Expand Down Expand Up @@ -199,6 +202,9 @@ public class RunImpl implements Run {
@Setter
private String info; // other info pertaining to the run

@Column(name = COLUMN_NAME_IS_SURVEY)
private boolean isSurvey;

@Column(name = COLUMN_NAME_MAX_WORKGROUP_SIZE, nullable = true)
@Getter
@Setter
Expand Down Expand Up @@ -427,7 +433,7 @@ public static class UserAlphabeticalComparator implements Comparator<User> {

/**
* Compares the user names of two User objects
*
*
* @param user1
* a user object
* @param user2
Expand Down Expand Up @@ -480,4 +486,12 @@ public boolean isLockedAfterEndDate() {
public void setLockedAfterEndDate(boolean isLockedAfterEndDate) {
this.isLockedAfterEndDate = isLockedAfterEndDate;
}

public boolean isSurvey() {
return isSurvey;
}

public void setIsSurvey(boolean isSurvey) {
this.isSurvey = isSurvey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.wise.portal.domain.project.Project;
import org.wise.portal.domain.user.User;

import java.io.Serializable;
import java.util.*;

/**
Expand Down Expand Up @@ -70,6 +69,8 @@ public class RunParameters implements Serializable {

private Boolean isLockedAfterEndDate = false;

private boolean isSurvey = false;

public String printAllPeriods() {
String allPeriods = null;

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/wise/portal/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public interface User extends Persistable, Comparable<User> {

boolean isStudent();

boolean isSurveyStudent();

boolean isTeacher();

boolean isTrustedAuthor();
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/wise/portal/domain/user/impl/UserImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ public boolean isStudent() {
return userDetails.hasGrantedAuthority(UserDetailsService.STUDENT_ROLE);
}

public boolean isSurveyStudent() {
return userDetails.hasGrantedAuthority(UserDetailsService.SURVEY_STUDENT_ROLE);
}

public boolean isTeacher() {
return userDetails.hasGrantedAuthority(UserDetailsService.TEACHER_ROLE);
}
Expand Down Expand Up @@ -111,6 +115,9 @@ public List<String> getRoles() {
if (this.isStudent()) {
roles.add("student");
}
if (this.isSurveyStudent()) {
roles.add("surveyStudent");
}
return roles;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ private void getRunConfigParameters(HttpServletRequest request, JSONObject confi
config.put("startTime", run.getStartTimeMilliseconds());
config.put("endTime", run.getEndTimeMilliseconds());
config.put("isLockedAfterEndDate", run.isLockedAfterEndDate());
config.put("isSurvey", run.isSurvey());
}

private void printConfigToResponse(HttpServletResponse response, JSONObject config)
Expand Down Expand Up @@ -785,6 +786,7 @@ private void addDummyUserInfoToConfig(JSONObject config) {

/**
* Gets the workgroup for the logged in user
*
* @param run
* @return Workgroup for the logged in user
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ private HashMap<String, Object> getRunInfo(Run run) {
info.put("teacherFirstName", owner.getUserDetails().getFirstname());
info.put("teacherLastName", owner.getUserDetails().getLastname());
info.put("wiseVersion", run.getProject().getWiseVersion());
info.put("isSurvey", run.isSurvey());
return info;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package org.wise.portal.presentation.web.controllers.survey;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.wise.portal.dao.ObjectNotFoundException;
import org.wise.portal.domain.PeriodNotFoundException;
import org.wise.portal.domain.RunHasEndedException;
import org.wise.portal.domain.StudentUserAlreadyAssociatedWithRunException;
import org.wise.portal.domain.authentication.Gender;
import org.wise.portal.domain.authentication.impl.StudentUserDetails;
import org.wise.portal.domain.project.impl.Projectcode;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.user.User;
import org.wise.portal.service.authentication.AuthorityNotFoundException;
import org.wise.portal.service.authentication.DuplicateUsernameException;
import org.wise.portal.service.authentication.UserDetailsService;
import org.wise.portal.service.run.RunService;
import org.wise.portal.service.student.StudentService;
import org.wise.portal.service.user.UserService;
import org.wise.portal.service.workgroup.WorkgroupService;

import java.io.IOException;
import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@RestController
@RequestMapping("/run-survey")
public class SurveyAPIController {
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private RunService runService;

@Autowired
private StudentService studentService;

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private UserService userService;

@Autowired
private WorkgroupService workgroupService;

@GetMapping("/{code}")
public void launchSurveyRun(@PathVariable String code, HttpServletResponse response,
HttpServletRequest request) throws AuthorityNotFoundException, IOException,
DuplicateUsernameException, ObjectNotFoundException, PeriodNotFoundException,
StudentUserAlreadyAssociatedWithRunException, RunHasEndedException {
Projectcode projectCode = new Projectcode(code);
Run run = runService.retrieveRunByRuncode(projectCode.getRuncode());
if (run.isSurvey() && isActive(run)) {
handleSurveyLaunched(response, request, run, projectCode);
} else {
sendRedirect(response, "/");
}
}

private boolean isActive(Run run) {
Date now = new Date();
Date endTime = run.getEndtime();
return run.getStarttime().before(now) && (endTime == null || endTime.after(now));
}

private void handleSurveyLaunched(HttpServletResponse response, HttpServletRequest request,
Run run, Projectcode projectCode) throws AuthorityNotFoundException, IOException,
DuplicateUsernameException, ObjectNotFoundException, PeriodNotFoundException,
StudentUserAlreadyAssociatedWithRunException, RunHasEndedException {

Object principal = getSecurityContextHolderPrincipal();
if (principal instanceof StudentUserDetails
&& isStudentAssociatedWithRun(run, (StudentUserDetails) principal)) {
sendRedirect(response, "/student/unit/" + run.getId());
return;
} else {
SecurityContextHolder.getContext().setAuthentication(null);
}
if (underWorkgroupLimit(run)) {
String password = RandomStringUtils.randomAlphanumeric(10);
User user = this.createNewStudentAccount(request.getLocale(), password);
loginStudent(request, user, password);
studentService.addStudentToRun(user, projectCode);
sendRedirect(response, "/student/unit/" + run.getId());
} else {
sendRedirect(response, "/survey/workgroupLimitReached");
}
}

private void sendRedirect(HttpServletResponse response, String redirectUrl) throws IOException {
response.sendRedirect(redirectUrl);
}

private Object getSecurityContextHolderPrincipal() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}

private boolean isStudentAssociatedWithRun(Run run, StudentUserDetails principal) {
return run.isStudentAssociatedToThisRun(userService.retrieveStudentById((principal).getId()));
}

private boolean underWorkgroupLimit(Run run) {
return workgroupService.getWorkgroupsForRun(run).size() <= 1000;
}

private void loginStudent(HttpServletRequest request, User user, String password) {
UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(
user.getUserDetails().getUsername(), password);
Authentication auth = authenticationManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
HttpSession session = request.getSession(true);
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sc);
}

private User createNewStudentAccount(Locale locale, String password)
throws AuthorityNotFoundException, DuplicateUsernameException {
StudentUserDetails sud = new StudentUserDetails();
sud.setFirstname("survey_student_" + RandomStringUtils.randomAlphanumeric(10));
sud.setLastname(RandomStringUtils.randomAlphanumeric(10));
sud.setBirthday(new Date());
sud.setPassword(password);
sud.setGender(Gender.UNSPECIFIED);
sud.setEmailAddress("null@null.com");
sud.setLanguage(locale.getLanguage());
sud.setNumberOfLogins(1);
sud.setLastLoginTime(new Date());

User user = userService.createUser(sud);
user.getUserDetails().addAuthority(
userDetailsService.loadAuthorityByName(UserDetailsService.SURVEY_STUDENT_ROLE));
return user;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ private List<Integer> getSharedOwnerPermissionsList(Run run, User user) {
@PostMapping("/run/create")
HashMap<String, Object> createRun(Authentication auth, HttpServletRequest request,
@RequestParam("projectId") Long projectId, @RequestParam("periods") String periods,
@RequestParam boolean isSurvey,
@RequestParam("maxStudentsPerTeam") Integer maxStudentsPerTeam,
@RequestParam("startDate") Long startDate,
@RequestParam(value = "endDate", required = false) Long endDate,
Expand All @@ -263,8 +264,8 @@ HashMap<String, Object> createRun(Authentication auth, HttpServletRequest reques
User user = userService.retrieveUserByUsername(auth.getName());
Locale locale = request.getLocale();
Set<String> periodNames = createPeriodNamesSet(periods);
Run run = runService.createRun(projectId, user, periodNames, maxStudentsPerTeam, startDate,
endDate, isLockedAfterEndDate, locale);
Run run = runService.createRun(projectId, user, periodNames, isSurvey, maxStudentsPerTeam,
startDate, endDate, isLockedAfterEndDate, locale);
return getRunMap(user, run);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.wise.portal.domain.authentication.MutableUserDetails;
import org.wise.portal.domain.authentication.impl.StudentUserDetails;
import org.wise.portal.domain.authentication.impl.TeacherUserDetails;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.project.Project;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.user.User;
Expand Down Expand Up @@ -262,6 +261,7 @@ protected HashMap<String, Object> getRunMap(User user, Run run) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id", run.getId());
map.put("name", run.getName());
map.put("isSurvey", run.isSurvey());
map.put("maxStudentsPerTeam", run.getMaxWorkgroupSize());
map.put("runCode", run.getRuncode());
map.put("startTime", run.getStartTimeMilliseconds());
Expand Down
Loading