diff options
author | Mole Shang <[email protected]> | 2024-12-15 00:00:13 +0800 |
---|---|---|
committer | Mole Shang <[email protected]> | 2024-12-15 00:02:19 +0800 |
commit | d3f77ea51dd72b055307ac259c3caf8091307aa6 (patch) | |
tree | ba5e10e04d1c6e4ce281f7cf6e43abad407eac65 /src/main/java/seu/se | |
download | se-intro-master.tar.gz se-intro-master.tar.bz2 se-intro-master.zip |
Diffstat (limited to 'src/main/java/seu/se')
-rw-r--r-- | src/main/java/seu/se/ApiController.java | 370 | ||||
-rw-r--r-- | src/main/java/seu/se/Application.java | 102 | ||||
-rw-r--r-- | src/main/java/seu/se/Exercise.java | 125 | ||||
-rw-r--r-- | src/main/java/seu/se/TreeNode.java | 215 | ||||
-rw-r--r-- | src/main/java/seu/se/TreeNodeRepository.java | 8 | ||||
-rw-r--r-- | src/main/java/seu/se/User.java | 65 | ||||
-rw-r--r-- | src/main/java/seu/se/UserRepository.java | 11 |
7 files changed, 896 insertions, 0 deletions
diff --git a/src/main/java/seu/se/ApiController.java b/src/main/java/seu/se/ApiController.java new file mode 100644 index 0000000..17a6702 --- /dev/null +++ b/src/main/java/seu/se/ApiController.java @@ -0,0 +1,370 @@ +package seu.se; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.HashSet; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@RequestMapping("/api") +@RestController +public class ApiController { + public static abstract class DataObject { + public Boolean ok; + public String message; + public Object otherData; + } + + public static abstract class ReturnObject { + public String status; + public DataObject dataObj; + } + + @GetMapping(value = "/ping") + public String Ping() { + return "Pong!"; + } + + @RequestMapping("/api/user") + @RestController + static class UserController { + private final UserRepository uRepo; + + UserController(UserRepository uRepo) { + this.uRepo = uRepo; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "400", description = "Invalid userId supplied"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @PostMapping("/login") + public JsonNode Login(@RequestBody com.fasterxml.jackson.databind.JsonNode body) { + var id = body.get("userId").asInt(); + var password = body.get("password").asText(); + var user = uRepo.findById(id); + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + String message; + Boolean ok; + if (user != null) { + if (password.equals(user.getPassword())) { + ok = true; + message = "Login Success"; + // save session + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + attr.getRequest().getSession(true).setAttribute("SESSION", user); + + } else { + ok = false; + message = "Wrong password"; + } + dataObj = new ObjectMapper().valueToTree(user); + } else { + ok = false; + message = "User not found"; + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + return retObj; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @PostMapping("/register") + public JsonNode Register(@RequestBody com.fasterxml.jackson.databind.JsonNode body) { + var username = body.get("username").asText(); + var password = body.get("password").asText(); + String message; + Boolean ok; + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + try { + var user = uRepo.save(new User(username, password)); + ok = true; + message = "Registered"; + dataObj = new ObjectMapper().valueToTree(user); + } catch (User.UserException ue) { + ok = false; + message = ue.getMessage(); + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + return retObj; + } + } + + @RequestMapping("/api/node") + @RestController + static class TreeNodeController { + private final TreeNodeRepository tnRepo; + + TreeNodeController(TreeNodeRepository tnRepo) { + this.tnRepo = tnRepo; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @GetMapping("/content/{nodeId}") + public JsonNode getContent(@PathVariable String nodeId) { + String message; + Boolean ok; + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + var treeNode = tnRepo.findByNodeId(nodeId); + if (treeNode != null) { + ok = true; + message = "Success"; + dataObj.put("nodeId", treeNode.getNodeId()); + dataObj.put("content", treeNode.getContent()); + } else { + ok = false; + message = "Node not found"; + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + return retObj; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @GetMapping("/exercises/{nodeId}") + public JsonNode getExercises(@PathVariable String nodeId) { + String message; + Boolean ok; + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + var treeNode = tnRepo.findByNodeId(nodeId); + if (treeNode != null) { + ok = true; + message = "Success"; + dataObj.put("nodeId", treeNode.getNodeId()); + var exercisesObj = new ObjectMapper().createArrayNode(); + for (var i : treeNode.getExercises()) { + var exerciseObj = new ObjectMapper().valueToTree(i); + exercisesObj.add(exerciseObj); + } + dataObj.set("exercises", exercisesObj); + } else { + ok = false; + message = "Node not found"; + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + System.out.println(attr.getRequest().getSession(true).getAttribute("SESSION")); + + return retObj; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @PostMapping("/submit/{nodeId}") + public JsonNode Submit(@PathVariable String nodeId, @RequestBody com.fasterxml.jackson.databind.JsonNode body) { + String message; + Boolean ok; + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + var treeNode = tnRepo.findByNodeId(nodeId); + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + var user = (User) attr.getRequest().getSession(true).getAttribute("SESSION"); + if (treeNode == null) { + ok = false; + message = "Node not found"; + } else if (user == null) { + ok = false; + message = "Login first"; + } else { + var score = body.get("score").asDouble(); + var usp = treeNode.getUserScorePair(user.getId(), nodeId); + if (usp != null) { + usp.getScores().add(score); + } else { + treeNode.getUserScores().add(new TreeNode.UserScorePair(treeNode.getNodeId(), user.getId(), List.of(score))); + } + tnRepo.save(treeNode); + ok = true; + message = "Success"; + dataObj.put("userId", user.getId()); + dataObj.put("nodeId", nodeId); + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + return retObj; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @GetMapping("/history/{nodeId}") + public JsonNode History(@PathVariable String nodeId) { + String message; + Boolean ok; + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + var treeNode = tnRepo.findByNodeId(nodeId); + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + var user = (User) attr.getRequest().getSession(true).getAttribute("SESSION"); + if (treeNode == null) { + ok = false; + message = "Node not found"; + } else if (user == null) { + ok = false; + message = "Login first"; + } else { + var usp = treeNode.getUserScorePair(user.getId(), nodeId); + if (usp != null) { + var uspObj = new ObjectMapper().valueToTree(usp); + ok = true; + message = "Success"; + dataObj.set("historyScores", uspObj); + } else { + ok = false; + message = "No score found"; + } + dataObj.put("userId", user.getId()); + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + return retObj; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @GetMapping("/home") + public JsonNode Home() { + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + String message; + Boolean ok; + var nodes = tnRepo.findAll(); + var roots = new HashSet<TreeNode>(); + for (var n : nodes) { + if (n.getLevel() == 1) roots.add(n); + } + if (roots.isEmpty()) { + ok = false; + message = "No root found"; + } else { + class RecursiveHelper { + public final List<TreeNode> nodes; + + public RecursiveHelper(List<TreeNode> nodes) { + this.nodes = nodes; + } + + public ObjectNode RecursiveConstruct(TreeNode tn) { + ObjectNode graphNodesObj = new ObjectMapper().valueToTree(tn); + ArrayNode graphNodesIterateArr = new ObjectMapper().createArrayNode(); + var children = tn.getChildren(nodes); + if (!children.isEmpty()) { + children.forEach(e -> { + graphNodesIterateArr.add(RecursiveConstruct(e)); + }); + graphNodesObj.set("children", graphNodesIterateArr); + } + return graphNodesObj; + } + } + ArrayNode graphNodesIterateArr = new ObjectMapper().createArrayNode(); + for (var root : roots) { + graphNodesIterateArr.add(new RecursiveHelper(nodes).RecursiveConstruct(root)); + } + dataObj.set("graphNodes", graphNodesIterateArr); + ok = true; + message = "Success"; + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + return retObj; + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = + {@Content(mediaType = "application/json", schema = + @Schema(implementation = ReturnObject.class))}), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @GetMapping("/review-suggestion") + public JsonNode ReviewSuggestion() { + String message; + Boolean ok; + var retObj = new ObjectMapper().createObjectNode(); + var dataObj = new ObjectMapper().createObjectNode(); + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + var user = (User) attr.getRequest().getSession(true).getAttribute("SESSION"); + if (user == null) { + ok = false; + message = "Login first"; + } else { + var reviewSuggestionsArr = new ObjectMapper().createArrayNode(); + var nodes = tnRepo.findAll(); + for (var node : nodes) { + var usp = node.getUserScorePair(user.getId(), node.getNodeId()); + if (usp == null) continue; + var msl = TreeNode.UserScorePair.MasteryLevel.getMasteryLevel(usp.getAverageScore()); + if (msl.equals(TreeNode.UserScorePair.MasteryLevel.Average) || msl.equals(TreeNode.UserScorePair.MasteryLevel.Bad)) + reviewSuggestionsArr.add(node.getNodeId()); + } + ok = true; + message = "Success"; + dataObj.put("userId", user.getId()); + dataObj.set("reviewSuggestions", reviewSuggestionsArr); + } + dataObj.put("ok", ok); + dataObj.put("message", message); + retObj.set("data", dataObj); + retObj.put("status", "ok"); + return retObj; + } + } +} diff --git a/src/main/java/seu/se/Application.java b/src/main/java/seu/se/Application.java new file mode 100644 index 0000000..928bc28 --- /dev/null +++ b/src/main/java/seu/se/Application.java @@ -0,0 +1,102 @@ +package seu.se; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.NoHandlerFoundException; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@EnableTransactionManagement +@EnableNeo4jRepositories +@SpringBootApplication +public class Application { + private static final Logger log = LoggerFactory.getLogger(Application.class); + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @ControllerAdvice + public static class ExHandler { + @ExceptionHandler(Exception.class) + public ResponseEntity<Object> handle(Exception ex, HttpServletRequest request, HttpServletResponse response) { + HttpStatus code = HttpStatus.INTERNAL_SERVER_ERROR; + String status = "internal server error"; + if (ex instanceof NullPointerException) { + code = HttpStatus.BAD_REQUEST; + status = "bad request"; + } + if (ex instanceof NoHandlerFoundException) { + code = HttpStatus.NOT_FOUND; + status = "not found"; + } + var retObj = new ObjectMapper().createObjectNode(); + retObj.put("status", status); + retObj.put("message", ex.getMessage()); + return ResponseEntity.status(code).body(retObj); + } + } + + // in-mem session + @EnableSpringHttpSession + @Configuration(proxyBeanMethods = false) + public static class SpringHttpSessionConfig { + @Bean + public MapSessionRepository sessionRepository() { + return new MapSessionRepository(new ConcurrentHashMap<>()); + } + } + + @Bean + public CommandLineRunner init(UserRepository ur, TreeNodeRepository tnr) { + return (args) -> { + // Load users + try { + List<User> users = new ObjectMapper().readValue( + new ClassPathResource("users.json").getFile(), + new TypeReference<>() { + } + ); + ur.saveAll(users); + } catch (IOException e) { + log.error("Error loading users from JSON file", e); + } + + // Load tree nodes + TreeNode.tnRepo = tnr; + try { + List<TreeNode> treeNodes = new ObjectMapper().readValue( + new ClassPathResource("treeNodes.json").getFile(), + new TypeReference<>() { + } + ); + tnr.saveAll(treeNodes); + } catch (IOException e) { + log.error("Error loading tree nodes from JSON file", e); + } + }; + } + +} diff --git a/src/main/java/seu/se/Exercise.java b/src/main/java/seu/se/Exercise.java new file mode 100644 index 0000000..6df9212 --- /dev/null +++ b/src/main/java/seu/se/Exercise.java @@ -0,0 +1,125 @@ +package seu.se; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.PersistenceCreator; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +@Node +public class Exercise { + @Id + private String questionId; + private String question; + private List<OptionPair> options; + private String answer; + + public interface Choice { + String A = "A"; + String B = "B"; + String C = "C"; + String D = "D"; + } + + @Node + @JsonSerialize(using = OptionPair.OptionPairSerializer.class) + public static class OptionPair { + @Id + private final String id; + private final String key; + private final String value; + + public static class OptionPairSerializer extends StdSerializer<OptionPair> { + public OptionPairSerializer() { + this(null); + } + + public OptionPairSerializer(Class<OptionPair> t) { + super(t); + } + + @Override + public void serialize(OptionPair value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + gen.writeStringField(value.key, value.value); + gen.writeEndObject(); + } + } + + public OptionPair(String id, String key, String value) { + this.id = id; + this.key = key; + this.value = value; + } + + public String value() { + return value; + } + + public String key() { + return key; + } + + public String getId() { + return id; + } + } + + @JsonCreator + public Exercise(@JsonProperty("questionId") String questionId, @JsonProperty("question") String question, @JsonProperty("options") Map<String, String> options, @JsonProperty("answer") String answer) { + this.questionId = questionId; + this.question = question; + this.options = new LinkedList<OptionPair>(); + options.forEach((k, v) -> this.options.add(new OptionPair(String.format("%s.%s", questionId, k), k, v))); + this.answer = answer; + } + + @Autowired + @PersistenceCreator + // https://stackoverflow.com/questions/55827640/how-to-fix-failed-to-instantiate-classname-using-constructor-no-constructor + public Exercise(String questionId, String question, List<OptionPair> options, String answer) { + this.questionId = questionId; + this.question = question; + this.options = options; + this.answer = answer; + } + + public String getQuestion() { + return question; + } + + public void setQuestion(String question) { + this.question = question; + } + + public String getQuestionId() { + return questionId; + } + + public List<OptionPair> getOptions() { + return options; + } + + public void setOptions(List<OptionPair> options) { + this.options = options; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } +} diff --git a/src/main/java/seu/se/TreeNode.java b/src/main/java/seu/se/TreeNode.java new file mode 100644 index 0000000..f6e28b2 --- /dev/null +++ b/src/main/java/seu/se/TreeNode.java @@ -0,0 +1,215 @@ +package seu.se; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.PersistenceCreator; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + + +@Node +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TreeNode { + @Id + final private String nodeId; + final private String label; + @JsonIgnore + final private String content; + final private String relation; + @JsonIgnore + final private TreeNode father; + @JsonIgnore + private List<Exercise> exercises; + @JsonIgnore + private List<UserScorePair> userScores; + public static TreeNodeRepository tnRepo; + + @Node + public static class UserScorePair { + @Id + private final String id; + private final String nodeId; + private final Long userId; + private List<Double> scores; + + public interface MasteryLevel { + String Excellent = "完美"; + String Good = "很好"; + String Average = "一般"; + String Bad = "差"; + + static String getMasteryLevel(double averageScore) { + if (averageScore > 95) + return Excellent; + else if (averageScore > 80) + return Good; + else if (averageScore > 60) + return Average; + else + return Bad; + } + } + + public static class UserScoreSerializer extends StdSerializer<UserScorePair> { + public UserScoreSerializer() { + this(null); + } + + public UserScoreSerializer(Class<UserScorePair> t) { + super(t); + } + + @Override + public void serialize(UserScorePair value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + gen.writeStringField("nodeId", value.nodeId); + var avg = value.getAverageScore(); + gen.writeNumberField("averageScore", avg); + gen.writeNumberField("lastScore", value.getLastScore()); + gen.writeStringField("masteryLevel", MasteryLevel.getMasteryLevel(avg)); + gen.writeEndObject(); + } + } + + public UserScorePair(String nodeId, Long userId, List<Double> scores) { + this(String.format("%s.%d", nodeId, userId), nodeId, userId, scores); + } + + @Autowired + @PersistenceCreator + public UserScorePair(String id, String nodeId, Long userId, List<Double> scores) { + this.id = id; + this.nodeId = nodeId; + this.userId = userId; + this.scores = scores; + } + + public String getId() { + return id; + } + + public String getNodeId() { + return nodeId; + } + + public Long getUserId() { + return userId; + } + + public List<Double> getScores() { + return scores; + } + + public Double getLastScore() { + return scores.getLast(); + } + + public Double getAverageScore() { + Double sum = 0.0; + for (var i : scores) + sum += i; + return scores.isEmpty() ? sum : sum / scores.size(); + } + + public void setScores(List<Double> scores) { + this.scores = scores; + } + } + + // MUST only be called during deserialization!! + @JsonCreator + public TreeNode(@JsonProperty("nodeId") String nodeId, @JsonProperty("label") String label, @JsonProperty("content") String content, @JsonProperty("relation") String relation, @JsonProperty("fatherId") String fatherId, @JsonProperty("exercises") List<Exercise> exercises) { + this(nodeId, label, content, relation, tnRepo.findByNodeId(fatherId), exercises, new ArrayList<>()); + } + + public TreeNode(String nodeId, String label, String content, String relation, TreeNode father, List<Exercise> exercises) { + this(nodeId, label, content, relation, father, exercises, new ArrayList<>()); + } + + @Autowired + @PersistenceCreator + public TreeNode(String nodeId, String label, String content, String relation, TreeNode father, List<Exercise> exercises, List<UserScorePair> userScores) { + this.nodeId = nodeId; + this.label = label; + this.content = content; + this.relation = relation; + this.father = father; + this.exercises = exercises; + this.userScores = userScores; + } + + public String getLabel() { + return label; + } + + public String getNodeId() { + return nodeId; + } + + public String getContent() { + return content; + } + + public TreeNode getFather() { + return father; + } + + public String getRelation() { + return relation; + } + + public List<Exercise> getExercises() { + return exercises; + } + + public void setExercises(List<Exercise> exercises) { + this.exercises = exercises; + } + + public List<UserScorePair> getUserScores() { + return userScores; + } + + public UserScorePair getUserScorePair(Long userId, String nodeId) { + var usp = userScores.stream().filter(e -> Objects.equals(e.getUserId(), userId) && e.getNodeId().equals(nodeId)).findFirst(); + return usp.orElse(null); + } + + public void setUserScores(List<UserScorePair> userScores) { + this.userScores = userScores; + } + + public int getLevel() { + var tmp = this; + var level = 1; + while (tmp.father != null) { + tmp = tmp.father; + level++; + } + return level; + } + + public Set<TreeNode> getChildren(List<TreeNode> nodes) { + var children = new HashSet<TreeNode>(); + for (var n : nodes) { + if (n.getFather() == this) { + children.add(n); + } + } + return children; + } +} diff --git a/src/main/java/seu/se/TreeNodeRepository.java b/src/main/java/seu/se/TreeNodeRepository.java new file mode 100644 index 0000000..d7aff83 --- /dev/null +++ b/src/main/java/seu/se/TreeNodeRepository.java @@ -0,0 +1,8 @@ +package seu.se; + +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +public interface TreeNodeRepository extends PagingAndSortingRepository<TreeNode, String>, ListCrudRepository<TreeNode, String> { + TreeNode findByNodeId(String nodeId); +} diff --git a/src/main/java/seu/se/User.java b/src/main/java/seu/se/User.java new file mode 100644 index 0000000..aa33038 --- /dev/null +++ b/src/main/java/seu/se/User.java @@ -0,0 +1,65 @@ +package seu.se; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +@Node +public class User { + @Id + @GeneratedValue + private Long id; + private String name; + @JsonIgnore + private String password; + + @JsonCreator + public User(@JsonProperty("name") String name, @JsonProperty("password") String password) throws UserException { + setName(name); + setPassword(password); + } + + static public class UserException extends Exception { + public UserException() { + } + + public UserException(String message) { + super(message); + } + } + + public String getName() { + return name; + } + + public Long getId() { + return id; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return String.format("User id: %d, name: %s", id, name); + } + + public void setName(String name) throws UserException { + if (name.length() > 22) + throw new UserException("Username too long!"); + this.name = name; + } + + public void setPassword(String password) throws UserException { + if (password.length() < 8) + throw new UserException("Password too short!"); + this.password = password; + } +} + diff --git a/src/main/java/seu/se/UserRepository.java b/src/main/java/seu/se/UserRepository.java new file mode 100644 index 0000000..6e55754 --- /dev/null +++ b/src/main/java/seu/se/UserRepository.java @@ -0,0 +1,11 @@ +package seu.se; + +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +import java.util.List; + +public interface UserRepository extends PagingAndSortingRepository<User, Long>, ListCrudRepository<User, Long> { + List<User> findByName(String name); + User findById(long id); +} |