diff --git a/pom.xml b/pom.xml index 46b5f08..ea04300 100644 --- a/pom.xml +++ b/pom.xml @@ -26,11 +26,20 @@ com.h2database h2 + org.springframework.boot spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + org.projectlombok lombok diff --git a/src/main/java/com/opinionowl/opinionowl/controllers/APIController.java b/src/main/java/com/opinionowl/opinionowl/controllers/APIController.java new file mode 100644 index 0000000..cb41639 --- /dev/null +++ b/src/main/java/com/opinionowl/opinionowl/controllers/APIController.java @@ -0,0 +1,116 @@ +package com.opinionowl.opinionowl.controllers; +import com.fasterxml.jackson.databind.ObjectMapper; // You might need to import this class + +import com.opinionowl.opinionowl.models.*; +import com.opinionowl.opinionowl.repos.SurveyRepository; +import com.opinionowl.opinionowl.repos.UserRepository; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.*; + +/** + * Version 1 of the API layer for Opinion Owl + */ +@RestController +@RequestMapping("/api/v1") +@NoArgsConstructor +public class APIController { + + @Autowired + SurveyRepository surveyRepo; + + + @Autowired + private UserRepository userRepository; + + + /** + *

Api call to handle the survey answers by a user.

+ *
+ * Api route: api/v1/postSurveyResponses + * @param response HttpServletResponse server side response + * @throws IOException + */ + @PostMapping("/postSurveyResponses") + public void postSurveyResponses(HttpServletResponse response) throws IOException { + // handle save of survey data + // redirect to home + response.sendRedirect("/"); + } + + /** + *

API Call to post a generated survey by the user. A survey generated JSON is required from the client

+ *
+ * Example of a JSON: + *
+     * json = {
+     *     title: "title",
+     *     textQuestions: ["question 1", "question 2"],
+     *     radioQuestions: {
+     *         "question 1": ["radio 1", "radio 2"],
+     *         "question 2": ["radio 1", "radio 2", "radio 3"]
+     *     },
+     *     numericRanges: {
+     *         "question 1": [1, 11],
+     *         "question 2": [1, 5]
+     *     }
+     * }
+     * 
+ * @param request HttpServletRequest request from the client + * @return 200 if api was a success + * @throws IOException + */ + @PostMapping("/createSurvey") + public int createSurvey(HttpServletRequest request) throws IOException { + System.out.println("createSurvey() API"); + // read the json sent by the client + BufferedReader reader = request.getReader(); + // create a string format of the json from the reader + StringBuilder jsonBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + jsonBuilder.append(line); + } + String jsonData = jsonBuilder.toString(); + System.out.println("JSONDATA: " + jsonData); + // Parse the JSON data using Jackson ObjectMapper + + //create the objects as java objects + ObjectMapper objectMapper = new ObjectMapper(); + HashMap surveyData = objectMapper.readValue(jsonData, new TypeReference>() {}); + // Extract specific data from the parsed JSON + String title = (String) surveyData.get("title"); + List textQuestions = (List) surveyData.get("textQuestions"); + HashMap> radioQuestions = (HashMap>) surveyData.get("radioQuestions"); + HashMap> numericRanges = (HashMap>) surveyData.get("numericRanges"); + + AppUser user = new AppUser("username", "password"); + userRepository.save(user); + Survey survey = new Survey(user, title); + user.addSurvey(survey); + // add all the question types to the survey + for (String questionTitle : textQuestions) { + survey.addQuestion(new LongAnswerQuestion(survey, questionTitle, 50)); + } + + for (String questionTitle : radioQuestions.keySet()) { + String[] radioQuestionsArr = new String[radioQuestions.get(questionTitle).size()]; + survey.addQuestion(new RadioChoiceQuestion(survey, questionTitle, radioQuestions.get(questionTitle).toArray(radioQuestionsArr))); + } + + for (String questionTitle : numericRanges.keySet()) { + List ranges = numericRanges.get(questionTitle); + survey.addQuestion(new RangeQuestion(survey, questionTitle, ranges.get(0), ranges.get(1), 1)); + } + surveyRepo.save(survey); + System.out.println("survey generated\n\n" + survey); + return 200; + } +} diff --git a/src/main/java/com/opinionowl/opinionowl/controllers/PageController.java b/src/main/java/com/opinionowl/opinionowl/controllers/PageController.java new file mode 100644 index 0000000..dec9210 --- /dev/null +++ b/src/main/java/com/opinionowl/opinionowl/controllers/PageController.java @@ -0,0 +1,103 @@ +package com.opinionowl.opinionowl.controllers; + +import com.opinionowl.opinionowl.models.*; +import com.opinionowl.opinionowl.repos.SurveyRepository; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +/** + * Route controller for Opinion Owl pages + */ +@Controller +@NoArgsConstructor +public class PageController { + + @Autowired + SurveyRepository surveyRepo; + + /** + *

Home route that gets all the surveys in the database, sends it to the model and directs the user to the home page

+ * @param model Model, the client Model + * @return String, the html template + */ + @GetMapping("/") + public String getHomePage(Model model) { + List surveys = surveyRepo.findAll(); + model.addAttribute("surveys", surveys); + return "index"; + } + + /** + *

Route for the create survey page

+ * @param model Model, the client Model + * @return String ,the html template + */ + @GetMapping("/createSurvey") + public String getCreateSurveyPage(Model model) { + return "createSurvey"; + } + + /** + *

Route to direct the client to the answer survey page, given a survey id to pass the Survey object to the Model

+ *
+ * Example call: /answerSurvey?surveyId=1 + * @param surveyId Long, the ID associated with a survey + * @param model Model, the client Model + * @return String, the html template + */ + @GetMapping("/answerSurvey") + public String getAnswerSurveyPage(@RequestParam(value = "surveyId") Long surveyId, Model model) { + // find the survey by id + Optional surveyO = surveyRepo.findById(surveyId); + if (surveyO.isPresent()) { + // was able to obtain a survey from the database by id, and grab it from the Optional Object + Survey survey = surveyO.get(); + System.out.println("Survey found:"); + System.out.println(survey); + // cast the order of the questions to the associtate subclass they belong to + // Cast in hashmaps as + List q = survey.getQuestions(); + HashMap longAnswerQuestions = new HashMap<>(); + HashMap radioChoiceQuestions = new HashMap<>(); + HashMap rangeQuestionQuestions = new HashMap<>(); + int numQuestions = q.size(); + String title = survey.getTitle(); + for (int i = 0; i { // Essentially performs SELECT * FROM survey List findAll(); + + Survey findById(long Id); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..0e621e4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ - +spring.thymeleaf.prefix=classpath:/templates/ +spring.thymeleaf.suffix=.html \ No newline at end of file diff --git a/src/main/resources/static/images/owl.png b/src/main/resources/static/images/owl.png new file mode 100644 index 0000000..b2135ad Binary files /dev/null and b/src/main/resources/static/images/owl.png differ diff --git a/src/main/resources/static/scripts/createSurvey.js b/src/main/resources/static/scripts/createSurvey.js new file mode 100644 index 0000000..c78af67 --- /dev/null +++ b/src/main/resources/static/scripts/createSurvey.js @@ -0,0 +1,167 @@ +let counter = 0; +let numOfQuestions = 0; + +const formTitle = $(".form-title"); +const survey = $("#survey"); +const addTextQuestion = $("#add-text"); +const addRadioChoice = $("#add-radio-choice"); +const addNumericRange = $("#add-numeric-range"); +const submitButton = $("#create-survey"); +submitButton.attr("disabled", true); +const generateUniqueID = () => { + counter++; + return `unique-${counter}-${Math.floor(Math.random() * 1000)}`; +}; + +/** + * + * @param {string} radioQuestionContainer + * @param {string} uniqueName + */ +const addMoreRadioOptions = (radioQuestionContainer, uniqueName) => { + const uniqueId = generateUniqueID(); + $(radioQuestionContainer).append(` +
+ + + `); +}; + +/** + * + * @param {string} tableRowId + */ +const removeTableRow = (tableRowId) => { + $(tableRowId).remove(); + decrementNumOfQuestion(); +}; + +const incrementNumOfQuestions = () => { + numOfQuestions++; + submitButton.removeAttr('disabled'); +}; + +const decrementNumOfQuestion = () => { + numOfQuestions--; + if (numOfQuestions === 0){ + submitButton.attr("disabled", true); + } +}; + +addTextQuestion.click((e) => { + e.preventDefault(); + const rowId = generateUniqueID(); + const question = ` + + + + + + +
+ + + + `; + survey.append(question); + incrementNumOfQuestions(); +}); + +addRadioChoice.click((e) => { + e.preventDefault(); + const rowId = generateUniqueID(); + const radioQuestionContainer = generateUniqueID(); + const uniqueName = generateUniqueID(); + const question = ` + + + + + +
+ +
+ + +
+ + + + `; + survey.append(question); + incrementNumOfQuestions(); +}); + +addNumericRange.click((e) => { + e.preventDefault(); + const rowId = generateUniqueID(); + const question = ` + + + + + + +
+ 0 + + 11 + + + `; + survey.append(question); + incrementNumOfQuestions(); +}); + +submitButton.click((e) => { + e.preventDefault(); + const dataDictionary = {}; + dataDictionary["radioQuestions"] = {}; + dataDictionary["numericRanges"] = {}; + dataDictionary["title"] = formTitle.text(); + // Iterate over table rows with the class 'text-questions' + const textQuestions = [] + $('.text-questions label').each(function () { + textQuestions.push($(this).text()) + }).get(); + dataDictionary["textQuestions"] = textQuestions; + + $('.radio-questions').each(function() { + const title = $(this).find('.title').text(); + const radioQuestions = []; + const radioQuestionContainer = $(this).find('div'); + $(radioQuestionContainer).find('label:not(.title)').each(function () { + radioQuestions.push($(this).text()); + }); + dataDictionary["radioQuestions"][title] = radioQuestions; + }); + + $('.numeric-questions').each(function() { + const title = $(this).find(".title").text(); + const ranges = []; + $(this).find("span").each(function() { + ranges.push(parseInt($(this).text())); + }) + dataDictionary["numericRanges"][title] = ranges; + }); + + console.log(dataDictionary); + + // send post using ajax + const dataJson = JSON.stringify(dataDictionary); + $.ajax({ + type: $("#survey-container").attr("method"), + url: '/api/v1/createSurvey', + data: dataJson, + contentType: 'application/json', + success: function(res) { + // success handling + console.log('Survey created successfully'); + if (res === 200) window.location.href = "/"; + }, + error: function(xhr, status, error) { + // error handling + console.error('Error creating survey:', error); + } + }); +}); \ No newline at end of file diff --git a/src/main/resources/templates/answerSurvey.html b/src/main/resources/templates/answerSurvey.html new file mode 100644 index 0000000..98e9004 --- /dev/null +++ b/src/main/resources/templates/answerSurvey.html @@ -0,0 +1,37 @@ + + + + OpinionOwl | Answer Survey + + + + +
+

Survey#:

+
+
+
+ +
+ +
+
+ +
+ + +
+
+
+ +
+ + + +
+
+ +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/createSurvey.html b/src/main/resources/templates/createSurvey.html new file mode 100644 index 0000000..ff98185 --- /dev/null +++ b/src/main/resources/templates/createSurvey.html @@ -0,0 +1,29 @@ + + + + OpinionOwl | Create Survey + + + + +
+

Create a Survey

+
+ + + +
+ +
+

Form Title

+
+ + +
+ +
+
+
+ + + diff --git a/src/main/resources/templates/fragments/footer.html b/src/main/resources/templates/fragments/footer.html new file mode 100644 index 0000000..28d587d --- /dev/null +++ b/src/main/resources/templates/fragments/footer.html @@ -0,0 +1 @@ +

© 2023 OpinionOwl. All rights reserved.

diff --git a/src/main/resources/templates/fragments/header.html b/src/main/resources/templates/fragments/header.html new file mode 100644 index 0000000..abcdf23 --- /dev/null +++ b/src/main/resources/templates/fragments/header.html @@ -0,0 +1,6 @@ +
+ +
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000..8eef2f2 --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,33 @@ + + + + OpinionOwl + + + + +
+

Welcome To OpinionOwl

+

+ The ultimate platform for both survey creators and respondents. If you're + a survey creator - a fellow owl companion, our user-friendly interface + will allow you to craft surveys with any topic your choosing. If you are a + survey respondents - either an owl companion or an unknown owl, you are + free to share your thoughts and opinions on the topics created by other + owl companions. +

+ Create a survey +
+

Surveys Available

+ +
+
+ + diff --git a/src/main/resources/templates/viewResponse.html b/src/main/resources/templates/viewResponse.html new file mode 100644 index 0000000..e26f3ad --- /dev/null +++ b/src/main/resources/templates/viewResponse.html @@ -0,0 +1,11 @@ + + + + OpinionOwl | Responses + + + + +

View Response

+ + \ No newline at end of file