From d3f77ea51dd72b055307ac259c3caf8091307aa6 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Sun, 15 Dec 2024 00:00:13 +0800 Subject: init --- src/main/java/seu/se/ApiController.java | 370 +++++++++++++++++++++++++++ src/main/java/seu/se/Application.java | 102 ++++++++ src/main/java/seu/se/Exercise.java | 125 +++++++++ src/main/java/seu/se/TreeNode.java | 215 ++++++++++++++++ src/main/java/seu/se/TreeNodeRepository.java | 8 + src/main/java/seu/se/User.java | 65 +++++ src/main/java/seu/se/UserRepository.java | 11 + src/main/resources/application.properties | 9 + src/main/resources/treeNodes.json | 73 ++++++ src/main/resources/users.json | 6 + 10 files changed, 984 insertions(+) create mode 100644 src/main/java/seu/se/ApiController.java create mode 100644 src/main/java/seu/se/Application.java create mode 100644 src/main/java/seu/se/Exercise.java create mode 100644 src/main/java/seu/se/TreeNode.java create mode 100644 src/main/java/seu/se/TreeNodeRepository.java create mode 100644 src/main/java/seu/se/User.java create mode 100644 src/main/java/seu/se/UserRepository.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/treeNodes.json create mode 100644 src/main/resources/users.json (limited to 'src/main') 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(); + 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 nodes; + + public RecursiveHelper(List 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 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 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 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 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 { + public OptionPairSerializer() { + this(null); + } + + public OptionPairSerializer(Class 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 options, @JsonProperty("answer") String answer) { + this.questionId = questionId; + this.question = question; + this.options = new LinkedList(); + 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 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 getOptions() { + return options; + } + + public void setOptions(List 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 exercises; + @JsonIgnore + private List 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 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 { + public UserScoreSerializer() { + this(null); + } + + public UserScoreSerializer(Class 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 scores) { + this(String.format("%s.%d", nodeId, userId), nodeId, userId, scores); + } + + @Autowired + @PersistenceCreator + public UserScorePair(String id, String nodeId, Long userId, List 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 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 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 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 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 exercises, List 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 getExercises() { + return exercises; + } + + public void setExercises(List exercises) { + this.exercises = exercises; + } + + public List 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 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 getChildren(List nodes) { + var children = new HashSet(); + 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, ListCrudRepository { + 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, ListCrudRepository { + List findByName(String name); + User findById(long id); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..1c74f98 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,9 @@ +spring.application.name=Neo4j SpringBoot Application + +springdoc.swagger-ui.path=/18f4f672-f9f4-4e8f-92ec-935e514be8ae/swagger-ui +swagger-ui.operationsSorter=method + +spring.neo4j.uri=bolt://localhost:7687 +spring.data.neo4j.database = neo4j +spring.neo4j.authentication.username=neo4j +spring.neo4j.authentication.password=neo4j-admin \ No newline at end of file diff --git a/src/main/resources/treeNodes.json b/src/main/resources/treeNodes.json new file mode 100644 index 0000000..d1fdb0a --- /dev/null +++ b/src/main/resources/treeNodes.json @@ -0,0 +1,73 @@ +[ + { + "nodeId": "node1", + "label": "Node 1", + "content": "Content node 1", + "relation": "", + "exercises": [ + { + "questionId": "question1.1", + "question": "Test Question 1", + "options": { + "A": "Node 1 Choice A", + "C": "Node 2 Choice C" + }, + "answer": "A" + } + ] + }, + { + "nodeId": "node2", + "label": "Node 2", + "content": "Content node 2", + "relation": "Son", + "fatherId": "node1", + "exercises": [ + { + "questionId": "question2.1", + "question": "Test Question 2", + "options": { + "A": "Node 2 Choice A", + "B": "Node 2 Choice B" + }, + "answer": "A" + } + ] + }, + { + "nodeId": "node3", + "label": "Node 3", + "content": "Content node 3", + "relation": "Sonson", + "fatherId": "node2", + "exercises": [ + { + "questionId": "question3.1", + "question": "Test Question 3", + "options": { + "A": "Node 3 Choice A", + "D": "Node 3 Choice D" + }, + "answer": "A" + } + ] + }, + { + "nodeId": "node4", + "label": "Node 4", + "content": "Content node 4", + "relation": "Sonsonson", + "fatherId": "node2", + "exercises": [ + { + "questionId": "question4.1", + "question": "Test Question 4", + "options": { + "A": "Node 4 Choice A", + "B": "Node 4 Choice B" + }, + "answer": "A" + } + ] + } +] diff --git a/src/main/resources/users.json b/src/main/resources/users.json new file mode 100644 index 0000000..033ed5b --- /dev/null +++ b/src/main/resources/users.json @@ -0,0 +1,6 @@ +[ + { + "name": "Jason", + "password": "suckless" + } +] -- cgit v1.2.3