Author SHA1 Message Date
jieun ed30cf85dc Fix relationship dropdown pagination 2026-10-20 19:44:08 +09:00
jieun 1d74d8f04b Generate question bank domain model from JDL 2026-10-20 18:46:29 +09:00
160 changed files with 26900 additions and 2 deletions

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
{
"annotations": {
"changelogDate": "20261020093324"
},
"applications": ["*"],
"dto": "mapstruct",
"fields": [
{
"fieldName": "optionKey",
"fieldType": "OptionKey",
"fieldValidateRules": ["required"],
"fieldValues": "A,B,C,D"
},
{
"fieldName": "content",
"fieldType": "String",
"fieldValidateRules": ["required", "maxlength"],
"fieldValidateRulesMaxlength": "500"
},
{
"fieldName": "displayOrder",
"fieldType": "Integer",
"fieldValidateRules": ["required", "min", "max"],
"fieldValidateRulesMax": "4",
"fieldValidateRulesMin": "1"
},
{
"fieldName": "correct",
"fieldType": "Boolean",
"fieldValidateRules": ["required"]
}
],
"name": "Choice",
"relationships": [
{
"otherEntityName": "question",
"otherEntityRelationshipName": "choice",
"relationshipName": "question",
"relationshipSide": "right",
"relationshipType": "many-to-one",
"relationshipValidateRules": "required"
}
],
"searchEngine": "no",
"service": "serviceClass"
}
+62
View File
@@ -0,0 +1,62 @@
{
"annotations": {
"changelogDate": "20261020093326"
},
"applications": ["*"],
"dto": "mapstruct",
"fields": [
{
"fieldName": "questionTextSnapshot",
"fieldType": "String",
"fieldValidateRules": ["required", "maxlength"],
"fieldValidateRulesMaxlength": "2000"
},
{
"fieldName": "explanationSnapshot",
"fieldType": "String",
"fieldValidateRules": ["maxlength"],
"fieldValidateRulesMaxlength": "2000"
},
{
"fieldName": "selectedOption",
"fieldType": "OptionKey",
"fieldValues": "A,B,C,D"
},
{
"fieldName": "selectedContentSnapshot",
"fieldType": "String",
"fieldValidateRules": ["maxlength"],
"fieldValidateRulesMaxlength": "500"
},
{
"fieldName": "correct",
"fieldType": "Boolean"
},
{
"fieldName": "awardedPoints",
"fieldType": "Integer",
"fieldValidateRules": ["min"],
"fieldValidateRulesMin": "0"
}
],
"name": "ExamAnswer",
"relationships": [
{
"otherEntityName": "question",
"relationshipName": "question",
"relationshipSide": "left",
"relationshipType": "many-to-one",
"relationshipValidateRules": "required"
},
{
"otherEntityName": "examAttempt",
"otherEntityRelationshipName": "answer",
"relationshipName": "attempt",
"relationshipSide": "right",
"relationshipType": "many-to-one",
"relationshipValidateRules": "required"
}
],
"searchEngine": "no",
"service": "serviceClass"
}
+60
View File
@@ -0,0 +1,60 @@
{
"annotations": {
"changelogDate": "20261020093325"
},
"applications": ["*"],
"dto": "mapstruct",
"fields": [
{
"fieldName": "startedAt",
"fieldType": "Instant",
"fieldValidateRules": ["required"]
},
{
"fieldName": "submittedAt",
"fieldType": "Instant"
},
{
"fieldName": "status",
"fieldType": "AttemptStatus",
"fieldValidateRules": ["required"],
"fieldValues": "IN_PROGRESS,SUBMITTED"
},
{
"fieldName": "score",
"fieldType": "Integer",
"fieldValidateRules": ["min"],
"fieldValidateRulesMin": "0"
}
],
"name": "ExamAttempt",
"pagination": "pagination",
"relationships": [
{
"otherEntityName": "examAnswer",
"otherEntityRelationshipName": "attempt",
"relationshipName": "answer",
"relationshipSide": "left",
"relationshipType": "one-to-many"
},
{
"otherEntityField": "name",
"otherEntityName": "subject",
"relationshipName": "subject",
"relationshipSide": "left",
"relationshipType": "many-to-one",
"relationshipValidateRules": "required"
},
{
"otherEntityField": "login",
"otherEntityName": "user",
"relationshipName": "user",
"relationshipSide": "left",
"relationshipType": "many-to-one",
"relationshipValidateRules": "required",
"relationshipWithBuiltInEntity": true
}
],
"searchEngine": "no",
"service": "serviceClass"
}
+48
View File
@@ -0,0 +1,48 @@
{
"annotations": {
"changelogDate": "20261020093323"
},
"applications": ["*"],
"dto": "mapstruct",
"fields": [
{
"fieldName": "prompt",
"fieldType": "String",
"fieldValidateRules": ["required", "minlength", "maxlength"],
"fieldValidateRulesMaxlength": "2000",
"fieldValidateRulesMinlength": "5"
},
{
"fieldName": "explanation",
"fieldType": "String",
"fieldValidateRules": ["maxlength"],
"fieldValidateRulesMaxlength": "2000"
},
{
"fieldName": "active",
"fieldType": "Boolean",
"fieldValidateRules": ["required"]
}
],
"name": "Question",
"pagination": "pagination",
"relationships": [
{
"otherEntityName": "choice",
"otherEntityRelationshipName": "question",
"relationshipName": "choice",
"relationshipSide": "left",
"relationshipType": "one-to-many"
},
{
"otherEntityField": "name",
"otherEntityName": "subject",
"relationshipName": "subject",
"relationshipSide": "left",
"relationshipType": "many-to-one",
"relationshipValidateRules": "required"
}
],
"searchEngine": "no",
"service": "serviceClass"
}
+32
View File
@@ -0,0 +1,32 @@
{
"annotations": {
"changelogDate": "20261020093322"
},
"applications": ["*"],
"dto": "mapstruct",
"fields": [
{
"fieldName": "name",
"fieldType": "String",
"fieldValidateRules": ["required", "minlength", "maxlength"],
"fieldValidateRulesMaxlength": "100",
"fieldValidateRulesMinlength": "2"
},
{
"fieldName": "description",
"fieldType": "String",
"fieldValidateRules": ["maxlength"],
"fieldValidateRulesMaxlength": "1000"
},
{
"fieldName": "active",
"fieldType": "Boolean",
"fieldValidateRules": ["required"]
}
],
"name": "Subject",
"pagination": "pagination",
"relationships": [],
"searchEngine": "no",
"service": "serviceClass"
}
+2 -1
View File
@@ -11,10 +11,11 @@
"databaseType": "sql", "databaseType": "sql",
"defaultCommand": "app", "defaultCommand": "app",
"devDatabaseType": "postgresql", "devDatabaseType": "postgresql",
"entities": [], "entities": ["Subject", "Question", "Choice", "ExamAttempt", "ExamAnswer"],
"jhipsterVersion": "9.3.0", "jhipsterVersion": "9.3.0",
"jwtSecretKey": "MzY1ZDU0MDM3M2Y5YWRiMDYxYjBmYzZlOTMxMWFiNmYyMDEyZTUzZjYxOWFjNjhlNjU2NzcxYjkwY2Y0MGIwOGUyNTc5NWQyOGY3OGJkMjExZjhkYzU3ZGU4ZDc0ZmZiMTYwMzY0NGM4ZWZmODRhZTZjNzBlNjk5MWJkYjM5YjI=", "jwtSecretKey": "MzY1ZDU0MDM3M2Y5YWRiMDYxYjBmYzZlOTMxMWFiNmYyMDEyZTUzZjYxOWFjNjhlNjU2NzcxYjkwY2Y0MGIwOGUyNTc5NWQyOGY3OGJkMjExZjhkYzU3ZGU4ZDc0ZmZiMTYwMzY0NGM4ZWZmODRhZTZjNzBlNjk5MWJkYjM5YjI=",
"languages": ["en"], "languages": ["en"],
"lastLiquibaseTimestamp": 1792488806000,
"messageBroker": "no", "messageBroker": "no",
"nativeLanguage": "en", "nativeLanguage": "en",
"packageName": "com.example.questionbank", "packageName": "com.example.questionbank",
+12613
View File
File diff suppressed because it is too large. Load diff
+62
View File
@@ -0,0 +1,62 @@
enum OptionKey {
A,
B,
C,
D
}
enum AttemptStatus {
IN_PROGRESS,
SUBMITTED
}
entity Subject {
name String required minlength(2) maxlength(100),
description String maxlength(1000),
active Boolean required
}
entity Question {
prompt String required minlength(5) maxlength(2000),
explanation String maxlength(2000),
active Boolean required
}
entity Choice {
optionKey OptionKey required,
content String required maxlength(500),
displayOrder Integer required min(1) max(4),
correct Boolean required
}
entity ExamAttempt {
startedAt Instant required,
submittedAt Instant,
status AttemptStatus required,
score Integer min(0)
}
entity ExamAnswer {
questionTextSnapshot String required maxlength(2000),
explanationSnapshot String maxlength(2000),
selectedOption OptionKey,
selectedContentSnapshot String maxlength(500),
correct Boolean,
awardedPoints Integer min(0)
}
relationship OneToMany {
Question{choice} to Choice{question required}
ExamAttempt{answer} to ExamAnswer{attempt required}
}
relationship ManyToOne {
Question{subject(name) required} to Subject
ExamAttempt{subject(name) required} to Subject
ExamAttempt{user(login) required} to User with builtInEntity
ExamAnswer{question required} to Question
}
dto Subject, Question, Choice, ExamAttempt, ExamAnswer with mapstruct
service Subject, Question, Choice, ExamAttempt, ExamAnswer with serviceClass
paginate Subject, Question, ExamAttempt with pagination
@@ -0,0 +1,162 @@
package com.example.questionbank.domain;
import com.example.questionbank.domain.enumeration.OptionKey;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.io.Serial;
import java.io.Serializable;
/**
* A Choice.
*/
@Entity
@Table(name = "choice")
@SuppressWarnings("common-java:DuplicatedBlocks")
public class Choice implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
@Column(name = "id")
private Long id;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "option_key", nullable = false)
private OptionKey optionKey;
@NotNull
@Size(max = 500)
@Column(name = "content", length = 500, nullable = false)
private String content;
@NotNull
@Min(value = 1)
@Max(value = 4)
@Column(name = "display_order", nullable = false)
private Integer displayOrder;
@NotNull
@Column(name = "correct", nullable = false)
private Boolean correct;
@ManyToOne(optional = false)
@NotNull
@JsonIgnoreProperties(value = { "choices", "subject" }, allowSetters = true)
private Question question;
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return this.id;
}
public Choice id(Long id) {
this.setId(id);
return this;
}
public void setId(Long id) {
this.id = id;
}
public OptionKey getOptionKey() {
return this.optionKey;
}
public Choice optionKey(OptionKey optionKey) {
this.setOptionKey(optionKey);
return this;
}
public void setOptionKey(OptionKey optionKey) {
this.optionKey = optionKey;
}
public String getContent() {
return this.content;
}
public Choice content(String content) {
this.setContent(content);
return this;
}
public void setContent(String content) {
this.content = content;
}
public Integer getDisplayOrder() {
return this.displayOrder;
}
public Choice displayOrder(Integer displayOrder) {
this.setDisplayOrder(displayOrder);
return this;
}
public void setDisplayOrder(Integer displayOrder) {
this.displayOrder = displayOrder;
}
public Boolean getCorrect() {
return this.correct;
}
public Choice correct(Boolean correct) {
this.setCorrect(correct);
return this;
}
public void setCorrect(Boolean correct) {
this.correct = correct;
}
public Question getQuestion() {
return this.question;
}
public void setQuestion(Question question) {
this.question = question;
}
public Choice question(Question question) {
this.setQuestion(question);
return this;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Choice)) {
return false;
}
return getId() != null && getId().equals(((Choice) o).getId());
}
@Override
public int hashCode() {
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
// prettier-ignore
@Override
public String toString() {
return "Choice{" +
"id=" + getId() +
", optionKey='" + getOptionKey() + "'" +
", content='" + getContent() + "'" +
", displayOrder=" + getDisplayOrder() +
", correct='" + getCorrect() + "'" +
"}";
}
}
@@ -0,0 +1,212 @@
package com.example.questionbank.domain;
import com.example.questionbank.domain.enumeration.OptionKey;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.io.Serial;
import java.io.Serializable;
/**
* A ExamAnswer.
*/
@Entity
@Table(name = "exam_answer")
@SuppressWarnings("common-java:DuplicatedBlocks")
public class ExamAnswer implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
@Column(name = "id")
private Long id;
@NotNull
@Size(max = 2000)
@Column(name = "question_text_snapshot", length = 2000, nullable = false)
private String questionTextSnapshot;
@Size(max = 2000)
@Column(name = "explanation_snapshot", length = 2000)
private String explanationSnapshot;
@Enumerated(EnumType.STRING)
@Column(name = "selected_option")
private OptionKey selectedOption;
@Size(max = 500)
@Column(name = "selected_content_snapshot", length = 500)
private String selectedContentSnapshot;
@Column(name = "correct")
private Boolean correct;
@Min(value = 0)
@Column(name = "awarded_points")
private Integer awardedPoints;
@ManyToOne(optional = false)
@NotNull
@JsonIgnoreProperties(value = { "choices", "subject" }, allowSetters = true)
private Question question;
@ManyToOne(optional = false)
@NotNull
@JsonIgnoreProperties(value = { "answers", "subject", "user" }, allowSetters = true)
private ExamAttempt attempt;
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return this.id;
}
public ExamAnswer id(Long id) {
this.setId(id);
return this;
}
public void setId(Long id) {
this.id = id;
}
public String getQuestionTextSnapshot() {
return this.questionTextSnapshot;
}
public ExamAnswer questionTextSnapshot(String questionTextSnapshot) {
this.setQuestionTextSnapshot(questionTextSnapshot);
return this;
}
public void setQuestionTextSnapshot(String questionTextSnapshot) {
this.questionTextSnapshot = questionTextSnapshot;
}
public String getExplanationSnapshot() {
return this.explanationSnapshot;
}
public ExamAnswer explanationSnapshot(String explanationSnapshot) {
this.setExplanationSnapshot(explanationSnapshot);
return this;
}
public void setExplanationSnapshot(String explanationSnapshot) {
this.explanationSnapshot = explanationSnapshot;
}
public OptionKey getSelectedOption() {
return this.selectedOption;
}
public ExamAnswer selectedOption(OptionKey selectedOption) {
this.setSelectedOption(selectedOption);
return this;
}
public void setSelectedOption(OptionKey selectedOption) {
this.selectedOption = selectedOption;
}
public String getSelectedContentSnapshot() {
return this.selectedContentSnapshot;
}
public ExamAnswer selectedContentSnapshot(String selectedContentSnapshot) {
this.setSelectedContentSnapshot(selectedContentSnapshot);
return this;
}
public void setSelectedContentSnapshot(String selectedContentSnapshot) {
this.selectedContentSnapshot = selectedContentSnapshot;
}
public Boolean getCorrect() {
return this.correct;
}
public ExamAnswer correct(Boolean correct) {
this.setCorrect(correct);
return this;
}
public void setCorrect(Boolean correct) {
this.correct = correct;
}
public Integer getAwardedPoints() {
return this.awardedPoints;
}
public ExamAnswer awardedPoints(Integer awardedPoints) {
this.setAwardedPoints(awardedPoints);
return this;
}
public void setAwardedPoints(Integer awardedPoints) {
this.awardedPoints = awardedPoints;
}
public Question getQuestion() {
return this.question;
}
public void setQuestion(Question question) {
this.question = question;
}
public ExamAnswer question(Question question) {
this.setQuestion(question);
return this;
}
public ExamAttempt getAttempt() {
return this.attempt;
}
public void setAttempt(ExamAttempt examAttempt) {
this.attempt = examAttempt;
}
public ExamAnswer attempt(ExamAttempt examAttempt) {
this.setAttempt(examAttempt);
return this;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ExamAnswer)) {
return false;
}
return getId() != null && getId().equals(((ExamAnswer) o).getId());
}
@Override
public int hashCode() {
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
// prettier-ignore
@Override
public String toString() {
return "ExamAnswer{" +
"id=" + getId() +
", questionTextSnapshot='" + getQuestionTextSnapshot() + "'" +
", explanationSnapshot='" + getExplanationSnapshot() + "'" +
", selectedOption='" + getSelectedOption() + "'" +
", selectedContentSnapshot='" + getSelectedContentSnapshot() + "'" +
", correct='" + getCorrect() + "'" +
", awardedPoints=" + getAwardedPoints() +
"}";
}
}
@@ -0,0 +1,212 @@
package com.example.questionbank.domain;
import com.example.questionbank.domain.enumeration.AttemptStatus;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
/**
* A ExamAttempt.
*/
@Entity
@Table(name = "exam_attempt")
@SuppressWarnings("common-java:DuplicatedBlocks")
public class ExamAttempt implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
@Column(name = "id")
private Long id;
@NotNull
@Column(name = "started_at", nullable = false)
private Instant startedAt;
@Column(name = "submitted_at")
private Instant submittedAt;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
private AttemptStatus status;
@Min(value = 0)
@Column(name = "score")
private Integer score;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "attempt")
@JsonIgnoreProperties(value = { "question", "attempt" }, allowSetters = true)
private Set<ExamAnswer> answers = new HashSet<>();
@ManyToOne(optional = false)
@NotNull
private Subject subject;
@ManyToOne(optional = false)
@NotNull
private User user;
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return this.id;
}
public ExamAttempt id(Long id) {
this.setId(id);
return this;
}
public void setId(Long id) {
this.id = id;
}
public Instant getStartedAt() {
return this.startedAt;
}
public ExamAttempt startedAt(Instant startedAt) {
this.setStartedAt(startedAt);
return this;
}
public void setStartedAt(Instant startedAt) {
this.startedAt = startedAt;
}
public Instant getSubmittedAt() {
return this.submittedAt;
}
public ExamAttempt submittedAt(Instant submittedAt) {
this.setSubmittedAt(submittedAt);
return this;
}
public void setSubmittedAt(Instant submittedAt) {
this.submittedAt = submittedAt;
}
public AttemptStatus getStatus() {
return this.status;
}
public ExamAttempt status(AttemptStatus status) {
this.setStatus(status);
return this;
}
public void setStatus(AttemptStatus status) {
this.status = status;
}
public Integer getScore() {
return this.score;
}
public ExamAttempt score(Integer score) {
this.setScore(score);
return this;
}
public void setScore(Integer score) {
this.score = score;
}
public Set<ExamAnswer> getAnswers() {
return this.answers;
}
public void setAnswers(Set<ExamAnswer> examAnswers) {
if (this.answers != null) {
this.answers.forEach(i -> i.setAttempt(null));
}
if (examAnswers != null) {
examAnswers.forEach(i -> i.setAttempt(this));
}
this.answers = examAnswers;
}
public ExamAttempt answers(Set<ExamAnswer> examAnswers) {
this.setAnswers(examAnswers);
return this;
}
public ExamAttempt addAnswer(ExamAnswer examAnswer) {
this.answers.add(examAnswer);
examAnswer.setAttempt(this);
return this;
}
public ExamAttempt removeAnswer(ExamAnswer examAnswer) {
this.answers.remove(examAnswer);
examAnswer.setAttempt(null);
return this;
}
public Subject getSubject() {
return this.subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public ExamAttempt subject(Subject subject) {
this.setSubject(subject);
return this;
}
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
public ExamAttempt user(User user) {
this.setUser(user);
return this;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ExamAttempt)) {
return false;
}
return getId() != null && getId().equals(((ExamAttempt) o).getId());
}
@Override
public int hashCode() {
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
// prettier-ignore
@Override
public String toString() {
return "ExamAttempt{" +
"id=" + getId() +
", startedAt='" + getStartedAt() + "'" +
", submittedAt='" + getSubmittedAt() + "'" +
", status='" + getStatus() + "'" +
", score=" + getScore() +
"}";
}
}
@@ -0,0 +1,176 @@
package com.example.questionbank.domain;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* A Question.
*/
@Entity
@Table(name = "question")
@SuppressWarnings("common-java:DuplicatedBlocks")
public class Question implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
@Column(name = "id")
private Long id;
@NotNull
@Size(min = 5, max = 2000)
@Column(name = "prompt", length = 2000, nullable = false)
private String prompt;
@Size(max = 2000)
@Column(name = "explanation", length = 2000)
private String explanation;
@NotNull
@Column(name = "active", nullable = false)
private Boolean active;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "question")
@JsonIgnoreProperties(value = { "question" }, allowSetters = true)
private Set<Choice> choices = new HashSet<>();
@ManyToOne(optional = false)
@NotNull
private Subject subject;
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return this.id;
}
public Question id(Long id) {
this.setId(id);
return this;
}
public void setId(Long id) {
this.id = id;
}
public String getPrompt() {
return this.prompt;
}
public Question prompt(String prompt) {
this.setPrompt(prompt);
return this;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
}
public String getExplanation() {
return this.explanation;
}
public Question explanation(String explanation) {
this.setExplanation(explanation);
return this;
}
public void setExplanation(String explanation) {
this.explanation = explanation;
}
public Boolean getActive() {
return this.active;
}
public Question active(Boolean active) {
this.setActive(active);
return this;
}
public void setActive(Boolean active) {
this.active = active;
}
public Set<Choice> getChoices() {
return this.choices;
}
public void setChoices(Set<Choice> choices) {
if (this.choices != null) {
this.choices.forEach(i -> i.setQuestion(null));
}
if (choices != null) {
choices.forEach(i -> i.setQuestion(this));
}
this.choices = choices;
}
public Question choices(Set<Choice> choices) {
this.setChoices(choices);
return this;
}
public Question addChoice(Choice choice) {
this.choices.add(choice);
choice.setQuestion(this);
return this;
}
public Question removeChoice(Choice choice) {
this.choices.remove(choice);
choice.setQuestion(null);
return this;
}
public Subject getSubject() {
return this.subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public Question subject(Subject subject) {
this.setSubject(subject);
return this;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Question)) {
return false;
}
return getId() != null && getId().equals(((Question) o).getId());
}
@Override
public int hashCode() {
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
// prettier-ignore
@Override
public String toString() {
return "Question{" +
"id=" + getId() +
", prompt='" + getPrompt() + "'" +
", explanation='" + getExplanation() + "'" +
", active='" + getActive() + "'" +
"}";
}
}
@@ -0,0 +1,121 @@
package com.example.questionbank.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.io.Serial;
import java.io.Serializable;
/**
* A Subject.
*/
@Entity
@Table(name = "subject")
@SuppressWarnings("common-java:DuplicatedBlocks")
public class Subject implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
@Column(name = "id")
private Long id;
@NotNull
@Size(min = 2, max = 100)
@Column(name = "name", length = 100, nullable = false)
private String name;
@Size(max = 1000)
@Column(name = "description", length = 1000)
private String description;
@NotNull
@Column(name = "active", nullable = false)
private Boolean active;
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return this.id;
}
public Subject id(Long id) {
this.setId(id);
return this;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public Subject name(String name) {
this.setName(name);
return this;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return this.description;
}
public Subject description(String description) {
this.setDescription(description);
return this;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getActive() {
return this.active;
}
public Subject active(Boolean active) {
this.setActive(active);
return this;
}
public void setActive(Boolean active) {
this.active = active;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Subject)) {
return false;
}
return getId() != null && getId().equals(((Subject) o).getId());
}
@Override
public int hashCode() {
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
// prettier-ignore
@Override
public String toString() {
return "Subject{" +
"id=" + getId() +
", name='" + getName() + "'" +
", description='" + getDescription() + "'" +
", active='" + getActive() + "'" +
"}";
}
}
@@ -0,0 +1,9 @@
package com.example.questionbank.domain.enumeration;
/**
* The AttemptStatus enumeration.
*/
public enum AttemptStatus {
IN_PROGRESS,
SUBMITTED,
}
@@ -0,0 +1,11 @@
package com.example.questionbank.domain.enumeration;
/**
* The OptionKey enumeration.
*/
public enum OptionKey {
A,
B,
C,
D,
}
@@ -0,0 +1,4 @@
/**
* This package file was generated by JHipster
*/
package com.example.questionbank.domain.enumeration;
@@ -0,0 +1,12 @@
package com.example.questionbank.repository;
import com.example.questionbank.domain.Choice;
import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;
/**
* Spring Data JPA repository for the Choice entity.
*/
@SuppressWarnings("unused")
@Repository
public interface ChoiceRepository extends JpaRepository<Choice, Long> {}
@@ -0,0 +1,12 @@
package com.example.questionbank.repository;
import com.example.questionbank.domain.ExamAnswer;
import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;
/**
* Spring Data JPA repository for the ExamAnswer entity.
*/
@SuppressWarnings("unused")
@Repository
public interface ExamAnswerRepository extends JpaRepository<ExamAnswer, Long> {}
@@ -0,0 +1,45 @@
package com.example.questionbank.repository;
import com.example.questionbank.domain.ExamAttempt;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
/**
* Spring Data JPA repository for the ExamAttempt entity.
*/
@Repository
public interface ExamAttemptRepository extends JpaRepository<ExamAttempt, Long> {
@Query("select examAttempt from ExamAttempt examAttempt where examAttempt.user.login = ?#{authentication.name}")
List<ExamAttempt> findByUserIsCurrentUser();
default Optional<ExamAttempt> findOneWithEagerRelationships(Long id) {
return this.findOneWithToOneRelationships(id);
}
default List<ExamAttempt> findAllWithEagerRelationships() {
return this.findAllWithToOneRelationships();
}
default Page<ExamAttempt> findAllWithEagerRelationships(Pageable pageable) {
return this.findAllWithToOneRelationships(pageable);
}
@Query(
value = "select examAttempt from ExamAttempt examAttempt left join fetch examAttempt.subject left join fetch examAttempt.user",
countQuery = "select count(examAttempt) from ExamAttempt examAttempt"
)
Page<ExamAttempt> findAllWithToOneRelationships(Pageable pageable);
@Query("select examAttempt from ExamAttempt examAttempt left join fetch examAttempt.subject left join fetch examAttempt.user")
List<ExamAttempt> findAllWithToOneRelationships();
@Query(
"select examAttempt from ExamAttempt examAttempt left join fetch examAttempt.subject left join fetch examAttempt.user where examAttempt.id =:id"
)
Optional<ExamAttempt> findOneWithToOneRelationships(@Param("id") Long id);
}
@@ -0,0 +1,40 @@
package com.example.questionbank.repository;
import com.example.questionbank.domain.Question;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
/**
* Spring Data JPA repository for the Question entity.
*/
@Repository
public interface QuestionRepository extends JpaRepository<Question, Long> {
default Optional<Question> findOneWithEagerRelationships(Long id) {
return this.findOneWithToOneRelationships(id);
}
default List<Question> findAllWithEagerRelationships() {
return this.findAllWithToOneRelationships();
}
default Page<Question> findAllWithEagerRelationships(Pageable pageable) {
return this.findAllWithToOneRelationships(pageable);
}
@Query(
value = "select question from Question question left join fetch question.subject",
countQuery = "select count(question) from Question question"
)
Page<Question> findAllWithToOneRelationships(Pageable pageable);
@Query("select question from Question question left join fetch question.subject")
List<Question> findAllWithToOneRelationships();
@Query("select question from Question question left join fetch question.subject where question.id =:id")
Optional<Question> findOneWithToOneRelationships(@Param("id") Long id);
}
@@ -0,0 +1,12 @@
package com.example.questionbank.repository;
import com.example.questionbank.domain.Subject;
import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;
/**
* Spring Data JPA repository for the Subject entity.
*/
@SuppressWarnings("unused")
@Repository
public interface SubjectRepository extends JpaRepository<Subject, Long> {}
@@ -0,0 +1,112 @@
package com.example.questionbank.service;
import com.example.questionbank.domain.Choice;
import com.example.questionbank.repository.ChoiceRepository;
import com.example.questionbank.service.dto.ChoiceDTO;
import com.example.questionbank.service.mapper.ChoiceMapper;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Service Implementation for managing {@link com.example.questionbank.domain.Choice}.
*/
@Service
@Transactional
public class ChoiceService {
private static final Logger LOG = LoggerFactory.getLogger(ChoiceService.class);
private final ChoiceRepository choiceRepository;
private final ChoiceMapper choiceMapper;
public ChoiceService(ChoiceRepository choiceRepository, ChoiceMapper choiceMapper) {
this.choiceRepository = choiceRepository;
this.choiceMapper = choiceMapper;
}
/**
* Save a choice.
*
* @param choiceDTO the entity to save.
* @return the persisted entity.
*/
public ChoiceDTO save(ChoiceDTO choiceDTO) {
LOG.debug("Request to save Choice : {}", choiceDTO);
Choice choice = choiceMapper.toEntity(choiceDTO);
choice = choiceRepository.save(choice);
return choiceMapper.toDto(choice);
}
/**
* Update a choice.
*
* @param choiceDTO the entity to save.
* @return the persisted entity.
*/
public ChoiceDTO update(ChoiceDTO choiceDTO) {
LOG.debug("Request to update Choice : {}", choiceDTO);
Choice choice = choiceMapper.toEntity(choiceDTO);
choice = choiceRepository.save(choice);
return choiceMapper.toDto(choice);
}
/**
* Partially update a choice.
*
* @param choiceDTO the entity to update partially.
* @return the persisted entity.
*/
public Optional<ChoiceDTO> partialUpdate(ChoiceDTO choiceDTO) {
LOG.debug("Request to partially update Choice : {}", choiceDTO);
return choiceRepository
.findById(choiceDTO.getId())
.map(existingChoice -> {
choiceMapper.partialUpdate(existingChoice, choiceDTO);
return existingChoice;
})
.map(choiceRepository::save)
.map(choiceMapper::toDto);
}
/**
* Get all the choices.
*
* @return the list of entities.
*/
@Transactional(readOnly = true)
public List<ChoiceDTO> findAll() {
LOG.debug("Request to get all Choices");
return choiceRepository.findAll().stream().map(choiceMapper::toDto).collect(Collectors.toCollection(LinkedList::new));
}
/**
* Get one choice by id.
*
* @param id the id of the entity.
* @return the entity.
*/
@Transactional(readOnly = true)
public Optional<ChoiceDTO> findOne(Long id) {
LOG.debug("Request to get Choice : {}", id);
return choiceRepository.findById(id).map(choiceMapper::toDto);
}
/**
* Delete the choice by id.
*
* @param id the id of the entity.
*/
public void delete(Long id) {
LOG.debug("Request to delete Choice : {}", id);
choiceRepository.deleteById(id);
}
}
@@ -0,0 +1,112 @@
package com.example.questionbank.service;
import com.example.questionbank.domain.ExamAnswer;
import com.example.questionbank.repository.ExamAnswerRepository;
import com.example.questionbank.service.dto.ExamAnswerDTO;
import com.example.questionbank.service.mapper.ExamAnswerMapper;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Service Implementation for managing {@link com.example.questionbank.domain.ExamAnswer}.
*/
@Service
@Transactional
public class ExamAnswerService {
private static final Logger LOG = LoggerFactory.getLogger(ExamAnswerService.class);
private final ExamAnswerRepository examAnswerRepository;
private final ExamAnswerMapper examAnswerMapper;
public ExamAnswerService(ExamAnswerRepository examAnswerRepository, ExamAnswerMapper examAnswerMapper) {
this.examAnswerRepository = examAnswerRepository;
this.examAnswerMapper = examAnswerMapper;
}
/**
* Save a examAnswer.
*
* @param examAnswerDTO the entity to save.
* @return the persisted entity.
*/
public ExamAnswerDTO save(ExamAnswerDTO examAnswerDTO) {
LOG.debug("Request to save ExamAnswer : {}", examAnswerDTO);
ExamAnswer examAnswer = examAnswerMapper.toEntity(examAnswerDTO);
examAnswer = examAnswerRepository.save(examAnswer);
return examAnswerMapper.toDto(examAnswer);
}
/**
* Update a examAnswer.
*
* @param examAnswerDTO the entity to save.
* @return the persisted entity.
*/
public ExamAnswerDTO update(ExamAnswerDTO examAnswerDTO) {
LOG.debug("Request to update ExamAnswer : {}", examAnswerDTO);
ExamAnswer examAnswer = examAnswerMapper.toEntity(examAnswerDTO);
examAnswer = examAnswerRepository.save(examAnswer);
return examAnswerMapper.toDto(examAnswer);
}
/**
* Partially update a examAnswer.
*
* @param examAnswerDTO the entity to update partially.
* @return the persisted entity.
*/
public Optional<ExamAnswerDTO> partialUpdate(ExamAnswerDTO examAnswerDTO) {
LOG.debug("Request to partially update ExamAnswer : {}", examAnswerDTO);
return examAnswerRepository
.findById(examAnswerDTO.getId())
.map(existingExamAnswer -> {
examAnswerMapper.partialUpdate(existingExamAnswer, examAnswerDTO);
return existingExamAnswer;
})
.map(examAnswerRepository::save)
.map(examAnswerMapper::toDto);
}
/**
* Get all the examAnswers.
*
* @return the list of entities.
*/
@Transactional(readOnly = true)
public List<ExamAnswerDTO> findAll() {
LOG.debug("Request to get all ExamAnswers");
return examAnswerRepository.findAll().stream().map(examAnswerMapper::toDto).collect(Collectors.toCollection(LinkedList::new));
}
/**
* Get one examAnswer by id.
*
* @param id the id of the entity.
* @return the entity.
*/
@Transactional(readOnly = true)
public Optional<ExamAnswerDTO> findOne(Long id) {
LOG.debug("Request to get ExamAnswer : {}", id);
return examAnswerRepository.findById(id).map(examAnswerMapper::toDto);
}
/**
* Delete the examAnswer by id.
*
* @param id the id of the entity.
*/
public void delete(Long id) {
LOG.debug("Request to delete ExamAnswer : {}", id);
examAnswerRepository.deleteById(id);
}
}
@@ -0,0 +1,121 @@
package com.example.questionbank.service;
import com.example.questionbank.domain.ExamAttempt;
import com.example.questionbank.repository.ExamAttemptRepository;
import com.example.questionbank.service.dto.ExamAttemptDTO;
import com.example.questionbank.service.mapper.ExamAttemptMapper;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Service Implementation for managing {@link com.example.questionbank.domain.ExamAttempt}.
*/
@Service
@Transactional
public class ExamAttemptService {
private static final Logger LOG = LoggerFactory.getLogger(ExamAttemptService.class);
private final ExamAttemptRepository examAttemptRepository;
private final ExamAttemptMapper examAttemptMapper;
public ExamAttemptService(ExamAttemptRepository examAttemptRepository, ExamAttemptMapper examAttemptMapper) {
this.examAttemptRepository = examAttemptRepository;
this.examAttemptMapper = examAttemptMapper;
}
/**
* Save a examAttempt.
*
* @param examAttemptDTO the entity to save.
* @return the persisted entity.
*/
public ExamAttemptDTO save(ExamAttemptDTO examAttemptDTO) {
LOG.debug("Request to save ExamAttempt : {}", examAttemptDTO);
ExamAttempt examAttempt = examAttemptMapper.toEntity(examAttemptDTO);
examAttempt = examAttemptRepository.save(examAttempt);
return examAttemptMapper.toDto(examAttempt);
}
/**
* Update a examAttempt.
*
* @param examAttemptDTO the entity to save.
* @return the persisted entity.
*/
public ExamAttemptDTO update(ExamAttemptDTO examAttemptDTO) {
LOG.debug("Request to update ExamAttempt : {}", examAttemptDTO);
ExamAttempt examAttempt = examAttemptMapper.toEntity(examAttemptDTO);
examAttempt = examAttemptRepository.save(examAttempt);
return examAttemptMapper.toDto(examAttempt);
}
/**
* Partially update a examAttempt.
*
* @param examAttemptDTO the entity to update partially.
* @return the persisted entity.
*/
public Optional<ExamAttemptDTO> partialUpdate(ExamAttemptDTO examAttemptDTO) {
LOG.debug("Request to partially update ExamAttempt : {}", examAttemptDTO);
return examAttemptRepository
.findById(examAttemptDTO.getId())
.map(existingExamAttempt -> {
examAttemptMapper.partialUpdate(existingExamAttempt, examAttemptDTO);
return existingExamAttempt;
})
.map(examAttemptRepository::save)
.map(examAttemptMapper::toDto);
}
/**
* Get all the examAttempts.
*
* @param pageable the pagination information.
* @return the list of entities.
*/
@Transactional(readOnly = true)
public Page<ExamAttemptDTO> findAll(Pageable pageable) {
LOG.debug("Request to get all ExamAttempts");
return examAttemptRepository.findAll(pageable).map(examAttemptMapper::toDto);
}
/**
* Get all the examAttempts with eager load of many-to-many relationships.
*
* @return the list of entities.
*/
public Page<ExamAttemptDTO> findAllWithEagerRelationships(Pageable pageable) {
return examAttemptRepository.findAllWithEagerRelationships(pageable).map(examAttemptMapper::toDto);
}
/**
* Get one examAttempt by id.
*
* @param id the id of the entity.
* @return the entity.
*/
@Transactional(readOnly = true)
public Optional<ExamAttemptDTO> findOne(Long id) {
LOG.debug("Request to get ExamAttempt : {}", id);
return examAttemptRepository.findOneWithEagerRelationships(id).map(examAttemptMapper::toDto);
}
/**
* Delete the examAttempt by id.
*
* @param id the id of the entity.
*/
public void delete(Long id) {
LOG.debug("Request to delete ExamAttempt : {}", id);
examAttemptRepository.deleteById(id);
}
}
@@ -0,0 +1,121 @@
package com.example.questionbank.service;
import com.example.questionbank.domain.Question;
import com.example.questionbank.repository.QuestionRepository;
import com.example.questionbank.service.dto.QuestionDTO;
import com.example.questionbank.service.mapper.QuestionMapper;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Service Implementation for managing {@link com.example.questionbank.domain.Question}.
*/
@Service
@Transactional
public class QuestionService {
private static final Logger LOG = LoggerFactory.getLogger(QuestionService.class);
private final QuestionRepository questionRepository;
private final QuestionMapper questionMapper;
public QuestionService(QuestionRepository questionRepository, QuestionMapper questionMapper) {
this.questionRepository = questionRepository;
this.questionMapper = questionMapper;
}
/**
* Save a question.
*
* @param questionDTO the entity to save.
* @return the persisted entity.
*/
public QuestionDTO save(QuestionDTO questionDTO) {
LOG.debug("Request to save Question : {}", questionDTO);
Question question = questionMapper.toEntity(questionDTO);
question = questionRepository.save(question);
return questionMapper.toDto(question);
}
/**
* Update a question.
*
* @param questionDTO the entity to save.
* @return the persisted entity.
*/
public QuestionDTO update(QuestionDTO questionDTO) {
LOG.debug("Request to update Question : {}", questionDTO);
Question question = questionMapper.toEntity(questionDTO);
question = questionRepository.save(question);
return questionMapper.toDto(question);
}
/**
* Partially update a question.
*
* @param questionDTO the entity to update partially.
* @return the persisted entity.
*/
public Optional<QuestionDTO> partialUpdate(QuestionDTO questionDTO) {
LOG.debug("Request to partially update Question : {}", questionDTO);
return questionRepository
.findById(questionDTO.getId())
.map(existingQuestion -> {
questionMapper.partialUpdate(existingQuestion, questionDTO);
return existingQuestion;
})
.map(questionRepository::save)
.map(questionMapper::toDto);
}
/**
* Get all the questions.
*
* @param pageable the pagination information.
* @return the list of entities.
*/
@Transactional(readOnly = true)
public Page<QuestionDTO> findAll(Pageable pageable) {
LOG.debug("Request to get all Questions");
return questionRepository.findAll(pageable).map(questionMapper::toDto);
}
/**
* Get all the questions with eager load of many-to-many relationships.
*
* @return the list of entities.
*/
public Page<QuestionDTO> findAllWithEagerRelationships(Pageable pageable) {
return questionRepository.findAllWithEagerRelationships(pageable).map(questionMapper::toDto);
}
/**
* Get one question by id.
*
* @param id the id of the entity.
* @return the entity.
*/
@Transactional(readOnly = true)
public Optional<QuestionDTO> findOne(Long id) {
LOG.debug("Request to get Question : {}", id);
return questionRepository.findOneWithEagerRelationships(id).map(questionMapper::toDto);
}
/**
* Delete the question by id.
*
* @param id the id of the entity.
*/
public void delete(Long id) {
LOG.debug("Request to delete Question : {}", id);
questionRepository.deleteById(id);
}
}
@@ -0,0 +1,112 @@
package com.example.questionbank.service;
import com.example.questionbank.domain.Subject;
import com.example.questionbank.repository.SubjectRepository;
import com.example.questionbank.service.dto.SubjectDTO;
import com.example.questionbank.service.mapper.SubjectMapper;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Service Implementation for managing {@link com.example.questionbank.domain.Subject}.
*/
@Service
@Transactional
public class SubjectService {
private static final Logger LOG = LoggerFactory.getLogger(SubjectService.class);
private final SubjectRepository subjectRepository;
private final SubjectMapper subjectMapper;
public SubjectService(SubjectRepository subjectRepository, SubjectMapper subjectMapper) {
this.subjectRepository = subjectRepository;
this.subjectMapper = subjectMapper;
}
/**
* Save a subject.
*
* @param subjectDTO the entity to save.
* @return the persisted entity.
*/
public SubjectDTO save(SubjectDTO subjectDTO) {
LOG.debug("Request to save Subject : {}", subjectDTO);
Subject subject = subjectMapper.toEntity(subjectDTO);
subject = subjectRepository.save(subject);
return subjectMapper.toDto(subject);
}
/**
* Update a subject.
*
* @param subjectDTO the entity to save.
* @return the persisted entity.
*/
public SubjectDTO update(SubjectDTO subjectDTO) {
LOG.debug("Request to update Subject : {}", subjectDTO);
Subject subject = subjectMapper.toEntity(subjectDTO);
subject = subjectRepository.save(subject);
return subjectMapper.toDto(subject);
}
/**
* Partially update a subject.
*
* @param subjectDTO the entity to update partially.
* @return the persisted entity.
*/
public Optional<SubjectDTO> partialUpdate(SubjectDTO subjectDTO) {
LOG.debug("Request to partially update Subject : {}", subjectDTO);
return subjectRepository
.findById(subjectDTO.getId())
.map(existingSubject -> {
subjectMapper.partialUpdate(existingSubject, subjectDTO);
return existingSubject;
})
.map(subjectRepository::save)
.map(subjectMapper::toDto);
}
/**
* Get all the subjects.
*
* @param pageable the pagination information.
* @return the list of entities.
*/
@Transactional(readOnly = true)
public Page<SubjectDTO> findAll(Pageable pageable) {
LOG.debug("Request to get all Subjects");
return subjectRepository.findAll(pageable).map(subjectMapper::toDto);
}
/**
* Get one subject by id.
*
* @param id the id of the entity.
* @return the entity.
*/
@Transactional(readOnly = true)
public Optional<SubjectDTO> findOne(Long id) {
LOG.debug("Request to get Subject : {}", id);
return subjectRepository.findById(id).map(subjectMapper::toDto);
}
/**
* Delete the subject by id.
*
* @param id the id of the entity.
*/
public void delete(Long id) {
LOG.debug("Request to delete Subject : {}", id);
subjectRepository.deleteById(id);
}
}
@@ -0,0 +1,115 @@
package com.example.questionbank.service.dto;
import com.example.questionbank.domain.enumeration.OptionKey;
import jakarta.validation.constraints.*;
import java.io.Serializable;
import java.util.Objects;
/**
* A DTO for the {@link com.example.questionbank.domain.Choice} entity.
*/
@SuppressWarnings("common-java:DuplicatedBlocks")
public class ChoiceDTO implements Serializable {
private Long id;
@NotNull
private OptionKey optionKey;
@NotNull
@Size(max = 500)
private String content;
@NotNull
@Min(value = 1)
@Max(value = 4)
private Integer displayOrder;
@NotNull
private Boolean correct;
@NotNull
private QuestionDTO question;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public OptionKey getOptionKey() {
return optionKey;
}
public void setOptionKey(OptionKey optionKey) {
this.optionKey = optionKey;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getDisplayOrder() {
return displayOrder;
}
public void setDisplayOrder(Integer displayOrder) {
this.displayOrder = displayOrder;
}
public Boolean getCorrect() {
return correct;
}
public void setCorrect(Boolean correct) {
this.correct = correct;
}
public QuestionDTO getQuestion() {
return question;
}
public void setQuestion(QuestionDTO question) {
this.question = question;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ChoiceDTO)) {
return false;
}
ChoiceDTO choiceDTO = (ChoiceDTO) o;
if (this.id == null) {
return false;
}
return Objects.equals(this.id, choiceDTO.id);
}
@Override
public int hashCode() {
return Objects.hash(this.id);
}
// prettier-ignore
@Override
public String toString() {
return "ChoiceDTO{" +
"id=" + getId() +
", optionKey='" + getOptionKey() + "'" +
", content='" + getContent() + "'" +
", displayOrder=" + getDisplayOrder() +
", correct='" + getCorrect() + "'" +
", question=" + getQuestion() +
"}";
}
}
@@ -0,0 +1,147 @@
package com.example.questionbank.service.dto;
import com.example.questionbank.domain.enumeration.OptionKey;
import jakarta.validation.constraints.*;
import java.io.Serializable;
import java.util.Objects;
/**
* A DTO for the {@link com.example.questionbank.domain.ExamAnswer} entity.
*/
@SuppressWarnings("common-java:DuplicatedBlocks")
public class ExamAnswerDTO implements Serializable {
private Long id;
@NotNull
@Size(max = 2000)
private String questionTextSnapshot;
@Size(max = 2000)
private String explanationSnapshot;
private OptionKey selectedOption;
@Size(max = 500)
private String selectedContentSnapshot;
private Boolean correct;
@Min(value = 0)
private Integer awardedPoints;
@NotNull
private QuestionDTO question;
@NotNull
private ExamAttemptDTO attempt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getQuestionTextSnapshot() {
return questionTextSnapshot;
}
public void setQuestionTextSnapshot(String questionTextSnapshot) {
this.questionTextSnapshot = questionTextSnapshot;
}
public String getExplanationSnapshot() {
return explanationSnapshot;
}
public void setExplanationSnapshot(String explanationSnapshot) {
this.explanationSnapshot = explanationSnapshot;
}
public OptionKey getSelectedOption() {
return selectedOption;
}
public void setSelectedOption(OptionKey selectedOption) {
this.selectedOption = selectedOption;
}
public String getSelectedContentSnapshot() {
return selectedContentSnapshot;
}
public void setSelectedContentSnapshot(String selectedContentSnapshot) {
this.selectedContentSnapshot = selectedContentSnapshot;
}
public Boolean getCorrect() {
return correct;
}
public void setCorrect(Boolean correct) {
this.correct = correct;
}
public Integer getAwardedPoints() {
return awardedPoints;
}
public void setAwardedPoints(Integer awardedPoints) {
this.awardedPoints = awardedPoints;
}
public QuestionDTO getQuestion() {
return question;
}
public void setQuestion(QuestionDTO question) {
this.question = question;
}
public ExamAttemptDTO getAttempt() {
return attempt;
}
public void setAttempt(ExamAttemptDTO attempt) {
this.attempt = attempt;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ExamAnswerDTO)) {
return false;
}
ExamAnswerDTO examAnswerDTO = (ExamAnswerDTO) o;
if (this.id == null) {
return false;
}
return Objects.equals(this.id, examAnswerDTO.id);
}
@Override
public int hashCode() {
return Objects.hash(this.id);
}
// prettier-ignore
@Override
public String toString() {
return "ExamAnswerDTO{" +
"id=" + getId() +
", questionTextSnapshot='" + getQuestionTextSnapshot() + "'" +
", explanationSnapshot='" + getExplanationSnapshot() + "'" +
", selectedOption='" + getSelectedOption() + "'" +
", selectedContentSnapshot='" + getSelectedContentSnapshot() + "'" +
", correct='" + getCorrect() + "'" +
", awardedPoints=" + getAwardedPoints() +
", question=" + getQuestion() +
", attempt=" + getAttempt() +
"}";
}
}
@@ -0,0 +1,124 @@
package com.example.questionbank.service.dto;
import com.example.questionbank.domain.enumeration.AttemptStatus;
import jakarta.validation.constraints.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
/**
* A DTO for the {@link com.example.questionbank.domain.ExamAttempt} entity.
*/
@SuppressWarnings("common-java:DuplicatedBlocks")
public class ExamAttemptDTO implements Serializable {
private Long id;
@NotNull
private Instant startedAt;
private Instant submittedAt;
@NotNull
private AttemptStatus status;
@Min(value = 0)
private Integer score;
@NotNull
private SubjectDTO subject;
@NotNull
private UserDTO user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Instant getStartedAt() {
return startedAt;
}
public void setStartedAt(Instant startedAt) {
this.startedAt = startedAt;
}
public Instant getSubmittedAt() {
return submittedAt;
}
public void setSubmittedAt(Instant submittedAt) {
this.submittedAt = submittedAt;
}
public AttemptStatus getStatus() {
return status;
}
public void setStatus(AttemptStatus status) {
this.status = status;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
public SubjectDTO getSubject() {
return subject;
}
public void setSubject(SubjectDTO subject) {
this.subject = subject;
}
public UserDTO getUser() {
return user;
}
public void setUser(UserDTO user) {
this.user = user;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ExamAttemptDTO)) {
return false;
}
ExamAttemptDTO examAttemptDTO = (ExamAttemptDTO) o;
if (this.id == null) {
return false;
}
return Objects.equals(this.id, examAttemptDTO.id);
}
@Override
public int hashCode() {
return Objects.hash(this.id);
}
// prettier-ignore
@Override
public String toString() {
return "ExamAttemptDTO{" +
"id=" + getId() +
", startedAt='" + getStartedAt() + "'" +
", submittedAt='" + getSubmittedAt() + "'" +
", status='" + getStatus() + "'" +
", score=" + getScore() +
", subject=" + getSubject() +
", user=" + getUser() +
"}";
}
}
@@ -0,0 +1,100 @@
package com.example.questionbank.service.dto;
import jakarta.validation.constraints.*;
import java.io.Serializable;
import java.util.Objects;
/**
* A DTO for the {@link com.example.questionbank.domain.Question} entity.
*/
@SuppressWarnings("common-java:DuplicatedBlocks")
public class QuestionDTO implements Serializable {
private Long id;
@NotNull
@Size(min = 5, max = 2000)
private String prompt;
@Size(max = 2000)
private String explanation;
@NotNull
private Boolean active;
@NotNull
private SubjectDTO subject;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPrompt() {
return prompt;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
}
public String getExplanation() {
return explanation;
}
public void setExplanation(String explanation) {
this.explanation = explanation;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public SubjectDTO getSubject() {
return subject;
}
public void setSubject(SubjectDTO subject) {
this.subject = subject;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof QuestionDTO)) {
return false;
}
QuestionDTO questionDTO = (QuestionDTO) o;
if (this.id == null) {
return false;
}
return Objects.equals(this.id, questionDTO.id);
}
@Override
public int hashCode() {
return Objects.hash(this.id);
}
// prettier-ignore
@Override
public String toString() {
return "QuestionDTO{" +
"id=" + getId() +
", prompt='" + getPrompt() + "'" +
", explanation='" + getExplanation() + "'" +
", active='" + getActive() + "'" +
", subject=" + getSubject() +
"}";
}
}
@@ -0,0 +1,88 @@
package com.example.questionbank.service.dto;
import jakarta.validation.constraints.*;
import java.io.Serializable;
import java.util.Objects;
/**
* A DTO for the {@link com.example.questionbank.domain.Subject} entity.
*/
@SuppressWarnings("common-java:DuplicatedBlocks")
public class SubjectDTO implements Serializable {
private Long id;
@NotNull
@Size(min = 2, max = 100)
private String name;
@Size(max = 1000)
private String description;
@NotNull
private Boolean active;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SubjectDTO)) {
return false;
}
SubjectDTO subjectDTO = (SubjectDTO) o;
if (this.id == null) {
return false;
}
return Objects.equals(this.id, subjectDTO.id);
}
@Override
public int hashCode() {
return Objects.hash(this.id);
}
// prettier-ignore
@Override
public String toString() {
return "SubjectDTO{" +
"id=" + getId() +
", name='" + getName() + "'" +
", description='" + getDescription() + "'" +
", active='" + getActive() + "'" +
"}";
}
}
@@ -0,0 +1,21 @@
package com.example.questionbank.service.mapper;
import com.example.questionbank.domain.Choice;
import com.example.questionbank.domain.Question;
import com.example.questionbank.service.dto.ChoiceDTO;
import com.example.questionbank.service.dto.QuestionDTO;
import org.mapstruct.*;
/**
* Mapper for the entity {@link Choice} and its DTO {@link ChoiceDTO}.
*/
@Mapper(componentModel = "spring")
public interface ChoiceMapper extends EntityMapper<ChoiceDTO, Choice> {
@Mapping(target = "question", source = "question", qualifiedByName = "questionId")
ChoiceDTO toDto(Choice s);
@Named("questionId")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
QuestionDTO toDtoQuestionId(Question question);
}
@@ -0,0 +1,28 @@
package com.example.questionbank.service.mapper;
import java.util.List;
import org.mapstruct.BeanMapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Named;
import org.mapstruct.NullValuePropertyMappingStrategy;
/**
* Contract for a generic dto to entity mapper.
*
* @param <D> - DTO type parameter.
* @param <E> - Entity type parameter.
*/
public interface EntityMapper<D, E> {
E toEntity(D dto);
D toDto(E entity);
List<E> toEntity(List<D> dtoList);
List<D> toDto(List<E> entityList);
@Named("partialUpdate")
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void partialUpdate(@MappingTarget E entity, D dto);
}
@@ -0,0 +1,29 @@
package com.example.questionbank.service.mapper;
import com.example.questionbank.domain.ExamAnswer;
import com.example.questionbank.domain.ExamAttempt;
import com.example.questionbank.domain.Question;
import com.example.questionbank.service.dto.ExamAnswerDTO;
import com.example.questionbank.service.dto.ExamAttemptDTO;
import com.example.questionbank.service.dto.QuestionDTO;
import org.mapstruct.*;
/**
* Mapper for the entity {@link ExamAnswer} and its DTO {@link ExamAnswerDTO}.
*/
@Mapper(componentModel = "spring")
public interface ExamAnswerMapper extends EntityMapper<ExamAnswerDTO, ExamAnswer> {
@Mapping(target = "question", source = "question", qualifiedByName = "questionId")
@Mapping(target = "attempt", source = "attempt", qualifiedByName = "examAttemptId")
ExamAnswerDTO toDto(ExamAnswer s);
@Named("questionId")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
QuestionDTO toDtoQuestionId(Question question);
@Named("examAttemptId")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
ExamAttemptDTO toDtoExamAttemptId(ExamAttempt examAttempt);
}
@@ -0,0 +1,31 @@
package com.example.questionbank.service.mapper;
import com.example.questionbank.domain.ExamAttempt;
import com.example.questionbank.domain.Subject;
import com.example.questionbank.domain.User;
import com.example.questionbank.service.dto.ExamAttemptDTO;
import com.example.questionbank.service.dto.SubjectDTO;
import com.example.questionbank.service.dto.UserDTO;
import org.mapstruct.*;
/**
* Mapper for the entity {@link ExamAttempt} and its DTO {@link ExamAttemptDTO}.
*/
@Mapper(componentModel = "spring")
public interface ExamAttemptMapper extends EntityMapper<ExamAttemptDTO, ExamAttempt> {
@Mapping(target = "subject", source = "subject", qualifiedByName = "subjectName")
@Mapping(target = "user", source = "user", qualifiedByName = "userLogin")
ExamAttemptDTO toDto(ExamAttempt s);
@Named("subjectName")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
@Mapping(target = "name", source = "name")
SubjectDTO toDtoSubjectName(Subject subject);
@Named("userLogin")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
@Mapping(target = "login", source = "login")
UserDTO toDtoUserLogin(User user);
}
@@ -0,0 +1,22 @@
package com.example.questionbank.service.mapper;
import com.example.questionbank.domain.Question;
import com.example.questionbank.domain.Subject;
import com.example.questionbank.service.dto.QuestionDTO;
import com.example.questionbank.service.dto.SubjectDTO;
import org.mapstruct.*;
/**
* Mapper for the entity {@link Question} and its DTO {@link QuestionDTO}.
*/
@Mapper(componentModel = "spring")
public interface QuestionMapper extends EntityMapper<QuestionDTO, Question> {
@Mapping(target = "subject", source = "subject", qualifiedByName = "subjectName")
QuestionDTO toDto(Question s);
@Named("subjectName")
@BeanMapping(ignoreByDefault = true)
@Mapping(target = "id", source = "id")
@Mapping(target = "name", source = "name")
SubjectDTO toDtoSubjectName(Subject subject);
}
@@ -0,0 +1,11 @@
package com.example.questionbank.service.mapper;
import com.example.questionbank.domain.Subject;
import com.example.questionbank.service.dto.SubjectDTO;
import org.mapstruct.*;
/**
* Mapper for the entity {@link Subject} and its DTO {@link SubjectDTO}.
*/
@Mapper(componentModel = "spring")
public interface SubjectMapper extends EntityMapper<SubjectDTO, Subject> {}
@@ -0,0 +1,171 @@
package com.example.questionbank.web.rest;
import com.example.questionbank.repository.ChoiceRepository;
import com.example.questionbank.service.ChoiceService;
import com.example.questionbank.service.dto.ChoiceDTO;
import com.example.questionbank.web.rest.errors.BadRequestAlertException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import tech.jhipster.web.util.HeaderUtil;
import tech.jhipster.web.util.ResponseUtil;
/**
* REST controller for managing {@link com.example.questionbank.domain.Choice}.
*/
@RestController
@RequestMapping("/api/choices")
public class ChoiceResource {
private static final Logger LOG = LoggerFactory.getLogger(ChoiceResource.class);
private static final String ENTITY_NAME = "choice";
@Value("${jhipster.clientApp.name:questionBank}")
private String applicationName;
private final ChoiceService choiceService;
private final ChoiceRepository choiceRepository;
public ChoiceResource(ChoiceService choiceService, ChoiceRepository choiceRepository) {
this.choiceService = choiceService;
this.choiceRepository = choiceRepository;
}
/**
* {@code POST /choices} : Create a new choice.
*
* @param choiceDTO the choiceDTO to create.
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new choiceDTO, or with status {@code 400 (Bad Request)} if the choice has already an ID.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PostMapping("")
public ResponseEntity<ChoiceDTO> createChoice(@Valid @RequestBody ChoiceDTO choiceDTO) throws URISyntaxException {
LOG.debug("REST request to save Choice : {}", choiceDTO);
if (choiceDTO.getId() != null) {
throw new BadRequestAlertException("A new choice cannot already have an ID", ENTITY_NAME, "idexists");
}
choiceDTO = choiceService.save(choiceDTO);
return ResponseEntity.created(new URI("/api/choices/" + choiceDTO.getId()))
.headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, choiceDTO.getId().toString()))
.body(choiceDTO);
}
/**
* {@code PUT /choices/:id} : Updates an existing choice.
*
* @param id the id of the choiceDTO to save.
* @param choiceDTO the choiceDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated choiceDTO,
* or with status {@code 400 (Bad Request)} if the choiceDTO is not valid,
* or with status {@code 500 (Internal Server Error)} if the choiceDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PutMapping("/{id}")
public ResponseEntity<ChoiceDTO> updateChoice(
@PathVariable(value = "id", required = false) final Long id,
@Valid @RequestBody ChoiceDTO choiceDTO
) throws URISyntaxException {
LOG.debug("REST request to update Choice : {}, {}", id, choiceDTO);
if (choiceDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, choiceDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!choiceRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
choiceDTO = choiceService.update(choiceDTO);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, choiceDTO.getId().toString()))
.body(choiceDTO);
}
/**
* {@code PATCH /choices/:id} : Partial updates given fields of an existing choice, field will ignore if it is null
*
* @param id the id of the choiceDTO to save.
* @param choiceDTO the choiceDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated choiceDTO,
* or with status {@code 400 (Bad Request)} if the choiceDTO is not valid,
* or with status {@code 404 (Not Found)} if the choiceDTO is not found,
* or with status {@code 500 (Internal Server Error)} if the choiceDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PatchMapping(value = "/{id}", consumes = { "application/json", "application/merge-patch+json" })
public ResponseEntity<ChoiceDTO> partialUpdateChoice(
@PathVariable(value = "id", required = false) final Long id,
@NotNull @RequestBody ChoiceDTO choiceDTO
) throws URISyntaxException {
LOG.debug("REST request to partial update Choice partially : {}, {}", id, choiceDTO);
if (choiceDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, choiceDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!choiceRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
Optional<ChoiceDTO> result = choiceService.partialUpdate(choiceDTO);
return ResponseUtil.wrapOrNotFound(
result,
HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, choiceDTO.getId().toString())
);
}
/**
* {@code GET /choices} : get all the Choices.
*
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of Choices in body.
*/
@GetMapping("")
public List<ChoiceDTO> getAllChoices() {
LOG.debug("REST request to get all Choices");
return choiceService.findAll();
}
/**
* {@code GET /choices/:id} : get the "id" choice.
*
* @param id the id of the choiceDTO to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the choiceDTO, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/{id}")
public ResponseEntity<ChoiceDTO> getChoice(@PathVariable("id") Long id) {
LOG.debug("REST request to get Choice : {}", id);
Optional<ChoiceDTO> choiceDTO = choiceService.findOne(id);
return ResponseUtil.wrapOrNotFound(choiceDTO);
}
/**
* {@code DELETE /choices/:id} : delete the "id" choice.
*
* @param id the id of the choiceDTO to delete.
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteChoice(@PathVariable("id") Long id) {
LOG.debug("REST request to delete Choice : {}", id);
choiceService.delete(id);
return ResponseEntity.noContent()
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString()))
.build();
}
}
@@ -0,0 +1,171 @@
package com.example.questionbank.web.rest;
import com.example.questionbank.repository.ExamAnswerRepository;
import com.example.questionbank.service.ExamAnswerService;
import com.example.questionbank.service.dto.ExamAnswerDTO;
import com.example.questionbank.web.rest.errors.BadRequestAlertException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import tech.jhipster.web.util.HeaderUtil;
import tech.jhipster.web.util.ResponseUtil;
/**
* REST controller for managing {@link com.example.questionbank.domain.ExamAnswer}.
*/
@RestController
@RequestMapping("/api/exam-answers")
public class ExamAnswerResource {
private static final Logger LOG = LoggerFactory.getLogger(ExamAnswerResource.class);
private static final String ENTITY_NAME = "examAnswer";
@Value("${jhipster.clientApp.name:questionBank}")
private String applicationName;
private final ExamAnswerService examAnswerService;
private final ExamAnswerRepository examAnswerRepository;
public ExamAnswerResource(ExamAnswerService examAnswerService, ExamAnswerRepository examAnswerRepository) {
this.examAnswerService = examAnswerService;
this.examAnswerRepository = examAnswerRepository;
}
/**
* {@code POST /exam-answers} : Create a new examAnswer.
*
* @param examAnswerDTO the examAnswerDTO to create.
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new examAnswerDTO, or with status {@code 400 (Bad Request)} if the examAnswer has already an ID.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PostMapping("")
public ResponseEntity<ExamAnswerDTO> createExamAnswer(@Valid @RequestBody ExamAnswerDTO examAnswerDTO) throws URISyntaxException {
LOG.debug("REST request to save ExamAnswer : {}", examAnswerDTO);
if (examAnswerDTO.getId() != null) {
throw new BadRequestAlertException("A new examAnswer cannot already have an ID", ENTITY_NAME, "idexists");
}
examAnswerDTO = examAnswerService.save(examAnswerDTO);
return ResponseEntity.created(new URI("/api/exam-answers/" + examAnswerDTO.getId()))
.headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, examAnswerDTO.getId().toString()))
.body(examAnswerDTO);
}
/**
* {@code PUT /exam-answers/:id} : Updates an existing examAnswer.
*
* @param id the id of the examAnswerDTO to save.
* @param examAnswerDTO the examAnswerDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated examAnswerDTO,
* or with status {@code 400 (Bad Request)} if the examAnswerDTO is not valid,
* or with status {@code 500 (Internal Server Error)} if the examAnswerDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PutMapping("/{id}")
public ResponseEntity<ExamAnswerDTO> updateExamAnswer(
@PathVariable(value = "id", required = false) final Long id,
@Valid @RequestBody ExamAnswerDTO examAnswerDTO
) throws URISyntaxException {
LOG.debug("REST request to update ExamAnswer : {}, {}", id, examAnswerDTO);
if (examAnswerDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, examAnswerDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!examAnswerRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
examAnswerDTO = examAnswerService.update(examAnswerDTO);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, examAnswerDTO.getId().toString()))
.body(examAnswerDTO);
}
/**
* {@code PATCH /exam-answers/:id} : Partial updates given fields of an existing examAnswer, field will ignore if it is null
*
* @param id the id of the examAnswerDTO to save.
* @param examAnswerDTO the examAnswerDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated examAnswerDTO,
* or with status {@code 400 (Bad Request)} if the examAnswerDTO is not valid,
* or with status {@code 404 (Not Found)} if the examAnswerDTO is not found,
* or with status {@code 500 (Internal Server Error)} if the examAnswerDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PatchMapping(value = "/{id}", consumes = { "application/json", "application/merge-patch+json" })
public ResponseEntity<ExamAnswerDTO> partialUpdateExamAnswer(
@PathVariable(value = "id", required = false) final Long id,
@NotNull @RequestBody ExamAnswerDTO examAnswerDTO
) throws URISyntaxException {
LOG.debug("REST request to partial update ExamAnswer partially : {}, {}", id, examAnswerDTO);
if (examAnswerDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, examAnswerDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!examAnswerRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
Optional<ExamAnswerDTO> result = examAnswerService.partialUpdate(examAnswerDTO);
return ResponseUtil.wrapOrNotFound(
result,
HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, examAnswerDTO.getId().toString())
);
}
/**
* {@code GET /exam-answers} : get all the Exam Answers.
*
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of Exam Answers in body.
*/
@GetMapping("")
public List<ExamAnswerDTO> getAllExamAnswers() {
LOG.debug("REST request to get all ExamAnswers");
return examAnswerService.findAll();
}
/**
* {@code GET /exam-answers/:id} : get the "id" examAnswer.
*
* @param id the id of the examAnswerDTO to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the examAnswerDTO, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/{id}")
public ResponseEntity<ExamAnswerDTO> getExamAnswer(@PathVariable("id") Long id) {
LOG.debug("REST request to get ExamAnswer : {}", id);
Optional<ExamAnswerDTO> examAnswerDTO = examAnswerService.findOne(id);
return ResponseUtil.wrapOrNotFound(examAnswerDTO);
}
/**
* {@code DELETE /exam-answers/:id} : delete the "id" examAnswer.
*
* @param id the id of the examAnswerDTO to delete.
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteExamAnswer(@PathVariable("id") Long id) {
LOG.debug("REST request to delete ExamAnswer : {}", id);
examAnswerService.delete(id);
return ResponseEntity.noContent()
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString()))
.build();
}
}
@@ -0,0 +1,188 @@
package com.example.questionbank.web.rest;
import com.example.questionbank.repository.ExamAttemptRepository;
import com.example.questionbank.service.ExamAttemptService;
import com.example.questionbank.service.dto.ExamAttemptDTO;
import com.example.questionbank.web.rest.errors.BadRequestAlertException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import tech.jhipster.web.util.HeaderUtil;
import tech.jhipster.web.util.PaginationUtil;
import tech.jhipster.web.util.ResponseUtil;
/**
* REST controller for managing {@link com.example.questionbank.domain.ExamAttempt}.
*/
@RestController
@RequestMapping("/api/exam-attempts")
public class ExamAttemptResource {
private static final Logger LOG = LoggerFactory.getLogger(ExamAttemptResource.class);
private static final String ENTITY_NAME = "examAttempt";
@Value("${jhipster.clientApp.name:questionBank}")
private String applicationName;
private final ExamAttemptService examAttemptService;
private final ExamAttemptRepository examAttemptRepository;
public ExamAttemptResource(ExamAttemptService examAttemptService, ExamAttemptRepository examAttemptRepository) {
this.examAttemptService = examAttemptService;
this.examAttemptRepository = examAttemptRepository;
}
/**
* {@code POST /exam-attempts} : Create a new examAttempt.
*
* @param examAttemptDTO the examAttemptDTO to create.
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new examAttemptDTO, or with status {@code 400 (Bad Request)} if the examAttempt has already an ID.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PostMapping("")
public ResponseEntity<ExamAttemptDTO> createExamAttempt(@Valid @RequestBody ExamAttemptDTO examAttemptDTO) throws URISyntaxException {
LOG.debug("REST request to save ExamAttempt : {}", examAttemptDTO);
if (examAttemptDTO.getId() != null) {
throw new BadRequestAlertException("A new examAttempt cannot already have an ID", ENTITY_NAME, "idexists");
}
examAttemptDTO = examAttemptService.save(examAttemptDTO);
return ResponseEntity.created(new URI("/api/exam-attempts/" + examAttemptDTO.getId()))
.headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, examAttemptDTO.getId().toString()))
.body(examAttemptDTO);
}
/**
* {@code PUT /exam-attempts/:id} : Updates an existing examAttempt.
*
* @param id the id of the examAttemptDTO to save.
* @param examAttemptDTO the examAttemptDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated examAttemptDTO,
* or with status {@code 400 (Bad Request)} if the examAttemptDTO is not valid,
* or with status {@code 500 (Internal Server Error)} if the examAttemptDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PutMapping("/{id}")
public ResponseEntity<ExamAttemptDTO> updateExamAttempt(
@PathVariable(value = "id", required = false) final Long id,
@Valid @RequestBody ExamAttemptDTO examAttemptDTO
) throws URISyntaxException {
LOG.debug("REST request to update ExamAttempt : {}, {}", id, examAttemptDTO);
if (examAttemptDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, examAttemptDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!examAttemptRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
examAttemptDTO = examAttemptService.update(examAttemptDTO);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, examAttemptDTO.getId().toString()))
.body(examAttemptDTO);
}
/**
* {@code PATCH /exam-attempts/:id} : Partial updates given fields of an existing examAttempt, field will ignore if it is null
*
* @param id the id of the examAttemptDTO to save.
* @param examAttemptDTO the examAttemptDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated examAttemptDTO,
* or with status {@code 400 (Bad Request)} if the examAttemptDTO is not valid,
* or with status {@code 404 (Not Found)} if the examAttemptDTO is not found,
* or with status {@code 500 (Internal Server Error)} if the examAttemptDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PatchMapping(value = "/{id}", consumes = { "application/json", "application/merge-patch+json" })
public ResponseEntity<ExamAttemptDTO> partialUpdateExamAttempt(
@PathVariable(value = "id", required = false) final Long id,
@NotNull @RequestBody ExamAttemptDTO examAttemptDTO
) throws URISyntaxException {
LOG.debug("REST request to partial update ExamAttempt partially : {}, {}", id, examAttemptDTO);
if (examAttemptDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, examAttemptDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!examAttemptRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
Optional<ExamAttemptDTO> result = examAttemptService.partialUpdate(examAttemptDTO);
return ResponseUtil.wrapOrNotFound(
result,
HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, examAttemptDTO.getId().toString())
);
}
/**
* {@code GET /exam-attempts} : get all the Exam Attempts.
*
* @param pageable the pagination information.
* @param eagerload flag to eager load entities from relationships (This is applicable for many-to-many).
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of Exam Attempts in body.
*/
@GetMapping("")
public ResponseEntity<List<ExamAttemptDTO>> getAllExamAttempts(
@org.springdoc.core.annotations.ParameterObject Pageable pageable,
@RequestParam(name = "eagerload", required = false, defaultValue = "true") boolean eagerload
) {
LOG.debug("REST request to get a page of ExamAttempts");
Page<ExamAttemptDTO> page;
if (eagerload) {
page = examAttemptService.findAllWithEagerRelationships(pageable);
} else {
page = examAttemptService.findAll(pageable);
}
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
return ResponseEntity.ok().headers(headers).body(page.getContent());
}
/**
* {@code GET /exam-attempts/:id} : get the "id" examAttempt.
*
* @param id the id of the examAttemptDTO to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the examAttemptDTO, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/{id}")
public ResponseEntity<ExamAttemptDTO> getExamAttempt(@PathVariable("id") Long id) {
LOG.debug("REST request to get ExamAttempt : {}", id);
Optional<ExamAttemptDTO> examAttemptDTO = examAttemptService.findOne(id);
return ResponseUtil.wrapOrNotFound(examAttemptDTO);
}
/**
* {@code DELETE /exam-attempts/:id} : delete the "id" examAttempt.
*
* @param id the id of the examAttemptDTO to delete.
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteExamAttempt(@PathVariable("id") Long id) {
LOG.debug("REST request to delete ExamAttempt : {}", id);
examAttemptService.delete(id);
return ResponseEntity.noContent()
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString()))
.build();
}
}
@@ -0,0 +1,188 @@
package com.example.questionbank.web.rest;
import com.example.questionbank.repository.QuestionRepository;
import com.example.questionbank.service.QuestionService;
import com.example.questionbank.service.dto.QuestionDTO;
import com.example.questionbank.web.rest.errors.BadRequestAlertException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import tech.jhipster.web.util.HeaderUtil;
import tech.jhipster.web.util.PaginationUtil;
import tech.jhipster.web.util.ResponseUtil;
/**
* REST controller for managing {@link com.example.questionbank.domain.Question}.
*/
@RestController
@RequestMapping("/api/questions")
public class QuestionResource {
private static final Logger LOG = LoggerFactory.getLogger(QuestionResource.class);
private static final String ENTITY_NAME = "question";
@Value("${jhipster.clientApp.name:questionBank}")
private String applicationName;
private final QuestionService questionService;
private final QuestionRepository questionRepository;
public QuestionResource(QuestionService questionService, QuestionRepository questionRepository) {
this.questionService = questionService;
this.questionRepository = questionRepository;
}
/**
* {@code POST /questions} : Create a new question.
*
* @param questionDTO the questionDTO to create.
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new questionDTO, or with status {@code 400 (Bad Request)} if the question has already an ID.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PostMapping("")
public ResponseEntity<QuestionDTO> createQuestion(@Valid @RequestBody QuestionDTO questionDTO) throws URISyntaxException {
LOG.debug("REST request to save Question : {}", questionDTO);
if (questionDTO.getId() != null) {
throw new BadRequestAlertException("A new question cannot already have an ID", ENTITY_NAME, "idexists");
}
questionDTO = questionService.save(questionDTO);
return ResponseEntity.created(new URI("/api/questions/" + questionDTO.getId()))
.headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, questionDTO.getId().toString()))
.body(questionDTO);
}
/**
* {@code PUT /questions/:id} : Updates an existing question.
*
* @param id the id of the questionDTO to save.
* @param questionDTO the questionDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated questionDTO,
* or with status {@code 400 (Bad Request)} if the questionDTO is not valid,
* or with status {@code 500 (Internal Server Error)} if the questionDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PutMapping("/{id}")
public ResponseEntity<QuestionDTO> updateQuestion(
@PathVariable(value = "id", required = false) final Long id,
@Valid @RequestBody QuestionDTO questionDTO
) throws URISyntaxException {
LOG.debug("REST request to update Question : {}, {}", id, questionDTO);
if (questionDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, questionDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!questionRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
questionDTO = questionService.update(questionDTO);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, questionDTO.getId().toString()))
.body(questionDTO);
}
/**
* {@code PATCH /questions/:id} : Partial updates given fields of an existing question, field will ignore if it is null
*
* @param id the id of the questionDTO to save.
* @param questionDTO the questionDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated questionDTO,
* or with status {@code 400 (Bad Request)} if the questionDTO is not valid,
* or with status {@code 404 (Not Found)} if the questionDTO is not found,
* or with status {@code 500 (Internal Server Error)} if the questionDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PatchMapping(value = "/{id}", consumes = { "application/json", "application/merge-patch+json" })
public ResponseEntity<QuestionDTO> partialUpdateQuestion(
@PathVariable(value = "id", required = false) final Long id,
@NotNull @RequestBody QuestionDTO questionDTO
) throws URISyntaxException {
LOG.debug("REST request to partial update Question partially : {}, {}", id, questionDTO);
if (questionDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, questionDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!questionRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
Optional<QuestionDTO> result = questionService.partialUpdate(questionDTO);
return ResponseUtil.wrapOrNotFound(
result,
HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, questionDTO.getId().toString())
);
}
/**
* {@code GET /questions} : get all the Questions.
*
* @param pageable the pagination information.
* @param eagerload flag to eager load entities from relationships (This is applicable for many-to-many).
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of Questions in body.
*/
@GetMapping("")
public ResponseEntity<List<QuestionDTO>> getAllQuestions(
@org.springdoc.core.annotations.ParameterObject Pageable pageable,
@RequestParam(name = "eagerload", required = false, defaultValue = "true") boolean eagerload
) {
LOG.debug("REST request to get a page of Questions");
Page<QuestionDTO> page;
if (eagerload) {
page = questionService.findAllWithEagerRelationships(pageable);
} else {
page = questionService.findAll(pageable);
}
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
return ResponseEntity.ok().headers(headers).body(page.getContent());
}
/**
* {@code GET /questions/:id} : get the "id" question.
*
* @param id the id of the questionDTO to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the questionDTO, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/{id}")
public ResponseEntity<QuestionDTO> getQuestion(@PathVariable("id") Long id) {
LOG.debug("REST request to get Question : {}", id);
Optional<QuestionDTO> questionDTO = questionService.findOne(id);
return ResponseUtil.wrapOrNotFound(questionDTO);
}
/**
* {@code DELETE /questions/:id} : delete the "id" question.
*
* @param id the id of the questionDTO to delete.
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteQuestion(@PathVariable("id") Long id) {
LOG.debug("REST request to delete Question : {}", id);
questionService.delete(id);
return ResponseEntity.noContent()
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString()))
.build();
}
}
@@ -0,0 +1,179 @@
package com.example.questionbank.web.rest;
import com.example.questionbank.repository.SubjectRepository;
import com.example.questionbank.service.SubjectService;
import com.example.questionbank.service.dto.SubjectDTO;
import com.example.questionbank.web.rest.errors.BadRequestAlertException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import tech.jhipster.web.util.HeaderUtil;
import tech.jhipster.web.util.PaginationUtil;
import tech.jhipster.web.util.ResponseUtil;
/**
* REST controller for managing {@link com.example.questionbank.domain.Subject}.
*/
@RestController
@RequestMapping("/api/subjects")
public class SubjectResource {
private static final Logger LOG = LoggerFactory.getLogger(SubjectResource.class);
private static final String ENTITY_NAME = "subject";
@Value("${jhipster.clientApp.name:questionBank}")
private String applicationName;
private final SubjectService subjectService;
private final SubjectRepository subjectRepository;
public SubjectResource(SubjectService subjectService, SubjectRepository subjectRepository) {
this.subjectService = subjectService;
this.subjectRepository = subjectRepository;
}
/**
* {@code POST /subjects} : Create a new subject.
*
* @param subjectDTO the subjectDTO to create.
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new subjectDTO, or with status {@code 400 (Bad Request)} if the subject has already an ID.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PostMapping("")
public ResponseEntity<SubjectDTO> createSubject(@Valid @RequestBody SubjectDTO subjectDTO) throws URISyntaxException {
LOG.debug("REST request to save Subject : {}", subjectDTO);
if (subjectDTO.getId() != null) {
throw new BadRequestAlertException("A new subject cannot already have an ID", ENTITY_NAME, "idexists");
}
subjectDTO = subjectService.save(subjectDTO);
return ResponseEntity.created(new URI("/api/subjects/" + subjectDTO.getId()))
.headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, subjectDTO.getId().toString()))
.body(subjectDTO);
}
/**
* {@code PUT /subjects/:id} : Updates an existing subject.
*
* @param id the id of the subjectDTO to save.
* @param subjectDTO the subjectDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated subjectDTO,
* or with status {@code 400 (Bad Request)} if the subjectDTO is not valid,
* or with status {@code 500 (Internal Server Error)} if the subjectDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PutMapping("/{id}")
public ResponseEntity<SubjectDTO> updateSubject(
@PathVariable(value = "id", required = false) final Long id,
@Valid @RequestBody SubjectDTO subjectDTO
) throws URISyntaxException {
LOG.debug("REST request to update Subject : {}, {}", id, subjectDTO);
if (subjectDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, subjectDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!subjectRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
subjectDTO = subjectService.update(subjectDTO);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, subjectDTO.getId().toString()))
.body(subjectDTO);
}
/**
* {@code PATCH /subjects/:id} : Partial updates given fields of an existing subject, field will ignore if it is null
*
* @param id the id of the subjectDTO to save.
* @param subjectDTO the subjectDTO to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated subjectDTO,
* or with status {@code 400 (Bad Request)} if the subjectDTO is not valid,
* or with status {@code 404 (Not Found)} if the subjectDTO is not found,
* or with status {@code 500 (Internal Server Error)} if the subjectDTO couldn't be updated.
* @throws URISyntaxException if the Location URI syntax is incorrect.
*/
@PatchMapping(value = "/{id}", consumes = { "application/json", "application/merge-patch+json" })
public ResponseEntity<SubjectDTO> partialUpdateSubject(
@PathVariable(value = "id", required = false) final Long id,
@NotNull @RequestBody SubjectDTO subjectDTO
) throws URISyntaxException {
LOG.debug("REST request to partial update Subject partially : {}, {}", id, subjectDTO);
if (subjectDTO.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
if (!Objects.equals(id, subjectDTO.getId())) {
throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
}
if (!subjectRepository.existsById(id)) {
throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
}
Optional<SubjectDTO> result = subjectService.partialUpdate(subjectDTO);
return ResponseUtil.wrapOrNotFound(
result,
HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, subjectDTO.getId().toString())
);
}
/**
* {@code GET /subjects} : get all the Subjects.
*
* @param pageable the pagination information.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of Subjects in body.
*/
@GetMapping("")
public ResponseEntity<List<SubjectDTO>> getAllSubjects(@org.springdoc.core.annotations.ParameterObject Pageable pageable) {
LOG.debug("REST request to get a page of Subjects");
Page<SubjectDTO> page = subjectService.findAll(pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
return ResponseEntity.ok().headers(headers).body(page.getContent());
}
/**
* {@code GET /subjects/:id} : get the "id" subject.
*
* @param id the id of the subjectDTO to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the subjectDTO, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/{id}")
public ResponseEntity<SubjectDTO> getSubject(@PathVariable("id") Long id) {
LOG.debug("REST request to get Subject : {}", id);
Optional<SubjectDTO> subjectDTO = subjectService.findOne(id);
return ResponseUtil.wrapOrNotFound(subjectDTO);
}
/**
* {@code DELETE /subjects/:id} : delete the "id" subject.
*
* @param id the id of the subjectDTO to delete.
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteSubject(@PathVariable("id") Long id) {
LOG.debug("REST request to delete Subject : {}", id);
subjectService.delete(id);
return ResponseEntity.noContent()
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString()))
.build();
}
}
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the entity Subject.
-->
<changeSet id="20261020093322-1" author="jhipster">
<createTable tableName="subject">
<column name="id" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(100)">
<constraints nullable="false" />
</column>
<column name="description" type="varchar(1000)">
<constraints nullable="true" />
</column>
<column name="active" type="boolean">
<constraints nullable="false" />
</column>
<!-- jhipster-needle-liquibase-add-column - JHipster will add columns here -->
</createTable>
</changeSet>
<!-- jhipster-needle-liquibase-add-changeset - JHipster will add changesets here -->
<!--
Load sample data generated with Faker.js
- This data can be easily edited using a CSV editor (or even MS Excel) and
is located in the 'src/main/resources/config/liquibase/fake-data' directory
- By default this data is applied when running with the JHipster 'dev' profile.
This can be customized by adding or removing 'faker' in the 'spring.liquibase.contexts'
Spring Boot configuration key.
-->
<changeSet id="20261020093322-1-data" author="jhipster" context="faker">
<loadData
file="../fake-data/subject.csv"
relativeToChangelogFile="true"
separator=";"
tableName="subject"
usePreparedStatements="true">
<column name="id" type="numeric"/>
<column name="name" type="string"/>
<column name="description" type="string"/>
<column name="active" type="boolean"/>
<!-- jhipster-needle-liquibase-add-loadcolumn - JHipster (and/or extensions) can add load columns here -->
</loadData>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the entity Question.
-->
<changeSet id="20261020093323-1" author="jhipster">
<createTable tableName="question">
<column name="id" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="prompt" type="varchar(2000)">
<constraints nullable="false" />
</column>
<column name="explanation" type="varchar(2000)">
<constraints nullable="true" />
</column>
<column name="active" type="boolean">
<constraints nullable="false" />
</column>
<column name="subject_id" type="bigint">
<constraints nullable="false" />
</column>
<!-- jhipster-needle-liquibase-add-column - JHipster will add columns here -->
</createTable>
</changeSet>
<!-- jhipster-needle-liquibase-add-changeset - JHipster will add changesets here -->
<!--
Load sample data generated with Faker.js
- This data can be easily edited using a CSV editor (or even MS Excel) and
is located in the 'src/main/resources/config/liquibase/fake-data' directory
- By default this data is applied when running with the JHipster 'dev' profile.
This can be customized by adding or removing 'faker' in the 'spring.liquibase.contexts'
Spring Boot configuration key.
-->
<changeSet id="20261020093323-1-data" author="jhipster" context="faker">
<loadData
file="../fake-data/question.csv"
relativeToChangelogFile="true"
separator=";"
tableName="question"
usePreparedStatements="true">
<column name="id" type="numeric"/>
<column name="prompt" type="string"/>
<column name="explanation" type="string"/>
<column name="active" type="boolean"/>
<column name="subject_id" type="numeric"/>
<!-- jhipster-needle-liquibase-add-loadcolumn - JHipster (and/or extensions) can add load columns here -->
</loadData>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the constraints for entity Question.
-->
<changeSet id="20261020093323-2" author="jhipster">
<addForeignKeyConstraint baseColumnNames="subject_id"
baseTableName="question"
constraintName="fk_question__subject_id"
referencedColumnNames="id"
referencedTableName="subject"
/>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the entity Choice.
-->
<changeSet id="20261020093324-1" author="jhipster">
<createTable tableName="choice">
<column name="id" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="option_key" type="varchar(255)">
<constraints nullable="false" />
</column>
<column name="content" type="varchar(500)">
<constraints nullable="false" />
</column>
<column name="display_order" type="integer">
<constraints nullable="false" />
</column>
<column name="correct" type="boolean">
<constraints nullable="false" />
</column>
<column name="question_id" type="bigint">
<constraints nullable="false" />
</column>
<!-- jhipster-needle-liquibase-add-column - JHipster will add columns here -->
</createTable>
</changeSet>
<!-- jhipster-needle-liquibase-add-changeset - JHipster will add changesets here -->
<!--
Load sample data generated with Faker.js
- This data can be easily edited using a CSV editor (or even MS Excel) and
is located in the 'src/main/resources/config/liquibase/fake-data' directory
- By default this data is applied when running with the JHipster 'dev' profile.
This can be customized by adding or removing 'faker' in the 'spring.liquibase.contexts'
Spring Boot configuration key.
-->
<changeSet id="20261020093324-1-data" author="jhipster" context="faker">
<loadData
file="../fake-data/choice.csv"
relativeToChangelogFile="true"
separator=";"
tableName="choice"
usePreparedStatements="true">
<column name="id" type="numeric"/>
<column name="option_key" type="string"/>
<column name="content" type="string"/>
<column name="display_order" type="numeric"/>
<column name="correct" type="boolean"/>
<column name="question_id" type="numeric"/>
<!-- jhipster-needle-liquibase-add-loadcolumn - JHipster (and/or extensions) can add load columns here -->
</loadData>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the constraints for entity Choice.
-->
<changeSet id="20261020093324-2" author="jhipster">
<addForeignKeyConstraint baseColumnNames="question_id"
baseTableName="choice"
constraintName="fk_choice__question_id"
referencedColumnNames="id"
referencedTableName="question"
/>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the entity ExamAttempt.
-->
<changeSet id="20261020093325-1" author="jhipster">
<createTable tableName="exam_attempt">
<column name="id" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="started_at" type="${datetimeType}">
<constraints nullable="false" />
</column>
<column name="submitted_at" type="${datetimeType}">
<constraints nullable="true" />
</column>
<column name="status" type="varchar(255)">
<constraints nullable="false" />
</column>
<column name="score" type="integer">
<constraints nullable="true" />
</column>
<column name="subject_id" type="bigint">
<constraints nullable="false" />
</column>
<column name="user_id" type="bigint">
<constraints nullable="false" />
</column>
<!-- jhipster-needle-liquibase-add-column - JHipster will add columns here -->
</createTable>
<dropDefaultValue tableName="exam_attempt" columnName="started_at" columnDataType="${datetimeType}"/>
<dropDefaultValue tableName="exam_attempt" columnName="submitted_at" columnDataType="${datetimeType}"/>
</changeSet>
<!-- jhipster-needle-liquibase-add-changeset - JHipster will add changesets here -->
<!--
Load sample data generated with Faker.js
- This data can be easily edited using a CSV editor (or even MS Excel) and
is located in the 'src/main/resources/config/liquibase/fake-data' directory
- By default this data is applied when running with the JHipster 'dev' profile.
This can be customized by adding or removing 'faker' in the 'spring.liquibase.contexts'
Spring Boot configuration key.
-->
<changeSet id="20261020093325-1-data" author="jhipster" context="faker">
<loadData
file="../fake-data/exam_attempt.csv"
relativeToChangelogFile="true"
separator=";"
tableName="exam_attempt"
usePreparedStatements="true">
<column name="id" type="numeric"/>
<column name="started_at" type="date"/>
<column name="submitted_at" type="date"/>
<column name="status" type="string"/>
<column name="score" type="numeric"/>
<column name="subject_id" type="numeric"/>
<column name="user_id" type="numeric"/>
<!-- jhipster-needle-liquibase-add-loadcolumn - JHipster (and/or extensions) can add load columns here -->
</loadData>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the constraints for entity ExamAttempt.
-->
<changeSet id="20261020093325-2" author="jhipster">
<addForeignKeyConstraint baseColumnNames="subject_id"
baseTableName="exam_attempt"
constraintName="fk_exam_attempt__subject_id"
referencedColumnNames="id"
referencedTableName="subject"
/>
<addForeignKeyConstraint baseColumnNames="user_id"
baseTableName="exam_attempt"
constraintName="fk_exam_attempt__user_id"
referencedColumnNames="id"
referencedTableName="jhi_user"
/>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the entity ExamAnswer.
-->
<changeSet id="20261020093326-1" author="jhipster">
<createTable tableName="exam_answer">
<column name="id" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="question_text_snapshot" type="varchar(2000)">
<constraints nullable="false" />
</column>
<column name="explanation_snapshot" type="varchar(2000)">
<constraints nullable="true" />
</column>
<column name="selected_option" type="varchar(255)">
<constraints nullable="true" />
</column>
<column name="selected_content_snapshot" type="varchar(500)">
<constraints nullable="true" />
</column>
<column name="correct" type="boolean">
<constraints nullable="true" />
</column>
<column name="awarded_points" type="integer">
<constraints nullable="true" />
</column>
<column name="question_id" type="bigint">
<constraints nullable="false" />
</column>
<column name="attempt_id" type="bigint">
<constraints nullable="false" />
</column>
<!-- jhipster-needle-liquibase-add-column - JHipster will add columns here -->
</createTable>
</changeSet>
<!-- jhipster-needle-liquibase-add-changeset - JHipster will add changesets here -->
<!--
Load sample data generated with Faker.js
- This data can be easily edited using a CSV editor (or even MS Excel) and
is located in the 'src/main/resources/config/liquibase/fake-data' directory
- By default this data is applied when running with the JHipster 'dev' profile.
This can be customized by adding or removing 'faker' in the 'spring.liquibase.contexts'
Spring Boot configuration key.
-->
<changeSet id="20261020093326-1-data" author="jhipster" context="faker">
<loadData
file="../fake-data/exam_answer.csv"
relativeToChangelogFile="true"
separator=";"
tableName="exam_answer"
usePreparedStatements="true">
<column name="id" type="numeric"/>
<column name="question_text_snapshot" type="string"/>
<column name="explanation_snapshot" type="string"/>
<column name="selected_option" type="string"/>
<column name="selected_content_snapshot" type="string"/>
<column name="correct" type="boolean"/>
<column name="awarded_points" type="numeric"/>
<column name="question_id" type="numeric"/>
<column name="attempt_id" type="numeric"/>
<!-- jhipster-needle-liquibase-add-loadcolumn - JHipster (and/or extensions) can add load columns here -->
</loadData>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Added the constraints for entity ExamAnswer.
-->
<changeSet id="20261020093326-2" author="jhipster">
<addForeignKeyConstraint baseColumnNames="question_id"
baseTableName="exam_answer"
constraintName="fk_exam_answer__question_id"
referencedColumnNames="id"
referencedTableName="question"
/>
<addForeignKeyConstraint baseColumnNames="attempt_id"
baseTableName="exam_answer"
constraintName="fk_exam_answer__attempt_id"
referencedColumnNames="id"
referencedTableName="exam_attempt"
/>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,31 @@
id;option_key;content;display_order;correct;question_id
1;A;with costume;2;true;1
2;A;lid deceivingly sequester;2;false;2
3;C;noxious bah;2;true;3
4;C;reluctantly for;4;true;4
5;C;focalise;3;false;5
6;D;unto deduce;2;true;6
7;A;appropriate;3;true;7
8;D;notwithstanding floodlight carefully;1;false;8
9;C;eek switch;1;true;9
10;C;frivolous;2;false;10
11;C;unlike internal;1;true;11
12;D;not finally cauliflower;3;true;12
13;D;snoop athwart;4;true;13
14;A;silt;4;false;14
15;A;lady ew;2;true;15
16;A;beautifully kettledrum;3;false;16
17;A;fervently vaguely;3;false;17
18;C;orchid flu;2;true;18
19;C;yellowish boohoo;4;false;19
20;B;unzip density;3;false;20
21;B;oof;3;false;21
22;C;brr before;4;true;22
23;C;fondly;2;true;23
24;C;juvenile;4;true;24
25;B;before;4;false;25
26;B;bathhouse pro once;2;true;26
27;D;now infinite;1;true;27
28;D;enraged wicked;3;true;28
29;C;hmph;3;false;29
30;C;storyboard until;3;false;30
1 id option_key content display_order correct question_id
2 1 A with costume 2 true 1
3 2 A lid deceivingly sequester 2 false 2
4 3 C noxious bah 2 true 3
5 4 C reluctantly for 4 true 4
6 5 C focalise 3 false 5
7 6 D unto deduce 2 true 6
8 7 A appropriate 3 true 7
9 8 D notwithstanding floodlight carefully 1 false 8
10 9 C eek switch 1 true 9
11 10 C frivolous 2 false 10
12 11 C unlike internal 1 true 11
13 12 D not finally cauliflower 3 true 12
14 13 D snoop athwart 4 true 13
15 14 A silt 4 false 14
16 15 A lady ew 2 true 15
17 16 A beautifully kettledrum 3 false 16
18 17 A fervently vaguely 3 false 17
19 18 C orchid flu 2 true 18
20 19 C yellowish boohoo 4 false 19
21 20 B unzip density 3 false 20
22 21 B oof 3 false 21
23 22 C brr before 4 true 22
24 23 C fondly 2 true 23
25 24 C juvenile 4 true 24
26 25 B before 4 false 25
27 26 B bathhouse pro once 2 true 26
28 27 D now infinite 1 true 27
29 28 D enraged wicked 3 true 28
30 29 C hmph 3 false 29
31 30 C storyboard until 3 false 30
@@ -0,0 +1,31 @@
id;question_text_snapshot;explanation_snapshot;selected_option;selected_content_snapshot;correct;awarded_points;question_id;attempt_id
1;ugh;toward;D;ick;true;12587;1;1
2;till woot;always;A;bin;true;16466;2;2
3;serpentine;violently circa;C;pinstripe;true;10708;3;3
4;down that deform;nor;B;as misfire fat;true;11576;4;4
5;riser oddly;but upbeat yahoo;A;however conjecture failing;true;13031;5;5
6;now;hydrolyse;D;founder even gosh;false;24241;6;6
7;pfft till broadcast;under absentmindedly unto;D;enlist but when;false;20728;7;7
8;internal versus;curiously pocket-watch;D;alongside;true;25319;8;8
9;follower vice agreement;sunbeam ah;D;woot monthly maestro;false;25579;9;9
10;aha mortise flawed;ha meanwhile but;B;pigsty musty;false;30644;10;10
11;circa scoff delete;decisive outlandish yellowish;D;atop where aside;true;7833;11;11
12;optimistically;character apropos;D;until horn till;true;29173;12;12
13;carefully scale;agreeable loose teammate;A;scale circa;false;14408;13;13
14;known since boohoo;fortunate tarry;C;crossly;true;23661;14;14
15;whoever thongs dime;meh bashfully zowie;C;woot kiss even;false;2961;15;15
16;fledgling;illiterate daily versus;B;rarely consequently;true;32555;16;16
17;actual thoroughly tightly;phooey vol;A;save;true;3135;17;17
18;now whereas plan;besides once;D;incidentally outstanding;true;6047;18;18
19;foolishly shrilly woot;nor;C;foolhardy;false;992;19;19
20;against;mixed during;C;limited boohoo;false;20306;20;20
21;instead serene jealous;out an;C;lest utter;false;6732;21;21
22;drat;edge following fervently;A;reassuringly qua or;true;17883;22;22
23;implode hence;divert but;A;which blindly;false;13140;23;23
24;miserable because detective;industrialize;D;forceful harmful;false;13920;24;24
25;whoever instantly purse;sign legislature save;D;faithfully despite until;true;5194;25;25
26;unto;oof;D;heavy above bathhouse;false;822;26;26
27;gah whoever woot;rubbery exonerate once;D;house;true;1962;27;27
28;carboxyl meal astride;round eek;D;transplant;false;9840;28;28
29;delectable;sideboard;C;failing;true;27119;29;29
30;ruin pike;above impressionable instead;A;gallivant however;true;25196;30;30
1 id question_text_snapshot explanation_snapshot selected_option selected_content_snapshot correct awarded_points question_id attempt_id
2 1 ugh toward D ick true 12587 1 1
3 2 till woot always A bin true 16466 2 2
4 3 serpentine violently circa C pinstripe true 10708 3 3
5 4 down that deform nor B as misfire fat true 11576 4 4
6 5 riser oddly but upbeat yahoo A however conjecture failing true 13031 5 5
7 6 now hydrolyse D founder even gosh false 24241 6 6
8 7 pfft till broadcast under absentmindedly unto D enlist but when false 20728 7 7
9 8 internal versus curiously pocket-watch D alongside true 25319 8 8
10 9 follower vice agreement sunbeam ah D woot monthly maestro false 25579 9 9
11 10 aha mortise flawed ha meanwhile but B pigsty musty false 30644 10 10
12 11 circa scoff delete decisive outlandish yellowish D atop where aside true 7833 11 11
13 12 optimistically character apropos D until horn till true 29173 12 12
14 13 carefully scale agreeable loose teammate A scale circa false 14408 13 13
15 14 known since boohoo fortunate tarry C crossly true 23661 14 14
16 15 whoever thongs dime meh bashfully zowie C woot kiss even false 2961 15 15
17 16 fledgling illiterate daily versus B rarely consequently true 32555 16 16
18 17 actual thoroughly tightly phooey vol A save true 3135 17 17
19 18 now whereas plan besides once D incidentally outstanding true 6047 18 18
20 19 foolishly shrilly woot nor C foolhardy false 992 19 19
21 20 against mixed during C limited boohoo false 20306 20 20
22 21 instead serene jealous out an C lest utter false 6732 21 21
23 22 drat edge following fervently A reassuringly qua or true 17883 22 22
24 23 implode hence divert but A which blindly false 13140 23 23
25 24 miserable because detective industrialize D forceful harmful false 13920 24 24
26 25 whoever instantly purse sign legislature save D faithfully despite until true 5194 25 25
27 26 unto oof D heavy above bathhouse false 822 26 26
28 27 gah whoever woot rubbery exonerate once D house true 1962 27 27
29 28 carboxyl meal astride round eek D transplant false 9840 28 28
30 29 delectable sideboard C failing true 27119 29 29
31 30 ruin pike above impressionable instead A gallivant however true 25196 30 30
@@ -0,0 +1,31 @@
id;started_at;submitted_at;status;score;subject_id;user_id
1;2026-10-20T05:13:21;2026-10-20T01:17:36;SUBMITTED;3734;1;1
2;2026-10-19T09:47:49;2026-10-20T05:21:23;IN_PROGRESS;28602;2;2
3;2026-10-19T12:45:47;2026-10-19T16:22:03;SUBMITTED;27107;3;1
4;2026-10-20T08:43:34;2026-10-20T06:21:59;IN_PROGRESS;7700;4;2
5;2026-10-20T05:35:25;2026-10-20T07:22:22;IN_PROGRESS;25958;5;1
6;2026-10-20T03:14:45;2026-10-19T11:15:55;SUBMITTED;11353;6;2
7;2026-10-19T23:27:46;2026-10-20T08:33:10;SUBMITTED;6060;7;2
8;2026-10-20T06:49:32;2026-10-19T20:38:10;IN_PROGRESS;16941;8;2
9;2026-10-19T15:09:55;2026-10-20T05:14:06;SUBMITTED;20866;9;1
10;2026-10-19T16:39:29;2026-10-19T20:06:46;IN_PROGRESS;16189;10;2
11;2026-10-20T06:38:28;2026-10-19T22:18:56;SUBMITTED;19064;11;2
12;2026-10-20T08:04:44;2026-10-19T15:02:59;SUBMITTED;18424;12;1
13;2026-10-19T10:18:45;2026-10-20T06:20:54;IN_PROGRESS;26938;13;2
14;2026-10-20T04:10:26;2026-10-20T01:44:42;SUBMITTED;8122;14;1
15;2026-10-19T18:16:57;2026-10-19T15:22:19;IN_PROGRESS;23873;15;1
16;2026-10-20T09:07:32;2026-10-20T05:17:47;IN_PROGRESS;9256;16;2
17;2026-10-20T04:02:47;2026-10-20T09:17:35;SUBMITTED;6982;17;2
18;2026-10-19T11:17:34;2026-10-20T03:26:21;IN_PROGRESS;25465;18;1
19;2026-10-20T04:33:44;2026-10-19T21:48:50;SUBMITTED;3984;19;2
20;2026-10-19T22:18:00;2026-10-19T16:02:57;IN_PROGRESS;4109;20;1
21;2026-10-20T01:49:34;2026-10-19T17:57:15;IN_PROGRESS;19778;21;2
22;2026-10-19T12:30:46;2026-10-20T03:16:39;SUBMITTED;14716;22;2
23;2026-10-19T11:51:17;2026-10-20T04:06:02;SUBMITTED;13292;23;2
24;2026-10-20T02:30:50;2026-10-20T00:57:33;IN_PROGRESS;7461;24;1
25;2026-10-20T02:04:10;2026-10-19T15:42:20;IN_PROGRESS;20711;25;1
26;2026-10-19T18:47:45;2026-10-20T03:42:21;IN_PROGRESS;4861;26;2
27;2026-10-19T15:38:42;2026-10-20T05:40:30;SUBMITTED;29915;27;2
28;2026-10-20T06:35:33;2026-10-19T16:47:02;IN_PROGRESS;1705;28;1
29;2026-10-19T20:45:52;2026-10-20T02:14:03;SUBMITTED;31593;29;1
30;2026-10-19T23:07:28;2026-10-19T12:20:15;IN_PROGRESS;10042;30;2
1 id started_at submitted_at status score subject_id user_id
2 1 2026-10-20T05:13:21 2026-10-20T01:17:36 SUBMITTED 3734 1 1
3 2 2026-10-19T09:47:49 2026-10-20T05:21:23 IN_PROGRESS 28602 2 2
4 3 2026-10-19T12:45:47 2026-10-19T16:22:03 SUBMITTED 27107 3 1
5 4 2026-10-20T08:43:34 2026-10-20T06:21:59 IN_PROGRESS 7700 4 2
6 5 2026-10-20T05:35:25 2026-10-20T07:22:22 IN_PROGRESS 25958 5 1
7 6 2026-10-20T03:14:45 2026-10-19T11:15:55 SUBMITTED 11353 6 2
8 7 2026-10-19T23:27:46 2026-10-20T08:33:10 SUBMITTED 6060 7 2
9 8 2026-10-20T06:49:32 2026-10-19T20:38:10 IN_PROGRESS 16941 8 2
10 9 2026-10-19T15:09:55 2026-10-20T05:14:06 SUBMITTED 20866 9 1
11 10 2026-10-19T16:39:29 2026-10-19T20:06:46 IN_PROGRESS 16189 10 2
12 11 2026-10-20T06:38:28 2026-10-19T22:18:56 SUBMITTED 19064 11 2
13 12 2026-10-20T08:04:44 2026-10-19T15:02:59 SUBMITTED 18424 12 1
14 13 2026-10-19T10:18:45 2026-10-20T06:20:54 IN_PROGRESS 26938 13 2
15 14 2026-10-20T04:10:26 2026-10-20T01:44:42 SUBMITTED 8122 14 1
16 15 2026-10-19T18:16:57 2026-10-19T15:22:19 IN_PROGRESS 23873 15 1
17 16 2026-10-20T09:07:32 2026-10-20T05:17:47 IN_PROGRESS 9256 16 2
18 17 2026-10-20T04:02:47 2026-10-20T09:17:35 SUBMITTED 6982 17 2
19 18 2026-10-19T11:17:34 2026-10-20T03:26:21 IN_PROGRESS 25465 18 1
20 19 2026-10-20T04:33:44 2026-10-19T21:48:50 SUBMITTED 3984 19 2
21 20 2026-10-19T22:18:00 2026-10-19T16:02:57 IN_PROGRESS 4109 20 1
22 21 2026-10-20T01:49:34 2026-10-19T17:57:15 IN_PROGRESS 19778 21 2
23 22 2026-10-19T12:30:46 2026-10-20T03:16:39 SUBMITTED 14716 22 2
24 23 2026-10-19T11:51:17 2026-10-20T04:06:02 SUBMITTED 13292 23 2
25 24 2026-10-20T02:30:50 2026-10-20T00:57:33 IN_PROGRESS 7461 24 1
26 25 2026-10-20T02:04:10 2026-10-19T15:42:20 IN_PROGRESS 20711 25 1
27 26 2026-10-19T18:47:45 2026-10-20T03:42:21 IN_PROGRESS 4861 26 2
28 27 2026-10-19T15:38:42 2026-10-20T05:40:30 SUBMITTED 29915 27 2
29 28 2026-10-20T06:35:33 2026-10-19T16:47:02 IN_PROGRESS 1705 28 1
30 29 2026-10-19T20:45:52 2026-10-20T02:14:03 SUBMITTED 31593 29 1
31 30 2026-10-19T23:07:28 2026-10-19T12:20:15 IN_PROGRESS 10042 30 2
@@ -0,0 +1,31 @@
id;prompt;explanation;active;subject_id
1;yuckX;meh;false;1
2;independence who hasty;pace fledgling idealistic;false;2
3;yetXX;shyly mid although;true;3
4;aw unsightly criminal;atop whether absent;false;4
5;nearly;till phooey which;true;5
6;embalm;first since brand;true;6
7;carelessly;reclassify each;true;7
8;awXXX;publication consequently median;false;8
9;curiously claw;gaseous phooey athwart;true;9
10;worth endow simple;unabashedly fork despite;false;10
11;for airline;immediate inconsequential;false;11
12;quaver;whether;true;12
13;oh brr bobble;who sandy;true;13
14;colorful;graffiti offset;true;14
15;intrepid;uncork;true;15
16;plus engage;ah;false;16
17;because;until nephew via;false;17
18;slink glass;zowie if loosely;false;18
19;mom ew;league easy huzzah;true;19
20;scotch yet;print unlike pfft;true;20
21;athletic cruel than;given valuable whose;false;21
22;mad schnitzel;rebel;false;22
23;tomorrow until though;now almost;true;23
24;overheard ouch after;anti needily;false;24
25;meanwhile tensely;minister gee gosh;false;25
26;intensely psst beside;overcoat because ack;true;26
27;reword;cluttered boo;false;27
28;colorfully beneath interestingly;flustered hasty;false;28
29;kiddingly unimportant uh-huh;deploy;false;29
30;about toward;revitalise ah decryption;true;30
1 id prompt explanation active subject_id
2 1 yuckX meh false 1
3 2 independence who hasty pace fledgling idealistic false 2
4 3 yetXX shyly mid although true 3
5 4 aw unsightly criminal atop whether absent false 4
6 5 nearly till phooey which true 5
7 6 embalm first since brand true 6
8 7 carelessly reclassify each true 7
9 8 awXXX publication consequently median false 8
10 9 curiously claw gaseous phooey athwart true 9
11 10 worth endow simple unabashedly fork despite false 10
12 11 for airline immediate inconsequential false 11
13 12 quaver whether true 12
14 13 oh brr bobble who sandy true 13
15 14 colorful graffiti offset true 14
16 15 intrepid uncork true 15
17 16 plus engage ah false 16
18 17 because until nephew via false 17
19 18 slink glass zowie if loosely false 18
20 19 mom ew league easy huzzah true 19
21 20 scotch yet print unlike pfft true 20
22 21 athletic cruel than given valuable whose false 21
23 22 mad schnitzel rebel false 22
24 23 tomorrow until though now almost true 23
25 24 overheard ouch after anti needily false 24
26 25 meanwhile tensely minister gee gosh false 25
27 26 intensely psst beside overcoat because ack true 26
28 27 reword cluttered boo false 27
29 28 colorfully beneath interestingly flustered hasty false 28
30 29 kiddingly unimportant uh-huh deploy false 29
31 30 about toward revitalise ah decryption true 30
@@ -0,0 +1,31 @@
id;name;description;active
1;indeed atomize pale;practical randomize and;true
2;accompany;per doodle for;true
3;swift zowie amid;continually;false
4;thankfully instead;shout excess fond;true
5;unfreeze configuration;gee unless;false
6;absentmindedly;pro clear lasting;true
7;courageously husk zowie;into;false
8;nougat little accidentally;ick;true
9;what;opposite subtract;false
10;underneath;despite roadway;true
11;officially restfully;guidance;false
12;likewise;boring drat ew;true
13;icebreaker considering upbeat;until across bogus;true
14;natural;ah of;false
15;ugh fax;ferret ack;true
16;longingly drum;nor;false
17;yahoo;doubtfully plan along;true
18;so through;wafer so;true
19;nervous luck;roadway digit;true
20;bare yippee;trolley arbitrate;false
21;uncomfortable;detective;true
22;flustered finally;brr terribly;true
23;a selfishly;biodegradable ew;true
24;ingratiate propound promptly;advocate;true
25;indeed ouch;pfft failing reschedule;false
26;while robust unique;camouflage after now;false
27;source;anenst curse resort;true
28;colorfully um;ugh adventurously;true
29;provided ah;why;false
30;ad along a;tusk;false
1 id name description active
2 1 indeed atomize pale practical randomize and true
3 2 accompany per doodle for true
4 3 swift zowie amid continually false
5 4 thankfully instead shout excess fond true
6 5 unfreeze configuration gee unless false
7 6 absentmindedly pro clear lasting true
8 7 courageously husk zowie into false
9 8 nougat little accidentally ick true
10 9 what opposite subtract false
11 10 underneath despite roadway true
12 11 officially restfully guidance false
13 12 likewise boring drat ew true
14 13 icebreaker considering upbeat until across bogus true
15 14 natural ah of false
16 15 ugh fax ferret ack true
17 16 longingly drum nor false
18 17 yahoo doubtfully plan along true
19 18 so through wafer so true
20 19 nervous luck roadway digit true
21 20 bare yippee trolley arbitrate false
22 21 uncomfortable detective true
23 22 flustered finally brr terribly true
24 23 a selfishly biodegradable ew true
25 24 ingratiate propound promptly advocate true
26 25 indeed ouch pfft failing reschedule false
27 26 while robust unique camouflage after now false
28 27 source anenst curse resort true
29 28 colorfully um ugh adventurously true
30 29 provided ah why false
31 30 ad along a tusk false
@@ -12,7 +12,16 @@
<property name="timeType" value="time(6)" dbms="postgresql"/> <property name="timeType" value="time(6)" dbms="postgresql"/>
<include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/> <include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093322_added_entity_Subject.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093323_added_entity_Question.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093324_added_entity_Choice.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093325_added_entity_ExamAttempt.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093326_added_entity_ExamAnswer.xml" relativeToChangelogFile="false"/>
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here --> <!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
<include file="config/liquibase/changelog/20261020093323_added_entity_constraints_Question.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093324_added_entity_constraints_Choice.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093325_added_entity_constraints_ExamAttempt.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20261020093326_added_entity_constraints_ExamAnswer.xml" relativeToChangelogFile="false"/>
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here --> <!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here --> <!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
</databaseChangeLog> </databaseChangeLog>
@@ -0,0 +1,91 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import ChoiceDetails from './choice-details.vue';
type ChoiceDetailsComponentType = InstanceType<typeof ChoiceDetails>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const choiceSample = { id: 123 };
describe('Component Tests', () => {
let alertService: AlertService;
afterEach(() => {
vi.resetAllMocks();
});
describe('Choice Management Detail Component', () => {
let choiceServiceStub: any;
let mountOptions: MountingOptions<ChoiceDetailsComponentType>['global'];
beforeEach(() => {
route = {};
choiceServiceStub = {
find: vi.fn(),
};
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'router-link': true,
},
provide: {
alertService,
choiceService: () => choiceServiceStub,
},
};
});
describe('Navigate to details', () => {
it('Should call load all on init', async () => {
// GIVEN
choiceServiceStub.find.mockResolvedValue(choiceSample);
route = {
params: {
choiceId: `${123}`,
},
};
const wrapper = shallowMount(ChoiceDetails, { global: mountOptions });
const comp = wrapper.vm;
// WHEN
await comp.$nextTick();
// THEN
expect(comp.choice).toMatchObject(choiceSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
choiceServiceStub.find.mockResolvedValue(choiceSample);
const wrapper = shallowMount(ChoiceDetails, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,42 @@
import { type Ref, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useAlertService } from '@/shared/alert/alert.service';
import { type IChoice } from '@/shared/model/choice.model';
import ChoiceService from './choice.service';
export default defineComponent({
name: 'ChoiceDetails',
setup() {
const choiceService = inject('choiceService', () => new ChoiceService());
const alertService = inject('alertService', () => useAlertService(), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const choice: Ref<IChoice> = ref({});
const retrieveChoice = async choiceId => {
try {
const res = await choiceService().find(choiceId);
choice.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.choiceId) {
retrieveChoice(route.params.choiceId);
}
return {
choice,
previousState,
t$: useI18n().t,
};
},
});
@@ -0,0 +1,55 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<div v-if="choice">
<h2 class="jh-entity-heading" data-cy="choiceDetailsHeading">
<span>{{ t$('questionBankApp.choice.detail.title') }}</span> {{ choice.id }}
</h2>
<dl class="row-md jh-entity-details">
<dt>
<span>{{ t$('questionBankApp.choice.optionKey') }}</span>
</dt>
<dd>
<span>{{ t$('questionBankApp.OptionKey.' + choice.optionKey) }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.choice.content') }}</span>
</dt>
<dd>
<span>{{ choice.content }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.choice.displayOrder') }}</span>
</dt>
<dd>
<span>{{ choice.displayOrder }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.choice.correct') }}</span>
</dt>
<dd>
<span>{{ choice.correct }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.choice.question') }}</span>
</dt>
<dd>
<div v-if="choice.question">
<router-link :to="{ name: 'QuestionView', params: { questionId: choice.question.id } }">{{ choice.question.id }}</router-link>
</div>
</dd>
</dl>
<button type="submit" @click.prevent="previousState()" class="btn btn-info" data-cy="entityDetailsBackButton">
<font-awesome-icon icon="arrow-left"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.back') }}</span>
</button>
<router-link v-if="choice.id" :to="{ name: 'ChoiceEdit', params: { choiceId: choice.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-primary">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.edit') }}</span>
</button>
</router-link>
</div>
</div>
</div>
</template>
<script lang="ts" src="./choice-details.component.ts"></script>
@@ -0,0 +1,139 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import ChoiceUpdate from './choice-update.vue';
type ChoiceUpdateComponentType = InstanceType<typeof ChoiceUpdate>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const choiceSample = { id: 123 };
describe('Component Tests', () => {
let mountOptions: MountingOptions<ChoiceUpdateComponentType>['global'];
let alertService: AlertService;
describe('Choice Management Update Component', () => {
let comp: ChoiceUpdateComponentType;
let choiceServiceStub: any;
beforeEach(() => {
route = {};
choiceServiceStub = {
retrieve: vi.fn(),
find: vi.fn(),
update: vi.fn(),
create: vi.fn(),
};
choiceServiceStub.retrieve.mockResolvedValueOnce([]);
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'b-input-group': true,
'b-input-group-prepend': true,
'b-form-datepicker': true,
'b-form-input': true,
},
provide: {
alertService,
choiceService: () => choiceServiceStub,
questionService: () => ({
retrieve: vi.fn().mockResolvedValue({}),
}),
},
};
});
afterEach(() => {
vi.resetAllMocks();
});
describe('save', () => {
it('Should call update service on save for existing entity', async () => {
// GIVEN
const wrapper = shallowMount(ChoiceUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.choice = choiceSample;
choiceServiceStub.update.mockResolvedValue(choiceSample);
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(choiceServiceStub.update).toHaveBeenCalledWith(choiceSample);
expect(comp.isSaving).toEqual(false);
});
it('Should call create service on save for new entity', async () => {
// GIVEN
const entity = {};
choiceServiceStub.create.mockResolvedValue(entity);
const wrapper = shallowMount(ChoiceUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.choice = entity;
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(choiceServiceStub.create).toHaveBeenCalledWith(entity);
expect(comp.isSaving).toEqual(false);
});
});
describe('Before route enter', () => {
it('Should retrieve data', async () => {
// GIVEN
choiceServiceStub.find.mockResolvedValue(choiceSample);
choiceServiceStub.retrieve.mockResolvedValue([choiceSample]);
// WHEN
route = {
params: {
choiceId: `${choiceSample.id}`,
},
};
const wrapper = shallowMount(ChoiceUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(comp.choice).toMatchObject(choiceSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
choiceServiceStub.find.mockResolvedValue(choiceSample);
const wrapper = shallowMount(ChoiceUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,129 @@
import { type Ref, computed, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useVuelidate } from '@vuelidate/core';
import QuestionService from '@/entities/question/question.service';
import { useAlertService } from '@/shared/alert/alert.service';
import { useValidation } from '@/shared/composables';
import { Choice, type IChoice } from '@/shared/model/choice.model';
import { OptionKey } from '@/shared/model/enumerations/option-key.model';
import { type IQuestion } from '@/shared/model/question.model';
import ChoiceService from './choice.service';
export default defineComponent({
name: 'ChoiceUpdate',
setup() {
const choiceService = inject('choiceService', () => new ChoiceService());
const alertService = inject('alertService', () => useAlertService(), true);
const choice: Ref<IChoice> = ref(new Choice());
const questionService = inject('questionService', () => new QuestionService());
const questions: Ref<IQuestion[]> = ref([]);
const optionKeyValues: Ref<string[]> = ref(Object.keys(OptionKey));
const isSaving = ref(false);
const currentLanguage = inject('currentLanguage', () => computed(() => navigator.language ?? 'en'), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const retrieveChoice = async choiceId => {
try {
const res = await choiceService().find(choiceId);
choice.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.choiceId) {
retrieveChoice(route.params.choiceId);
}
const initRelationships = () => {
questionService()
.retrieve({ page: 0, size: 100 })
.then(res => {
questions.value = res.data;
});
};
initRelationships();
const { t: t$ } = useI18n();
const validations = useValidation();
const validationRules = {
optionKey: {
required: validations.required(t$('entity.validation.required').toString()),
},
content: {
required: validations.required(t$('entity.validation.required').toString()),
maxLength: validations.maxLength(t$('entity.validation.maxlength', { max: 500 }).toString(), 500),
},
displayOrder: {
required: validations.required(t$('entity.validation.required').toString()),
integer: validations.integer(t$('entity.validation.number').toString()),
min: validations.minValue(t$('entity.validation.min', { min: 1 }).toString(), 1),
max: validations.maxValue(t$('entity.validation.max', { max: 4 }).toString(), 4),
},
correct: {
required: validations.required(t$('entity.validation.required').toString()),
},
question: {
required: validations.required(t$('entity.validation.required').toString()),
},
};
const v$ = useVuelidate(validationRules, choice as any);
v$.value.$validate();
return {
choiceService,
alertService,
choice,
previousState,
optionKeyValues,
isSaving,
currentLanguage,
questions,
v$,
t$,
};
},
created(): void {},
methods: {
save(): void {
this.isSaving = true;
if (this.choice.id) {
this.choiceService()
.update(this.choice)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showInfo(this.t$('questionBankApp.choice.updated', { param: param.id }));
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
} else {
this.choiceService()
.create(this.choice)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showSuccess(this.t$('questionBankApp.choice.created', { param: param.id }).toString());
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
}
},
},
});
@@ -0,0 +1,120 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<form name="editForm" novalidate @submit.prevent="save()">
<h2 id="questionBankApp.choice.home.createOrEditLabel" data-cy="ChoiceCreateUpdateHeading">
{{ t$('questionBankApp.choice.home.createOrEditLabel') }}
</h2>
<div>
<div class="mb-3" v-if="choice.id">
<label for="id">{{ t$('global.field.id') }}</label>
<input type="text" class="form-control" id="id" name="id" v-model="choice.id" readonly />
</div>
<div class="mb-3">
<label class="form-control-label" for="choice-optionKey">{{ t$('questionBankApp.choice.optionKey') }}</label>
<select
class="form-control"
name="optionKey"
:class="{ valid: !v$.optionKey.$invalid, invalid: v$.optionKey.$invalid }"
v-model="v$.optionKey.$model"
id="choice-optionKey"
data-cy="optionKey"
required
>
<option
v-for="optionKey in optionKeyValues"
:key="optionKey"
:value="optionKey"
:label="t$('questionBankApp.OptionKey.' + optionKey)"
>
{{ optionKey }}
</option>
</select>
<div v-if="v$.optionKey.$anyDirty && v$.optionKey.$invalid">
<small class="form-text text-danger" v-for="error of v$.optionKey.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="choice-content">{{ t$('questionBankApp.choice.content') }}</label>
<input
type="text"
class="form-control"
name="content"
id="choice-content"
data-cy="content"
:class="{ valid: !v$.content.$invalid, invalid: v$.content.$invalid }"
v-model="v$.content.$model"
required
/>
<div v-if="v$.content.$anyDirty && v$.content.$invalid">
<small class="form-text text-danger" v-for="error of v$.content.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="choice-displayOrder">{{ t$('questionBankApp.choice.displayOrder') }}</label>
<input
type="number"
class="form-control"
name="displayOrder"
id="choice-displayOrder"
data-cy="displayOrder"
:class="{ valid: !v$.displayOrder.$invalid, invalid: v$.displayOrder.$invalid }"
v-model.number="v$.displayOrder.$model"
required
/>
<div v-if="v$.displayOrder.$anyDirty && v$.displayOrder.$invalid">
<small class="form-text text-danger" v-for="error of v$.displayOrder.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3 form-check">
<input
type="checkbox"
class="form-check-input"
name="correct"
id="choice-correct"
data-cy="correct"
:class="{ valid: !v$.correct.$invalid, invalid: v$.correct.$invalid }"
v-model="v$.correct.$model"
required
/>
<label class="form-check-label" for="choice-correct">{{ t$('questionBankApp.choice.correct') }}</label>
<div v-if="v$.correct.$anyDirty && v$.correct.$invalid">
<small class="form-text text-danger" v-for="error of v$.correct.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="choice">{{ t$('questionBankApp.choice.question') }}</label>
<select class="form-control" id="choice-question" data-cy="question" name="question" v-model="choice.question" required>
<option v-if="!choice.question" :value="null" selected></option>
<option
:value="choice.question && questionOption.id === choice.question.id ? choice.question : questionOption"
v-for="questionOption in questions"
:key="questionOption.id"
>
{{ questionOption.id }}
</option>
</select>
</div>
<div v-if="v$.question.$anyDirty && v$.question.$invalid">
<small class="form-text text-danger" v-for="error of v$.question.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div>
<button type="button" id="cancel-save" data-cy="entityCreateCancelButton" class="btn btn-secondary" @click="previousState()">
<font-awesome-icon icon="ban"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.cancel') }}</span>
</button>
<button
type="submit"
id="save-entity"
data-cy="entityCreateSaveButton"
:disabled="v$.$invalid || isSaving"
class="btn btn-primary"
>
<font-awesome-icon icon="save"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.save') }}</span>
</button>
</div>
</form>
</div>
</div>
</template>
<script lang="ts" src="./choice-update.component.ts"></script>
@@ -0,0 +1,103 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import Choice from './choice.vue';
type ChoiceComponentType = InstanceType<typeof Choice>;
const bModalStub = {
render: () => {},
methods: {
hide: () => {},
show: () => {},
},
};
describe('Component Tests', () => {
let alertService: AlertService;
describe('Choice Management Component', () => {
let choiceServiceStub: any;
let mountOptions: MountingOptions<ChoiceComponentType>['global'];
beforeEach(() => {
choiceServiceStub = {
retrieve: vi.fn(),
delete: vi.fn(),
};
choiceServiceStub.retrieve.mockResolvedValue({ headers: {} });
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
bModal: bModalStub as any,
'font-awesome-icon': true,
'b-badge': true,
'b-button': true,
'router-link': true,
},
directives: {
'b-modal': {},
},
provide: {
alertService,
choiceService: () => choiceServiceStub,
},
};
});
describe('Mount', () => {
it('Should call load all on init', async () => {
// GIVEN
choiceServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
const wrapper = shallowMount(Choice, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(choiceServiceStub.retrieve).toHaveBeenCalledOnce();
expect(comp.choices[0]).toEqual(expect.objectContaining({ id: 123 }));
});
});
describe('Handles', () => {
let comp: ChoiceComponentType;
beforeEach(async () => {
const wrapper = shallowMount(Choice, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
choiceServiceStub.retrieve.mockReset();
choiceServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [] });
});
it('Should call delete service on confirmDelete', async () => {
// GIVEN
choiceServiceStub.delete.mockResolvedValue({});
// WHEN
comp.prepareRemove({ id: 123 });
comp.removeChoice();
await comp.$nextTick(); // clear components
// THEN
expect(choiceServiceStub.delete).toHaveBeenCalled();
// THEN
await comp.$nextTick(); // handle component clear watch
expect(choiceServiceStub.retrieve).toHaveBeenCalledTimes(1);
});
});
});
});
@@ -0,0 +1,78 @@
import { type Ref, defineComponent, inject, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlertService } from '@/shared/alert/alert.service';
import { type IChoice } from '@/shared/model/choice.model';
import ChoiceService from './choice.service';
export default defineComponent({
name: 'Choice',
setup() {
const { t: t$ } = useI18n();
const choiceService = inject('choiceService', () => new ChoiceService());
const alertService = inject('alertService', () => useAlertService(), true);
const choices: Ref<IChoice[]> = ref([]);
const isFetching = ref(false);
const clear = () => {};
const retrieveChoices = async () => {
isFetching.value = true;
try {
const res = await choiceService().retrieve();
choices.value = res.data;
} catch (err) {
alertService.showHttpError(err.response);
} finally {
isFetching.value = false;
}
};
const handleSyncList = () => {
retrieveChoices();
};
onMounted(async () => {
await retrieveChoices();
});
const removeId: Ref<number> = ref(null);
const removeEntity = ref<any>(null);
const prepareRemove = (instance: IChoice) => {
removeId.value = instance.id;
removeEntity.value.show();
};
const closeDialog = () => {
removeEntity.value.hide();
};
const removeChoice = async () => {
try {
await choiceService().delete(removeId.value);
const message = t$('questionBankApp.choice.deleted', { param: removeId.value }).toString();
alertService.showInfo(message, { variant: 'danger' });
removeId.value = null;
retrieveChoices();
closeDialog();
} catch (error) {
alertService.showHttpError(error.response);
}
};
return {
choices,
handleSyncList,
isFetching,
retrieveChoices,
clear,
removeId,
removeEntity,
prepareRemove,
closeDialog,
removeChoice,
t$,
};
},
});
@@ -0,0 +1,161 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import axios from 'axios';
import { Choice } from '@/shared/model/choice.model';
import ChoiceService from './choice.service';
const error = {
response: {
status: null,
data: {
type: null,
},
},
};
const axiosStub = {
get: vi.spyOn(axios, 'get'),
post: vi.spyOn(axios, 'post'),
put: vi.spyOn(axios, 'put'),
patch: vi.spyOn(axios, 'patch'),
delete: vi.spyOn(axios, 'delete'),
};
describe('Service Tests', () => {
describe('Choice Service', () => {
let service: ChoiceService;
let elemDefault;
beforeEach(() => {
service = new ChoiceService();
elemDefault = new Choice(123, 'A', 'AAAAAAA', 0, false);
});
describe('Service methods', () => {
it('should find an element', async () => {
const returnedFromService = { ...elemDefault };
axiosStub.get.mockResolvedValue({ data: returnedFromService });
return service.find(123).then(res => {
expect(res).toMatchObject(elemDefault);
});
});
it('should not find an element', async () => {
axiosStub.get.mockRejectedValue(error);
return service
.find(123)
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should create a Choice', async () => {
const returnedFromService = { id: 123, ...elemDefault };
const expected = { ...returnedFromService };
axiosStub.post.mockResolvedValue({ data: returnedFromService });
return service.create({}).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not create a Choice', async () => {
axiosStub.post.mockRejectedValue(error);
return service
.create({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should update a Choice', async () => {
const returnedFromService = { optionKey: 'BBBBBB', content: 'BBBBBB', displayOrder: 1, correct: true, ...elemDefault };
const expected = { ...returnedFromService };
axiosStub.put.mockResolvedValue({ data: returnedFromService });
return service.update(expected).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not update a Choice', async () => {
axiosStub.put.mockRejectedValue(error);
return service
.update({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should partial update a Choice', async () => {
const patchObject = { content: 'BBBBBB', ...new Choice() };
const returnedFromService = Object.assign(patchObject, elemDefault);
const expected = { ...returnedFromService };
axiosStub.patch.mockResolvedValue({ data: returnedFromService });
return service.partialUpdate(patchObject).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not partial update a Choice', async () => {
axiosStub.patch.mockRejectedValue(error);
return service
.partialUpdate({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should return a list of Choice', async () => {
const returnedFromService = { optionKey: 'BBBBBB', content: 'BBBBBB', displayOrder: 1, correct: true, ...elemDefault };
const expected = { ...returnedFromService };
axiosStub.get.mockResolvedValue([returnedFromService]);
return service.retrieve().then(res => {
expect(res).toContainEqual(expected);
});
});
it('should not return a list of Choice', async () => {
axiosStub.get.mockRejectedValue(error);
return service
.retrieve()
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should delete a Choice', async () => {
axiosStub.delete.mockResolvedValue({ ok: true });
return service.delete(123).then(res => {
expect(res.ok).toBeTruthy();
});
});
it('should not delete a Choice', async () => {
axiosStub.delete.mockRejectedValue(error);
return service
.delete(123)
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
});
});
});
@@ -0,0 +1,85 @@
import axios from 'axios';
import { type IChoice } from '@/shared/model/choice.model';
const baseApiUrl = 'api/choices';
export default class ChoiceService {
find(id: number): Promise<IChoice> {
return new Promise<IChoice>((resolve, reject) => {
axios
.get(`${baseApiUrl}/${id}`)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
retrieve(): Promise<any> {
return new Promise<any>((resolve, reject) => {
axios
.get(baseApiUrl)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}
delete(id: number): Promise<any> {
return new Promise<any>((resolve, reject) => {
axios
.delete(`${baseApiUrl}/${id}`)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}
create(entity: IChoice): Promise<IChoice> {
return new Promise<IChoice>((resolve, reject) => {
axios
.post(baseApiUrl, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
update(entity: IChoice): Promise<IChoice> {
return new Promise<IChoice>((resolve, reject) => {
axios
.put(`${baseApiUrl}/${entity.id}`, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
partialUpdate(entity: IChoice): Promise<IChoice> {
return new Promise<IChoice>((resolve, reject) => {
axios
.patch(`${baseApiUrl}/${entity.id}`, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
}
@@ -0,0 +1,117 @@
<template>
<div>
<h2 id="page-heading" data-cy="ChoiceHeading">
<span id="choice">{{ t$('questionBankApp.choice.home.title') }}</span>
<div class="d-flex justify-content-end">
<button class="btn btn-info me-2" @click="handleSyncList" :disabled="isFetching">
<font-awesome-icon icon="sync" :spin="isFetching"></font-awesome-icon>
<span>{{ t$('questionBankApp.choice.home.refreshListLabel') }}</span>
</button>
<router-link :to="{ name: 'ChoiceCreate' }" custom v-slot="{ navigate }">
<button
@click="navigate"
id="jh-create-entity"
data-cy="entityCreateButton"
class="btn btn-primary jh-create-entity create-choice"
>
<font-awesome-icon icon="plus"></font-awesome-icon>
<span>{{ t$('questionBankApp.choice.home.createLabel') }}</span>
</button>
</router-link>
</div>
</h2>
<br />
<div class="alert alert-warning" v-if="!isFetching && choices?.length === 0">
<span>{{ t$('questionBankApp.choice.home.notFound') }}</span>
</div>
<div class="table-responsive" v-if="choices?.length > 0">
<table class="table table-striped" aria-describedby="choices">
<thead>
<tr>
<th scope="col">
<span>{{ t$('global.field.id') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.choice.optionKey') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.choice.content') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.choice.displayOrder') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.choice.correct') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.choice.question') }}</span>
</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="choice in choices" :key="choice.id" data-cy="entityTable">
<td>
<router-link :to="{ name: 'ChoiceView', params: { choiceId: choice.id } }">{{ choice.id }}</router-link>
</td>
<td>{{ t$('questionBankApp.OptionKey.' + choice.optionKey) }}</td>
<td>{{ choice.content }}</td>
<td>{{ choice.displayOrder }}</td>
<td>{{ choice.correct }}</td>
<td>
<div v-if="choice.question">
<router-link :to="{ name: 'QuestionView', params: { questionId: choice.question.id } }">{{
choice.question.id
}}</router-link>
</div>
</td>
<td class="text-end">
<div class="btn-group">
<router-link :to="{ name: 'ChoiceView', params: { choiceId: choice.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-info btn-sm details" data-cy="entityDetailsButton">
<font-awesome-icon icon="eye"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.view') }}</span>
</button>
</router-link>
<router-link :to="{ name: 'ChoiceEdit', params: { choiceId: choice.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-primary btn-sm edit" data-cy="entityEditButton">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.edit') }}</span>
</button>
</router-link>
<b-button @click="prepareRemove(choice)" variant="danger" class="btn btn-sm" data-cy="entityDeleteButton">
<font-awesome-icon icon="times"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.delete') }}</span>
</b-button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<b-modal ref="removeEntity" id="removeEntity">
<template #title>
<span id="questionBankApp.choice.delete.question" data-cy="choiceDeleteDialogHeading">{{ t$('entity.delete.title') }}</span>
</template>
<div class="modal-body">
<p id="jhi-delete-choice-heading">{{ t$('questionBankApp.choice.delete.question', { id: removeId }) }}</p>
</div>
<template #footer>
<div>
<button type="button" class="btn btn-secondary" @click="closeDialog()">{{ t$('entity.action.cancel') }}</button>
<button
type="button"
class="btn btn-primary"
id="jhi-confirm-delete-choice"
data-cy="entityConfirmDeleteButton"
@click="removeChoice"
>
{{ t$('entity.action.delete') }}
</button>
</div>
</template>
</b-modal>
</div>
</template>
<script lang="ts" src="./choice.component.ts"></script>
@@ -1,5 +1,25 @@
<template> <template>
<div> <div>
<b-dropdown-item to="/subject">
<font-awesome-icon icon="asterisk" />
<span>{{ t$('global.menu.entities.subject') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/question">
<font-awesome-icon icon="asterisk" />
<span>{{ t$('global.menu.entities.question') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/choice">
<font-awesome-icon icon="asterisk" />
<span>{{ t$('global.menu.entities.choice') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/exam-attempt">
<font-awesome-icon icon="asterisk" />
<span>{{ t$('global.menu.entities.examAttempt') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/exam-answer">
<font-awesome-icon icon="asterisk" />
<span>{{ t$('global.menu.entities.examAnswer') }}</span>
</b-dropdown-item>
<!-- jhipster-needle-add-entity-to-menu - JHipster will add entities to the menu here --> <!-- jhipster-needle-add-entity-to-menu - JHipster will add entities to the menu here -->
</div> </div>
</template> </template>
@@ -1,12 +1,23 @@
import { defineComponent, provide } from 'vue'; import { defineComponent, provide } from 'vue';
import UserService from '@/entities/user/user.service'; import UserService from '@/entities/user/user.service';
import ChoiceService from './choice/choice.service';
import ExamAnswerService from './exam-answer/exam-answer.service';
import ExamAttemptService from './exam-attempt/exam-attempt.service';
import QuestionService from './question/question.service';
import SubjectService from './subject/subject.service';
// jhipster-needle-add-entity-service-to-entities-component-import - JHipster will import entities services here // jhipster-needle-add-entity-service-to-entities-component-import - JHipster will import entities services here
export default defineComponent({ export default defineComponent({
name: 'Entities', name: 'Entities',
setup() { setup() {
provide('userService', () => new UserService()); provide('userService', () => new UserService());
provide('subjectService', () => new SubjectService());
provide('questionService', () => new QuestionService());
provide('choiceService', () => new ChoiceService());
provide('examAttemptService', () => new ExamAttemptService());
provide('examAnswerService', () => new ExamAnswerService());
// jhipster-needle-add-entity-service-to-entities-component - JHipster will import entities services here // jhipster-needle-add-entity-service-to-entities-component - JHipster will import entities services here
}, },
}); });
@@ -0,0 +1,91 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import ExamAnswerDetails from './exam-answer-details.vue';
type ExamAnswerDetailsComponentType = InstanceType<typeof ExamAnswerDetails>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const examAnswerSample = { id: 123 };
describe('Component Tests', () => {
let alertService: AlertService;
afterEach(() => {
vi.resetAllMocks();
});
describe('ExamAnswer Management Detail Component', () => {
let examAnswerServiceStub: any;
let mountOptions: MountingOptions<ExamAnswerDetailsComponentType>['global'];
beforeEach(() => {
route = {};
examAnswerServiceStub = {
find: vi.fn(),
};
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'router-link': true,
},
provide: {
alertService,
examAnswerService: () => examAnswerServiceStub,
},
};
});
describe('Navigate to details', () => {
it('Should call load all on init', async () => {
// GIVEN
examAnswerServiceStub.find.mockResolvedValue(examAnswerSample);
route = {
params: {
examAnswerId: `${123}`,
},
};
const wrapper = shallowMount(ExamAnswerDetails, { global: mountOptions });
const comp = wrapper.vm;
// WHEN
await comp.$nextTick();
// THEN
expect(comp.examAnswer).toMatchObject(examAnswerSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
examAnswerServiceStub.find.mockResolvedValue(examAnswerSample);
const wrapper = shallowMount(ExamAnswerDetails, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,42 @@
import { type Ref, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useAlertService } from '@/shared/alert/alert.service';
import { type IExamAnswer } from '@/shared/model/exam-answer.model';
import ExamAnswerService from './exam-answer.service';
export default defineComponent({
name: 'ExamAnswerDetails',
setup() {
const examAnswerService = inject('examAnswerService', () => new ExamAnswerService());
const alertService = inject('alertService', () => useAlertService(), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const examAnswer: Ref<IExamAnswer> = ref({});
const retrieveExamAnswer = async examAnswerId => {
try {
const res = await examAnswerService().find(examAnswerId);
examAnswer.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.examAnswerId) {
retrieveExamAnswer(route.params.examAnswerId);
}
return {
examAnswer,
previousState,
t$: useI18n().t,
};
},
});
@@ -0,0 +1,84 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<div v-if="examAnswer">
<h2 class="jh-entity-heading" data-cy="examAnswerDetailsHeading">
<span>{{ t$('questionBankApp.examAnswer.detail.title') }}</span> {{ examAnswer.id }}
</h2>
<dl class="row-md jh-entity-details">
<dt>
<span>{{ t$('questionBankApp.examAnswer.questionTextSnapshot') }}</span>
</dt>
<dd>
<span>{{ examAnswer.questionTextSnapshot }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAnswer.explanationSnapshot') }}</span>
</dt>
<dd>
<span>{{ examAnswer.explanationSnapshot }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAnswer.selectedOption') }}</span>
</dt>
<dd>
<span>{{ t$('questionBankApp.OptionKey.' + examAnswer.selectedOption) }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAnswer.selectedContentSnapshot') }}</span>
</dt>
<dd>
<span>{{ examAnswer.selectedContentSnapshot }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAnswer.correct') }}</span>
</dt>
<dd>
<span>{{ examAnswer.correct }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAnswer.awardedPoints') }}</span>
</dt>
<dd>
<span>{{ examAnswer.awardedPoints }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAnswer.question') }}</span>
</dt>
<dd>
<div v-if="examAnswer.question">
<router-link :to="{ name: 'QuestionView', params: { questionId: examAnswer.question.id } }">{{
examAnswer.question.id
}}</router-link>
</div>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAnswer.attempt') }}</span>
</dt>
<dd>
<div v-if="examAnswer.attempt">
<router-link :to="{ name: 'ExamAttemptView', params: { examAttemptId: examAnswer.attempt.id } }">{{
examAnswer.attempt.id
}}</router-link>
</div>
</dd>
</dl>
<button type="submit" @click.prevent="previousState()" class="btn btn-info" data-cy="entityDetailsBackButton">
<font-awesome-icon icon="arrow-left"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.back') }}</span>
</button>
<router-link
v-if="examAnswer.id"
:to="{ name: 'ExamAnswerEdit', params: { examAnswerId: examAnswer.id } }"
custom
v-slot="{ navigate }"
>
<button @click="navigate" class="btn btn-primary">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.edit') }}</span>
</button>
</router-link>
</div>
</div>
</div>
</template>
<script lang="ts" src="./exam-answer-details.component.ts"></script>
@@ -0,0 +1,142 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import ExamAnswerUpdate from './exam-answer-update.vue';
type ExamAnswerUpdateComponentType = InstanceType<typeof ExamAnswerUpdate>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const examAnswerSample = { id: 123 };
describe('Component Tests', () => {
let mountOptions: MountingOptions<ExamAnswerUpdateComponentType>['global'];
let alertService: AlertService;
describe('ExamAnswer Management Update Component', () => {
let comp: ExamAnswerUpdateComponentType;
let examAnswerServiceStub: any;
beforeEach(() => {
route = {};
examAnswerServiceStub = {
retrieve: vi.fn(),
find: vi.fn(),
update: vi.fn(),
create: vi.fn(),
};
examAnswerServiceStub.retrieve.mockResolvedValueOnce([]);
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'b-input-group': true,
'b-input-group-prepend': true,
'b-form-datepicker': true,
'b-form-input': true,
},
provide: {
alertService,
examAnswerService: () => examAnswerServiceStub,
questionService: () => ({
retrieve: vi.fn().mockResolvedValue({}),
}),
examAttemptService: () => ({
retrieve: vi.fn().mockResolvedValue({}),
}),
},
};
});
afterEach(() => {
vi.resetAllMocks();
});
describe('save', () => {
it('Should call update service on save for existing entity', async () => {
// GIVEN
const wrapper = shallowMount(ExamAnswerUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.examAnswer = examAnswerSample;
examAnswerServiceStub.update.mockResolvedValue(examAnswerSample);
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(examAnswerServiceStub.update).toHaveBeenCalledWith(examAnswerSample);
expect(comp.isSaving).toEqual(false);
});
it('Should call create service on save for new entity', async () => {
// GIVEN
const entity = {};
examAnswerServiceStub.create.mockResolvedValue(entity);
const wrapper = shallowMount(ExamAnswerUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.examAnswer = entity;
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(examAnswerServiceStub.create).toHaveBeenCalledWith(entity);
expect(comp.isSaving).toEqual(false);
});
});
describe('Before route enter', () => {
it('Should retrieve data', async () => {
// GIVEN
examAnswerServiceStub.find.mockResolvedValue(examAnswerSample);
examAnswerServiceStub.retrieve.mockResolvedValue([examAnswerSample]);
// WHEN
route = {
params: {
examAnswerId: `${examAnswerSample.id}`,
},
};
const wrapper = shallowMount(ExamAnswerUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(comp.examAnswer).toMatchObject(examAnswerSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
examAnswerServiceStub.find.mockResolvedValue(examAnswerSample);
const wrapper = shallowMount(ExamAnswerUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,144 @@
import { type Ref, computed, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useVuelidate } from '@vuelidate/core';
import ExamAttemptService from '@/entities/exam-attempt/exam-attempt.service';
import QuestionService from '@/entities/question/question.service';
import { useAlertService } from '@/shared/alert/alert.service';
import { useValidation } from '@/shared/composables';
import { OptionKey } from '@/shared/model/enumerations/option-key.model';
import { ExamAnswer, type IExamAnswer } from '@/shared/model/exam-answer.model';
import { type IExamAttempt } from '@/shared/model/exam-attempt.model';
import { type IQuestion } from '@/shared/model/question.model';
import ExamAnswerService from './exam-answer.service';
export default defineComponent({
name: 'ExamAnswerUpdate',
setup() {
const examAnswerService = inject('examAnswerService', () => new ExamAnswerService());
const alertService = inject('alertService', () => useAlertService(), true);
const examAnswer: Ref<IExamAnswer> = ref(new ExamAnswer());
const questionService = inject('questionService', () => new QuestionService());
const questions: Ref<IQuestion[]> = ref([]);
const examAttemptService = inject('examAttemptService', () => new ExamAttemptService());
const examAttempts: Ref<IExamAttempt[]> = ref([]);
const optionKeyValues: Ref<string[]> = ref(Object.keys(OptionKey));
const isSaving = ref(false);
const currentLanguage = inject('currentLanguage', () => computed(() => navigator.language ?? 'en'), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const retrieveExamAnswer = async examAnswerId => {
try {
const res = await examAnswerService().find(examAnswerId);
examAnswer.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.examAnswerId) {
retrieveExamAnswer(route.params.examAnswerId);
}
const initRelationships = () => {
questionService()
.retrieve()
.then(res => {
questions.value = res.data;
});
examAttemptService()
.retrieve()
.then(res => {
examAttempts.value = res.data;
});
};
initRelationships();
const { t: t$ } = useI18n();
const validations = useValidation();
const validationRules = {
questionTextSnapshot: {
required: validations.required(t$('entity.validation.required').toString()),
maxLength: validations.maxLength(t$('entity.validation.maxlength', { max: 2000 }).toString(), 2000),
},
explanationSnapshot: {
maxLength: validations.maxLength(t$('entity.validation.maxlength', { max: 2000 }).toString(), 2000),
},
selectedOption: {},
selectedContentSnapshot: {
maxLength: validations.maxLength(t$('entity.validation.maxlength', { max: 500 }).toString(), 500),
},
correct: {},
awardedPoints: {
integer: validations.integer(t$('entity.validation.number').toString()),
min: validations.minValue(t$('entity.validation.min', { min: 0 }).toString(), 0),
},
question: {
required: validations.required(t$('entity.validation.required').toString()),
},
attempt: {
required: validations.required(t$('entity.validation.required').toString()),
},
};
const v$ = useVuelidate(validationRules, examAnswer as any);
v$.value.$validate();
return {
examAnswerService,
alertService,
examAnswer,
previousState,
optionKeyValues,
isSaving,
currentLanguage,
questions,
examAttempts,
v$,
t$,
};
},
created(): void {},
methods: {
save(): void {
this.isSaving = true;
if (this.examAnswer.id) {
this.examAnswerService()
.update(this.examAnswer)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showInfo(this.t$('questionBankApp.examAnswer.updated', { param: param.id }));
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
} else {
this.examAnswerService()
.create(this.examAnswer)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showSuccess(this.t$('questionBankApp.examAnswer.created', { param: param.id }).toString());
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
}
},
},
});
@@ -0,0 +1,176 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<form name="editForm" novalidate @submit.prevent="save()">
<h2 id="questionBankApp.examAnswer.home.createOrEditLabel" data-cy="ExamAnswerCreateUpdateHeading">
{{ t$('questionBankApp.examAnswer.home.createOrEditLabel') }}
</h2>
<div>
<div class="mb-3" v-if="examAnswer.id">
<label for="id">{{ t$('global.field.id') }}</label>
<input type="text" class="form-control" id="id" name="id" v-model="examAnswer.id" readonly />
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-answer-questionTextSnapshot">{{
t$('questionBankApp.examAnswer.questionTextSnapshot')
}}</label>
<input
type="text"
class="form-control"
name="questionTextSnapshot"
id="exam-answer-questionTextSnapshot"
data-cy="questionTextSnapshot"
:class="{ valid: !v$.questionTextSnapshot.$invalid, invalid: v$.questionTextSnapshot.$invalid }"
v-model="v$.questionTextSnapshot.$model"
required
/>
<div v-if="v$.questionTextSnapshot.$anyDirty && v$.questionTextSnapshot.$invalid">
<small class="form-text text-danger" v-for="error of v$.questionTextSnapshot.$errors" :key="error.$uid">{{
error.$message
}}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-answer-explanationSnapshot">{{
t$('questionBankApp.examAnswer.explanationSnapshot')
}}</label>
<input
type="text"
class="form-control"
name="explanationSnapshot"
id="exam-answer-explanationSnapshot"
data-cy="explanationSnapshot"
:class="{ valid: !v$.explanationSnapshot.$invalid, invalid: v$.explanationSnapshot.$invalid }"
v-model="v$.explanationSnapshot.$model"
/>
<div v-if="v$.explanationSnapshot.$anyDirty && v$.explanationSnapshot.$invalid">
<small class="form-text text-danger" v-for="error of v$.explanationSnapshot.$errors" :key="error.$uid">{{
error.$message
}}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-answer-selectedOption">{{ t$('questionBankApp.examAnswer.selectedOption') }}</label>
<select
class="form-control"
name="selectedOption"
:class="{ valid: !v$.selectedOption.$invalid, invalid: v$.selectedOption.$invalid }"
v-model="v$.selectedOption.$model"
id="exam-answer-selectedOption"
data-cy="selectedOption"
>
<option
v-for="optionKey in optionKeyValues"
:key="optionKey"
:value="optionKey"
:label="t$('questionBankApp.OptionKey.' + optionKey)"
>
{{ optionKey }}
</option>
</select>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-answer-selectedContentSnapshot">{{
t$('questionBankApp.examAnswer.selectedContentSnapshot')
}}</label>
<input
type="text"
class="form-control"
name="selectedContentSnapshot"
id="exam-answer-selectedContentSnapshot"
data-cy="selectedContentSnapshot"
:class="{ valid: !v$.selectedContentSnapshot.$invalid, invalid: v$.selectedContentSnapshot.$invalid }"
v-model="v$.selectedContentSnapshot.$model"
/>
<div v-if="v$.selectedContentSnapshot.$anyDirty && v$.selectedContentSnapshot.$invalid">
<small class="form-text text-danger" v-for="error of v$.selectedContentSnapshot.$errors" :key="error.$uid">{{
error.$message
}}</small>
</div>
</div>
<div class="mb-3 form-check">
<input
type="checkbox"
class="form-check-input"
name="correct"
id="exam-answer-correct"
data-cy="correct"
:class="{ valid: !v$.correct.$invalid, invalid: v$.correct.$invalid }"
v-model="v$.correct.$model"
/>
<label class="form-check-label" for="exam-answer-correct">{{ t$('questionBankApp.examAnswer.correct') }}</label>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-answer-awardedPoints">{{ t$('questionBankApp.examAnswer.awardedPoints') }}</label>
<input
type="number"
class="form-control"
name="awardedPoints"
id="exam-answer-awardedPoints"
data-cy="awardedPoints"
:class="{ valid: !v$.awardedPoints.$invalid, invalid: v$.awardedPoints.$invalid }"
v-model.number="v$.awardedPoints.$model"
/>
<div v-if="v$.awardedPoints.$anyDirty && v$.awardedPoints.$invalid">
<small class="form-text text-danger" v-for="error of v$.awardedPoints.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-answer">{{ t$('questionBankApp.examAnswer.question') }}</label>
<select
class="form-control"
id="exam-answer-question"
data-cy="question"
name="question"
v-model="examAnswer.question"
required
>
<option v-if="!examAnswer.question" :value="null" selected></option>
<option
:value="examAnswer.question && questionOption.id === examAnswer.question.id ? examAnswer.question : questionOption"
v-for="questionOption in questions"
:key="questionOption.id"
>
{{ questionOption.id }}
</option>
</select>
</div>
<div v-if="v$.question.$anyDirty && v$.question.$invalid">
<small class="form-text text-danger" v-for="error of v$.question.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-answer">{{ t$('questionBankApp.examAnswer.attempt') }}</label>
<select class="form-control" id="exam-answer-attempt" data-cy="attempt" name="attempt" v-model="examAnswer.attempt" required>
<option v-if="!examAnswer.attempt" :value="null" selected></option>
<option
:value="examAnswer.attempt && examAttemptOption.id === examAnswer.attempt.id ? examAnswer.attempt : examAttemptOption"
v-for="examAttemptOption in examAttempts"
:key="examAttemptOption.id"
>
{{ examAttemptOption.id }}
</option>
</select>
</div>
<div v-if="v$.attempt.$anyDirty && v$.attempt.$invalid">
<small class="form-text text-danger" v-for="error of v$.attempt.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div>
<button type="button" id="cancel-save" data-cy="entityCreateCancelButton" class="btn btn-secondary" @click="previousState()">
<font-awesome-icon icon="ban"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.cancel') }}</span>
</button>
<button
type="submit"
id="save-entity"
data-cy="entityCreateSaveButton"
:disabled="v$.$invalid || isSaving"
class="btn btn-primary"
>
<font-awesome-icon icon="save"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.save') }}</span>
</button>
</div>
</form>
</div>
</div>
</template>
<script lang="ts" src="./exam-answer-update.component.ts"></script>
@@ -0,0 +1,103 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import ExamAnswer from './exam-answer.vue';
type ExamAnswerComponentType = InstanceType<typeof ExamAnswer>;
const bModalStub = {
render: () => {},
methods: {
hide: () => {},
show: () => {},
},
};
describe('Component Tests', () => {
let alertService: AlertService;
describe('ExamAnswer Management Component', () => {
let examAnswerServiceStub: any;
let mountOptions: MountingOptions<ExamAnswerComponentType>['global'];
beforeEach(() => {
examAnswerServiceStub = {
retrieve: vi.fn(),
delete: vi.fn(),
};
examAnswerServiceStub.retrieve.mockResolvedValue({ headers: {} });
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
bModal: bModalStub as any,
'font-awesome-icon': true,
'b-badge': true,
'b-button': true,
'router-link': true,
},
directives: {
'b-modal': {},
},
provide: {
alertService,
examAnswerService: () => examAnswerServiceStub,
},
};
});
describe('Mount', () => {
it('Should call load all on init', async () => {
// GIVEN
examAnswerServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
const wrapper = shallowMount(ExamAnswer, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(examAnswerServiceStub.retrieve).toHaveBeenCalledOnce();
expect(comp.examAnswers[0]).toEqual(expect.objectContaining({ id: 123 }));
});
});
describe('Handles', () => {
let comp: ExamAnswerComponentType;
beforeEach(async () => {
const wrapper = shallowMount(ExamAnswer, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
examAnswerServiceStub.retrieve.mockReset();
examAnswerServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [] });
});
it('Should call delete service on confirmDelete', async () => {
// GIVEN
examAnswerServiceStub.delete.mockResolvedValue({});
// WHEN
comp.prepareRemove({ id: 123 });
comp.removeExamAnswer();
await comp.$nextTick(); // clear components
// THEN
expect(examAnswerServiceStub.delete).toHaveBeenCalled();
// THEN
await comp.$nextTick(); // handle component clear watch
expect(examAnswerServiceStub.retrieve).toHaveBeenCalledTimes(1);
});
});
});
});
@@ -0,0 +1,78 @@
import { type Ref, defineComponent, inject, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlertService } from '@/shared/alert/alert.service';
import { type IExamAnswer } from '@/shared/model/exam-answer.model';
import ExamAnswerService from './exam-answer.service';
export default defineComponent({
name: 'ExamAnswer',
setup() {
const { t: t$ } = useI18n();
const examAnswerService = inject('examAnswerService', () => new ExamAnswerService());
const alertService = inject('alertService', () => useAlertService(), true);
const examAnswers: Ref<IExamAnswer[]> = ref([]);
const isFetching = ref(false);
const clear = () => {};
const retrieveExamAnswers = async () => {
isFetching.value = true;
try {
const res = await examAnswerService().retrieve();
examAnswers.value = res.data;
} catch (err) {
alertService.showHttpError(err.response);
} finally {
isFetching.value = false;
}
};
const handleSyncList = () => {
retrieveExamAnswers();
};
onMounted(async () => {
await retrieveExamAnswers();
});
const removeId: Ref<number> = ref(null);
const removeEntity = ref<any>(null);
const prepareRemove = (instance: IExamAnswer) => {
removeId.value = instance.id;
removeEntity.value.show();
};
const closeDialog = () => {
removeEntity.value.hide();
};
const removeExamAnswer = async () => {
try {
await examAnswerService().delete(removeId.value);
const message = t$('questionBankApp.examAnswer.deleted', { param: removeId.value }).toString();
alertService.showInfo(message, { variant: 'danger' });
removeId.value = null;
retrieveExamAnswers();
closeDialog();
} catch (error) {
alertService.showHttpError(error.response);
}
};
return {
examAnswers,
handleSyncList,
isFetching,
retrieveExamAnswers,
clear,
removeId,
removeEntity,
prepareRemove,
closeDialog,
removeExamAnswer,
t$,
};
},
});
@@ -0,0 +1,177 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import axios from 'axios';
import { ExamAnswer } from '@/shared/model/exam-answer.model';
import ExamAnswerService from './exam-answer.service';
const error = {
response: {
status: null,
data: {
type: null,
},
},
};
const axiosStub = {
get: vi.spyOn(axios, 'get'),
post: vi.spyOn(axios, 'post'),
put: vi.spyOn(axios, 'put'),
patch: vi.spyOn(axios, 'patch'),
delete: vi.spyOn(axios, 'delete'),
};
describe('Service Tests', () => {
describe('ExamAnswer Service', () => {
let service: ExamAnswerService;
let elemDefault;
beforeEach(() => {
service = new ExamAnswerService();
elemDefault = new ExamAnswer(123, 'AAAAAAA', 'AAAAAAA', 'A', 'AAAAAAA', false, 0);
});
describe('Service methods', () => {
it('should find an element', async () => {
const returnedFromService = { ...elemDefault };
axiosStub.get.mockResolvedValue({ data: returnedFromService });
return service.find(123).then(res => {
expect(res).toMatchObject(elemDefault);
});
});
it('should not find an element', async () => {
axiosStub.get.mockRejectedValue(error);
return service
.find(123)
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should create a ExamAnswer', async () => {
const returnedFromService = { id: 123, ...elemDefault };
const expected = { ...returnedFromService };
axiosStub.post.mockResolvedValue({ data: returnedFromService });
return service.create({}).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not create a ExamAnswer', async () => {
axiosStub.post.mockRejectedValue(error);
return service
.create({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should update a ExamAnswer', async () => {
const returnedFromService = {
questionTextSnapshot: 'BBBBBB',
explanationSnapshot: 'BBBBBB',
selectedOption: 'BBBBBB',
selectedContentSnapshot: 'BBBBBB',
correct: true,
awardedPoints: 1,
...elemDefault,
};
const expected = { ...returnedFromService };
axiosStub.put.mockResolvedValue({ data: returnedFromService });
return service.update(expected).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not update a ExamAnswer', async () => {
axiosStub.put.mockRejectedValue(error);
return service
.update({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should partial update a ExamAnswer', async () => {
const patchObject = { questionTextSnapshot: 'BBBBBB', explanationSnapshot: 'BBBBBB', ...new ExamAnswer() };
const returnedFromService = Object.assign(patchObject, elemDefault);
const expected = { ...returnedFromService };
axiosStub.patch.mockResolvedValue({ data: returnedFromService });
return service.partialUpdate(patchObject).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not partial update a ExamAnswer', async () => {
axiosStub.patch.mockRejectedValue(error);
return service
.partialUpdate({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should return a list of ExamAnswer', async () => {
const returnedFromService = {
questionTextSnapshot: 'BBBBBB',
explanationSnapshot: 'BBBBBB',
selectedOption: 'BBBBBB',
selectedContentSnapshot: 'BBBBBB',
correct: true,
awardedPoints: 1,
...elemDefault,
};
const expected = { ...returnedFromService };
axiosStub.get.mockResolvedValue([returnedFromService]);
return service.retrieve().then(res => {
expect(res).toContainEqual(expected);
});
});
it('should not return a list of ExamAnswer', async () => {
axiosStub.get.mockRejectedValue(error);
return service
.retrieve()
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should delete a ExamAnswer', async () => {
axiosStub.delete.mockResolvedValue({ ok: true });
return service.delete(123).then(res => {
expect(res.ok).toBeTruthy();
});
});
it('should not delete a ExamAnswer', async () => {
axiosStub.delete.mockRejectedValue(error);
return service
.delete(123)
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
});
});
});
@@ -0,0 +1,85 @@
import axios from 'axios';
import { type IExamAnswer } from '@/shared/model/exam-answer.model';
const baseApiUrl = 'api/exam-answers';
export default class ExamAnswerService {
find(id: number): Promise<IExamAnswer> {
return new Promise<IExamAnswer>((resolve, reject) => {
axios
.get(`${baseApiUrl}/${id}`)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
retrieve(): Promise<any> {
return new Promise<any>((resolve, reject) => {
axios
.get(baseApiUrl)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}
delete(id: number): Promise<any> {
return new Promise<any>((resolve, reject) => {
axios
.delete(`${baseApiUrl}/${id}`)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}
create(entity: IExamAnswer): Promise<IExamAnswer> {
return new Promise<IExamAnswer>((resolve, reject) => {
axios
.post(baseApiUrl, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
update(entity: IExamAnswer): Promise<IExamAnswer> {
return new Promise<IExamAnswer>((resolve, reject) => {
axios
.put(`${baseApiUrl}/${entity.id}`, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
partialUpdate(entity: IExamAnswer): Promise<IExamAnswer> {
return new Promise<IExamAnswer>((resolve, reject) => {
axios
.patch(`${baseApiUrl}/${entity.id}`, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
}
@@ -0,0 +1,135 @@
<template>
<div>
<h2 id="page-heading" data-cy="ExamAnswerHeading">
<span id="exam-answer">{{ t$('questionBankApp.examAnswer.home.title') }}</span>
<div class="d-flex justify-content-end">
<button class="btn btn-info me-2" @click="handleSyncList" :disabled="isFetching">
<font-awesome-icon icon="sync" :spin="isFetching"></font-awesome-icon>
<span>{{ t$('questionBankApp.examAnswer.home.refreshListLabel') }}</span>
</button>
<router-link :to="{ name: 'ExamAnswerCreate' }" custom v-slot="{ navigate }">
<button
@click="navigate"
id="jh-create-entity"
data-cy="entityCreateButton"
class="btn btn-primary jh-create-entity create-exam-answer"
>
<font-awesome-icon icon="plus"></font-awesome-icon>
<span>{{ t$('questionBankApp.examAnswer.home.createLabel') }}</span>
</button>
</router-link>
</div>
</h2>
<br />
<div class="alert alert-warning" v-if="!isFetching && examAnswers?.length === 0">
<span>{{ t$('questionBankApp.examAnswer.home.notFound') }}</span>
</div>
<div class="table-responsive" v-if="examAnswers?.length > 0">
<table class="table table-striped" aria-describedby="examAnswers">
<thead>
<tr>
<th scope="col">
<span>{{ t$('global.field.id') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.questionTextSnapshot') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.explanationSnapshot') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.selectedOption') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.selectedContentSnapshot') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.correct') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.awardedPoints') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.question') }}</span>
</th>
<th scope="col">
<span>{{ t$('questionBankApp.examAnswer.attempt') }}</span>
</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="examAnswer in examAnswers" :key="examAnswer.id" data-cy="entityTable">
<td>
<router-link :to="{ name: 'ExamAnswerView', params: { examAnswerId: examAnswer.id } }">{{ examAnswer.id }}</router-link>
</td>
<td>{{ examAnswer.questionTextSnapshot }}</td>
<td>{{ examAnswer.explanationSnapshot }}</td>
<td>{{ t$('questionBankApp.OptionKey.' + examAnswer.selectedOption) }}</td>
<td>{{ examAnswer.selectedContentSnapshot }}</td>
<td>{{ examAnswer.correct }}</td>
<td>{{ examAnswer.awardedPoints }}</td>
<td>
<div v-if="examAnswer.question">
<router-link :to="{ name: 'QuestionView', params: { questionId: examAnswer.question.id } }">{{
examAnswer.question.id
}}</router-link>
</div>
</td>
<td>
<div v-if="examAnswer.attempt">
<router-link :to="{ name: 'ExamAttemptView', params: { examAttemptId: examAnswer.attempt.id } }">{{
examAnswer.attempt.id
}}</router-link>
</div>
</td>
<td class="text-end">
<div class="btn-group">
<router-link :to="{ name: 'ExamAnswerView', params: { examAnswerId: examAnswer.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-info btn-sm details" data-cy="entityDetailsButton">
<font-awesome-icon icon="eye"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.view') }}</span>
</button>
</router-link>
<router-link :to="{ name: 'ExamAnswerEdit', params: { examAnswerId: examAnswer.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-primary btn-sm edit" data-cy="entityEditButton">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.edit') }}</span>
</button>
</router-link>
<b-button @click="prepareRemove(examAnswer)" variant="danger" class="btn btn-sm" data-cy="entityDeleteButton">
<font-awesome-icon icon="times"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.delete') }}</span>
</b-button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<b-modal ref="removeEntity" id="removeEntity">
<template #title>
<span id="questionBankApp.examAnswer.delete.question" data-cy="examAnswerDeleteDialogHeading">{{ t$('entity.delete.title') }}</span>
</template>
<div class="modal-body">
<p id="jhi-delete-examAnswer-heading">{{ t$('questionBankApp.examAnswer.delete.question', { id: removeId }) }}</p>
</div>
<template #footer>
<div>
<button type="button" class="btn btn-secondary" @click="closeDialog()">{{ t$('entity.action.cancel') }}</button>
<button
type="button"
class="btn btn-primary"
id="jhi-confirm-delete-examAnswer"
data-cy="entityConfirmDeleteButton"
@click="removeExamAnswer"
>
{{ t$('entity.action.delete') }}
</button>
</div>
</template>
</b-modal>
</div>
</template>
<script lang="ts" src="./exam-answer.component.ts"></script>
@@ -0,0 +1,91 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import ExamAttemptDetails from './exam-attempt-details.vue';
type ExamAttemptDetailsComponentType = InstanceType<typeof ExamAttemptDetails>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const examAttemptSample = { id: 123 };
describe('Component Tests', () => {
let alertService: AlertService;
afterEach(() => {
vi.resetAllMocks();
});
describe('ExamAttempt Management Detail Component', () => {
let examAttemptServiceStub: any;
let mountOptions: MountingOptions<ExamAttemptDetailsComponentType>['global'];
beforeEach(() => {
route = {};
examAttemptServiceStub = {
find: vi.fn(),
};
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'router-link': true,
},
provide: {
alertService,
examAttemptService: () => examAttemptServiceStub,
},
};
});
describe('Navigate to details', () => {
it('Should call load all on init', async () => {
// GIVEN
examAttemptServiceStub.find.mockResolvedValue(examAttemptSample);
route = {
params: {
examAttemptId: `${123}`,
},
};
const wrapper = shallowMount(ExamAttemptDetails, { global: mountOptions });
const comp = wrapper.vm;
// WHEN
await comp.$nextTick();
// THEN
expect(comp.examAttempt).toMatchObject(examAttemptSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
examAttemptServiceStub.find.mockResolvedValue(examAttemptSample);
const wrapper = shallowMount(ExamAttemptDetails, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,45 @@
import { type Ref, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useAlertService } from '@/shared/alert/alert.service';
import { useDateFormat } from '@/shared/composables';
import { type IExamAttempt } from '@/shared/model/exam-attempt.model';
import ExamAttemptService from './exam-attempt.service';
export default defineComponent({
name: 'ExamAttemptDetails',
setup() {
const dateFormat = useDateFormat();
const examAttemptService = inject('examAttemptService', () => new ExamAttemptService());
const alertService = inject('alertService', () => useAlertService(), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const examAttempt: Ref<IExamAttempt> = ref({});
const retrieveExamAttempt = async examAttemptId => {
try {
const res = await examAttemptService().find(examAttemptId);
examAttempt.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.examAttemptId) {
retrieveExamAttempt(route.params.examAttemptId);
}
return {
...dateFormat,
examAttempt,
previousState,
t$: useI18n().t,
};
},
});
@@ -0,0 +1,68 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<div v-if="examAttempt">
<h2 class="jh-entity-heading" data-cy="examAttemptDetailsHeading">
<span>{{ t$('questionBankApp.examAttempt.detail.title') }}</span> {{ examAttempt.id }}
</h2>
<dl class="row-md jh-entity-details">
<dt>
<span>{{ t$('questionBankApp.examAttempt.startedAt') }}</span>
</dt>
<dd>
<span v-if="examAttempt.startedAt">{{ formatDateLong(examAttempt.startedAt) }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAttempt.submittedAt') }}</span>
</dt>
<dd>
<span v-if="examAttempt.submittedAt">{{ formatDateLong(examAttempt.submittedAt) }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAttempt.status') }}</span>
</dt>
<dd>
<span>{{ t$('questionBankApp.AttemptStatus.' + examAttempt.status) }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAttempt.score') }}</span>
</dt>
<dd>
<span>{{ examAttempt.score }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAttempt.subject') }}</span>
</dt>
<dd>
<div v-if="examAttempt.subject">
<router-link :to="{ name: 'SubjectView', params: { subjectId: examAttempt.subject.id } }">{{
examAttempt.subject.name
}}</router-link>
</div>
</dd>
<dt>
<span>{{ t$('questionBankApp.examAttempt.user') }}</span>
</dt>
<dd>
{{ examAttempt.user ? examAttempt.user.login : '' }}
</dd>
</dl>
<button type="submit" @click.prevent="previousState()" class="btn btn-info" data-cy="entityDetailsBackButton">
<font-awesome-icon icon="arrow-left"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.back') }}</span>
</button>
<router-link
v-if="examAttempt.id"
:to="{ name: 'ExamAttemptEdit', params: { examAttemptId: examAttempt.id } }"
custom
v-slot="{ navigate }"
>
<button @click="navigate" class="btn btn-primary">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.edit') }}</span>
</button>
</router-link>
</div>
</div>
</div>
</template>
<script lang="ts" src="./exam-attempt-details.component.ts"></script>
@@ -0,0 +1,166 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import dayjs from 'dayjs';
import AlertService from '@/shared/alert/alert.service';
import { DATE_TIME_LONG_FORMAT } from '@/shared/composables/date-format';
import ExamAttemptUpdate from './exam-attempt-update.vue';
type ExamAttemptUpdateComponentType = InstanceType<typeof ExamAttemptUpdate>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const examAttemptSample = { id: 123 };
describe('Component Tests', () => {
let mountOptions: MountingOptions<ExamAttemptUpdateComponentType>['global'];
let alertService: AlertService;
describe('ExamAttempt Management Update Component', () => {
let comp: ExamAttemptUpdateComponentType;
let examAttemptServiceStub: any;
beforeEach(() => {
route = {};
examAttemptServiceStub = {
retrieve: vi.fn(),
find: vi.fn(),
update: vi.fn(),
create: vi.fn(),
};
examAttemptServiceStub.retrieve.mockResolvedValueOnce([]);
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'b-input-group': true,
'b-input-group-prepend': true,
'b-form-datepicker': true,
'b-form-input': true,
},
provide: {
alertService,
examAttemptService: () => examAttemptServiceStub,
subjectService: () => ({
retrieve: vi.fn().mockResolvedValue({}),
}),
userService: () => ({
retrieve: vi.fn().mockResolvedValue({}),
}),
},
};
});
afterEach(() => {
vi.resetAllMocks();
});
describe('load', () => {
beforeEach(() => {
const wrapper = shallowMount(ExamAttemptUpdate, { global: mountOptions });
comp = wrapper.vm;
});
it('Should convert date from string', () => {
// GIVEN
const date = new Date('2019-10-15T11:42:02Z');
// WHEN
const convertedDate = comp.convertDateTimeFromServer(date);
// THEN
expect(convertedDate).toEqual(dayjs(date).format(DATE_TIME_LONG_FORMAT));
});
it('Should not convert date if date is not present', () => {
expect(comp.convertDateTimeFromServer(null)).toBeNull();
});
});
describe('save', () => {
it('Should call update service on save for existing entity', async () => {
// GIVEN
const wrapper = shallowMount(ExamAttemptUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.examAttempt = examAttemptSample;
examAttemptServiceStub.update.mockResolvedValue(examAttemptSample);
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(examAttemptServiceStub.update).toHaveBeenCalledWith(examAttemptSample);
expect(comp.isSaving).toEqual(false);
});
it('Should call create service on save for new entity', async () => {
// GIVEN
const entity = {};
examAttemptServiceStub.create.mockResolvedValue(entity);
const wrapper = shallowMount(ExamAttemptUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.examAttempt = entity;
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(examAttemptServiceStub.create).toHaveBeenCalledWith(entity);
expect(comp.isSaving).toEqual(false);
});
});
describe('Before route enter', () => {
it('Should retrieve data', async () => {
// GIVEN
examAttemptServiceStub.find.mockResolvedValue(examAttemptSample);
examAttemptServiceStub.retrieve.mockResolvedValue([examAttemptSample]);
// WHEN
route = {
params: {
examAttemptId: `${examAttemptSample.id}`,
},
};
const wrapper = shallowMount(ExamAttemptUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(comp.examAttempt).toMatchObject(examAttemptSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
examAttemptServiceStub.find.mockResolvedValue(examAttemptSample);
const wrapper = shallowMount(ExamAttemptUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,140 @@
import { type Ref, computed, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useVuelidate } from '@vuelidate/core';
import SubjectService from '@/entities/subject/subject.service';
import UserService from '@/entities/user/user.service';
import { useAlertService } from '@/shared/alert/alert.service';
import { useDateFormat, useValidation } from '@/shared/composables';
import { AttemptStatus } from '@/shared/model/enumerations/attempt-status.model';
import { ExamAttempt, type IExamAttempt } from '@/shared/model/exam-attempt.model';
import { type ISubject } from '@/shared/model/subject.model';
import ExamAttemptService from './exam-attempt.service';
export default defineComponent({
name: 'ExamAttemptUpdate',
setup() {
const examAttemptService = inject('examAttemptService', () => new ExamAttemptService());
const alertService = inject('alertService', () => useAlertService(), true);
const examAttempt: Ref<IExamAttempt> = ref(new ExamAttempt());
const subjectService = inject('subjectService', () => new SubjectService());
const subjects: Ref<ISubject[]> = ref([]);
const userService = inject('userService', () => new UserService());
const users: Ref<Array<any>> = ref([]);
const attemptStatusValues: Ref<string[]> = ref(Object.keys(AttemptStatus));
const isSaving = ref(false);
const currentLanguage = inject('currentLanguage', () => computed(() => navigator.language ?? 'en'), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const retrieveExamAttempt = async examAttemptId => {
try {
const res = await examAttemptService().find(examAttemptId);
res.startedAt = new Date(res.startedAt);
res.submittedAt = new Date(res.submittedAt);
examAttempt.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.examAttemptId) {
retrieveExamAttempt(route.params.examAttemptId);
}
const initRelationships = () => {
subjectService()
.retrieve()
.then(res => {
subjects.value = res.data;
});
userService()
.retrieve()
.then(res => {
users.value = res.data;
});
};
initRelationships();
const { t: t$ } = useI18n();
const validations = useValidation();
const validationRules = {
startedAt: {
required: validations.required(t$('entity.validation.required').toString()),
},
submittedAt: {},
status: {
required: validations.required(t$('entity.validation.required').toString()),
},
score: {
integer: validations.integer(t$('entity.validation.number').toString()),
min: validations.minValue(t$('entity.validation.min', { min: 0 }).toString(), 0),
},
answers: {},
subject: {
required: validations.required(t$('entity.validation.required').toString()),
},
user: {
required: validations.required(t$('entity.validation.required').toString()),
},
};
const v$ = useVuelidate(validationRules, examAttempt as any);
v$.value.$validate();
return {
examAttemptService,
alertService,
examAttempt,
previousState,
attemptStatusValues,
isSaving,
currentLanguage,
subjects,
users,
v$,
...useDateFormat({ entityRef: examAttempt }),
t$,
};
},
created(): void {},
methods: {
save(): void {
this.isSaving = true;
if (this.examAttempt.id) {
this.examAttemptService()
.update(this.examAttempt)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showInfo(this.t$('questionBankApp.examAttempt.updated', { param: param.id }));
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
} else {
this.examAttemptService()
.create(this.examAttempt)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showSuccess(this.t$('questionBankApp.examAttempt.created', { param: param.id }).toString());
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
}
},
},
});
@@ -0,0 +1,137 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<form name="editForm" novalidate @submit.prevent="save()">
<h2 id="questionBankApp.examAttempt.home.createOrEditLabel" data-cy="ExamAttemptCreateUpdateHeading">
{{ t$('questionBankApp.examAttempt.home.createOrEditLabel') }}
</h2>
<div>
<div class="mb-3" v-if="examAttempt.id">
<label for="id">{{ t$('global.field.id') }}</label>
<input type="text" class="form-control" id="id" name="id" v-model="examAttempt.id" readonly />
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-attempt-startedAt">{{ t$('questionBankApp.examAttempt.startedAt') }}</label>
<div class="d-flex">
<input
id="exam-attempt-startedAt"
data-cy="startedAt"
type="datetime-local"
class="form-control"
name="startedAt"
:class="{ valid: !v$.startedAt.$invalid, invalid: v$.startedAt.$invalid }"
required
:value="convertDateTimeFromServer(v$.startedAt.$model)"
@change="updateInstantField('startedAt', $event)"
/>
</div>
<div v-if="v$.startedAt.$anyDirty && v$.startedAt.$invalid">
<small class="form-text text-danger" v-for="error of v$.startedAt.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-attempt-submittedAt">{{ t$('questionBankApp.examAttempt.submittedAt') }}</label>
<div class="d-flex">
<input
id="exam-attempt-submittedAt"
data-cy="submittedAt"
type="datetime-local"
class="form-control"
name="submittedAt"
:class="{ valid: !v$.submittedAt.$invalid, invalid: v$.submittedAt.$invalid }"
:value="convertDateTimeFromServer(v$.submittedAt.$model)"
@change="updateInstantField('submittedAt', $event)"
/>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-attempt-status">{{ t$('questionBankApp.examAttempt.status') }}</label>
<select
class="form-control"
name="status"
:class="{ valid: !v$.status.$invalid, invalid: v$.status.$invalid }"
v-model="v$.status.$model"
id="exam-attempt-status"
data-cy="status"
required
>
<option
v-for="attemptStatus in attemptStatusValues"
:key="attemptStatus"
:value="attemptStatus"
:label="t$('questionBankApp.AttemptStatus.' + attemptStatus)"
>
{{ attemptStatus }}
</option>
</select>
<div v-if="v$.status.$anyDirty && v$.status.$invalid">
<small class="form-text text-danger" v-for="error of v$.status.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-attempt-score">{{ t$('questionBankApp.examAttempt.score') }}</label>
<input
type="number"
class="form-control"
name="score"
id="exam-attempt-score"
data-cy="score"
:class="{ valid: !v$.score.$invalid, invalid: v$.score.$invalid }"
v-model.number="v$.score.$model"
/>
<div v-if="v$.score.$anyDirty && v$.score.$invalid">
<small class="form-text text-danger" v-for="error of v$.score.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-attempt">{{ t$('questionBankApp.examAttempt.subject') }}</label>
<select class="form-control" id="exam-attempt-subject" data-cy="subject" name="subject" v-model="examAttempt.subject" required>
<option v-if="!examAttempt.subject" :value="null" selected></option>
<option
:value="examAttempt.subject && subjectOption.id === examAttempt.subject.id ? examAttempt.subject : subjectOption"
v-for="subjectOption in subjects"
:key="subjectOption.id"
>
{{ subjectOption.name }}
</option>
</select>
</div>
<div v-if="v$.subject.$anyDirty && v$.subject.$invalid">
<small class="form-text text-danger" v-for="error of v$.subject.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
<div class="mb-3">
<label class="form-control-label" for="exam-attempt">{{ t$('questionBankApp.examAttempt.user') }}</label>
<select class="form-control" id="exam-attempt-user" data-cy="user" name="user" v-model="examAttempt.user" required>
<option v-if="!examAttempt.user" :value="null" selected></option>
<option
:value="examAttempt.user && userOption.id === examAttempt.user.id ? examAttempt.user : userOption"
v-for="userOption in users"
:key="userOption.id"
>
{{ userOption.login }}
</option>
</select>
</div>
<div v-if="v$.user.$anyDirty && v$.user.$invalid">
<small class="form-text text-danger" v-for="error of v$.user.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div>
<button type="button" id="cancel-save" data-cy="entityCreateCancelButton" class="btn btn-secondary" @click="previousState()">
<font-awesome-icon icon="ban"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.cancel') }}</span>
</button>
<button
type="submit"
id="save-entity"
data-cy="entityCreateSaveButton"
:disabled="v$.$invalid || isSaving"
class="btn btn-primary"
>
<font-awesome-icon icon="save"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.save') }}</span>
</button>
</div>
</form>
</div>
</div>
</template>
<script lang="ts" src="./exam-attempt-update.component.ts"></script>
@@ -0,0 +1,167 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import ExamAttempt from './exam-attempt.vue';
type ExamAttemptComponentType = InstanceType<typeof ExamAttempt>;
const bModalStub = {
render: () => {},
methods: {
hide: () => {},
show: () => {},
},
};
describe('Component Tests', () => {
let alertService: AlertService;
describe('ExamAttempt Management Component', () => {
let examAttemptServiceStub: any;
let mountOptions: MountingOptions<ExamAttemptComponentType>['global'];
beforeEach(() => {
examAttemptServiceStub = {
retrieve: vi.fn(),
delete: vi.fn(),
};
examAttemptServiceStub.retrieve.mockResolvedValue({ headers: {} });
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
jhiItemCount: true,
bPagination: true,
bModal: bModalStub as any,
'font-awesome-icon': true,
'b-badge': true,
'jhi-sort-indicator': true,
'b-button': true,
'router-link': true,
},
directives: {
'b-modal': {},
},
provide: {
alertService,
examAttemptService: () => examAttemptServiceStub,
},
};
});
describe('Mount', () => {
it('Should call load all on init', async () => {
// GIVEN
examAttemptServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
const wrapper = shallowMount(ExamAttempt, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(examAttemptServiceStub.retrieve).toHaveBeenCalledOnce();
expect(comp.examAttempts[0]).toEqual(expect.objectContaining({ id: 123 }));
});
it('should calculate the sort attribute for an id', async () => {
// WHEN
const wrapper = shallowMount(ExamAttempt, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(examAttemptServiceStub.retrieve.mock.lastCall?.[0]).toMatchObject({
sort: ['id,asc'],
});
});
});
describe('Handles', () => {
let comp: ExamAttemptComponentType;
beforeEach(async () => {
const wrapper = shallowMount(ExamAttempt, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
examAttemptServiceStub.retrieve.mockReset();
examAttemptServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [] });
});
it('should load a page', async () => {
// GIVEN
examAttemptServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
comp.page = 2;
await comp.$nextTick();
// THEN
expect(examAttemptServiceStub.retrieve).toHaveBeenCalled();
expect(comp.examAttempts[0]).toEqual(expect.objectContaining({ id: 123 }));
});
it('should not load a page if the page is the same as the previous page', () => {
// WHEN
comp.page = 1;
// THEN
expect(examAttemptServiceStub.retrieve).not.toHaveBeenCalled();
});
it('should re-initialize the page', async () => {
// GIVEN
comp.page = 2;
await comp.$nextTick();
examAttemptServiceStub.retrieve.mockReset();
examAttemptServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
comp.clear();
await comp.$nextTick();
// THEN
expect(comp.page).toEqual(1);
expect(examAttemptServiceStub.retrieve).toHaveBeenCalledTimes(1);
expect(comp.examAttempts[0]).toEqual(expect.objectContaining({ id: 123 }));
});
it('should calculate the sort attribute for a non-id attribute', async () => {
// WHEN
comp.propOrder = 'name';
await comp.$nextTick();
// THEN
expect(examAttemptServiceStub.retrieve.mock.lastCall?.[0]).toMatchObject({
sort: ['name,asc', 'id'],
});
});
it('Should call delete service on confirmDelete', async () => {
// GIVEN
examAttemptServiceStub.delete.mockResolvedValue({});
// WHEN
comp.prepareRemove({ id: 123 });
comp.removeExamAttempt();
await comp.$nextTick(); // clear components
// THEN
expect(examAttemptServiceStub.delete).toHaveBeenCalled();
// THEN
await comp.$nextTick(); // handle component clear watch
expect(examAttemptServiceStub.retrieve).toHaveBeenCalledTimes(1);
});
});
});
});
@@ -0,0 +1,137 @@
import { type Ref, defineComponent, inject, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlertService } from '@/shared/alert/alert.service';
import { useDateFormat } from '@/shared/composables';
import { type IExamAttempt } from '@/shared/model/exam-attempt.model';
import ExamAttemptService from './exam-attempt.service';
export default defineComponent({
name: 'ExamAttempt',
setup() {
const { t: t$ } = useI18n();
const dateFormat = useDateFormat();
const examAttemptService = inject('examAttemptService', () => new ExamAttemptService());
const alertService = inject('alertService', () => useAlertService(), true);
const itemsPerPage = ref(20);
const queryCount: Ref<number> = ref(null);
const page: Ref<number> = ref(1);
const propOrder = ref('id');
const reverse = ref(false);
const totalItems = ref(0);
const examAttempts: Ref<IExamAttempt[]> = ref([]);
const isFetching = ref(false);
const clear = () => {
page.value = 1;
};
const sort = (): Array<any> => {
const result = [`${propOrder.value},${reverse.value ? 'desc' : 'asc'}`];
if (propOrder.value !== 'id') {
result.push('id');
}
return result;
};
const retrieveExamAttempts = async () => {
isFetching.value = true;
try {
const paginationQuery = {
page: page.value - 1,
size: itemsPerPage.value,
sort: sort(),
};
const res = await examAttemptService().retrieve(paginationQuery);
totalItems.value = Number(res.headers['x-total-count']);
queryCount.value = totalItems.value;
examAttempts.value = res.data;
} catch (err) {
alertService.showHttpError(err.response);
} finally {
isFetching.value = false;
}
};
const handleSyncList = () => {
retrieveExamAttempts();
};
onMounted(async () => {
await retrieveExamAttempts();
});
const removeId: Ref<number> = ref(null);
const removeEntity = ref<any>(null);
const prepareRemove = (instance: IExamAttempt) => {
removeId.value = instance.id;
removeEntity.value.show();
};
const closeDialog = () => {
removeEntity.value.hide();
};
const removeExamAttempt = async () => {
try {
await examAttemptService().delete(removeId.value);
const message = t$('questionBankApp.examAttempt.deleted', { param: removeId.value }).toString();
alertService.showInfo(message, { variant: 'danger' });
removeId.value = null;
retrieveExamAttempts();
closeDialog();
} catch (error) {
alertService.showHttpError(error.response);
}
};
const changeOrder = (newOrder: string) => {
if (propOrder.value === newOrder) {
reverse.value = !reverse.value;
} else {
reverse.value = false;
}
propOrder.value = newOrder;
};
// Whenever order changes, reset the pagination
watch([propOrder, reverse], async () => {
if (page.value === 1) {
// first page, retrieve new data
await retrieveExamAttempts();
} else {
// reset the pagination
clear();
}
});
// Whenever page changes, switch to the new page.
watch(page, async () => {
await retrieveExamAttempts();
});
return {
examAttempts,
handleSyncList,
isFetching,
retrieveExamAttempts,
clear,
...dateFormat,
removeId,
removeEntity,
prepareRemove,
closeDialog,
removeExamAttempt,
itemsPerPage,
queryCount,
page,
propOrder,
reverse,
totalItems,
changeOrder,
t$,
};
},
});
@@ -0,0 +1,186 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import axios from 'axios';
import dayjs from 'dayjs';
import { DATE_TIME_FORMAT } from '@/shared/composables/date-format';
import { ExamAttempt } from '@/shared/model/exam-attempt.model';
import ExamAttemptService from './exam-attempt.service';
const error = {
response: {
status: null,
data: {
type: null,
},
},
};
const axiosStub = {
get: vi.spyOn(axios, 'get'),
post: vi.spyOn(axios, 'post'),
put: vi.spyOn(axios, 'put'),
patch: vi.spyOn(axios, 'patch'),
delete: vi.spyOn(axios, 'delete'),
};
describe('Service Tests', () => {
describe('ExamAttempt Service', () => {
let service: ExamAttemptService;
let elemDefault;
let currentDate: Date;
beforeEach(() => {
service = new ExamAttemptService();
currentDate = new Date();
elemDefault = new ExamAttempt(123, currentDate, currentDate, 'IN_PROGRESS', 0);
});
describe('Service methods', () => {
it('should find an element', async () => {
const returnedFromService = {
startedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
submittedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
...elemDefault,
};
axiosStub.get.mockResolvedValue({ data: returnedFromService });
return service.find(123).then(res => {
expect(res).toMatchObject(elemDefault);
});
});
it('should not find an element', async () => {
axiosStub.get.mockRejectedValue(error);
return service
.find(123)
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should create a ExamAttempt', async () => {
const returnedFromService = {
id: 123,
startedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
submittedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
...elemDefault,
};
const expected = { startedAt: currentDate, submittedAt: currentDate, ...returnedFromService };
axiosStub.post.mockResolvedValue({ data: returnedFromService });
return service.create({}).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not create a ExamAttempt', async () => {
axiosStub.post.mockRejectedValue(error);
return service
.create({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should update a ExamAttempt', async () => {
const returnedFromService = {
startedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
submittedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
status: 'BBBBBB',
score: 1,
...elemDefault,
};
const expected = { startedAt: currentDate, submittedAt: currentDate, ...returnedFromService };
axiosStub.put.mockResolvedValue({ data: returnedFromService });
return service.update(expected).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not update a ExamAttempt', async () => {
axiosStub.put.mockRejectedValue(error);
return service
.update({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should partial update a ExamAttempt', async () => {
const patchObject = { status: 'BBBBBB', score: 1, ...new ExamAttempt() };
const returnedFromService = Object.assign(patchObject, elemDefault);
const expected = { startedAt: currentDate, submittedAt: currentDate, ...returnedFromService };
axiosStub.patch.mockResolvedValue({ data: returnedFromService });
return service.partialUpdate(patchObject).then(res => {
expect(res).toMatchObject(expected);
});
});
it('should not partial update a ExamAttempt', async () => {
axiosStub.patch.mockRejectedValue(error);
return service
.partialUpdate({})
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should return a list of ExamAttempt', async () => {
const returnedFromService = {
startedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
submittedAt: dayjs(currentDate).format(DATE_TIME_FORMAT),
status: 'BBBBBB',
score: 1,
...elemDefault,
};
const expected = { startedAt: currentDate, submittedAt: currentDate, ...returnedFromService };
axiosStub.get.mockResolvedValue([returnedFromService]);
return service.retrieve({ sort: {}, page: 0, size: 10 }).then(res => {
expect(res).toContainEqual(expected);
});
});
it('should not return a list of ExamAttempt', async () => {
axiosStub.get.mockRejectedValue(error);
return service
.retrieve()
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
it('should delete a ExamAttempt', async () => {
axiosStub.delete.mockResolvedValue({ ok: true });
return service.delete(123).then(res => {
expect(res.ok).toBeTruthy();
});
});
it('should not delete a ExamAttempt', async () => {
axiosStub.delete.mockRejectedValue(error);
return service
.delete(123)
.then()
.catch(err => {
expect(err).toMatchObject(error);
});
});
});
});
});
@@ -0,0 +1,86 @@
import axios from 'axios';
import { type IExamAttempt } from '@/shared/model/exam-attempt.model';
import buildPaginationQueryOpts from '@/shared/sort/sorts';
const baseApiUrl = 'api/exam-attempts';
export default class ExamAttemptService {
find(id: number): Promise<IExamAttempt> {
return new Promise<IExamAttempt>((resolve, reject) => {
axios
.get(`${baseApiUrl}/${id}`)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
retrieve(paginationQuery?: any): Promise<any> {
return new Promise<any>((resolve, reject) => {
axios
.get(`${baseApiUrl}?${buildPaginationQueryOpts(paginationQuery)}`)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}
delete(id: number): Promise<any> {
return new Promise<any>((resolve, reject) => {
axios
.delete(`${baseApiUrl}/${id}`)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}
create(entity: IExamAttempt): Promise<IExamAttempt> {
return new Promise<IExamAttempt>((resolve, reject) => {
axios
.post(baseApiUrl, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
update(entity: IExamAttempt): Promise<IExamAttempt> {
return new Promise<IExamAttempt>((resolve, reject) => {
axios
.put(`${baseApiUrl}/${entity.id}`, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
partialUpdate(entity: IExamAttempt): Promise<IExamAttempt> {
return new Promise<IExamAttempt>((resolve, reject) => {
axios
.patch(`${baseApiUrl}/${entity.id}`, entity)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
}
@@ -0,0 +1,140 @@
<template>
<div>
<h2 id="page-heading" data-cy="ExamAttemptHeading">
<span id="exam-attempt">{{ t$('questionBankApp.examAttempt.home.title') }}</span>
<div class="d-flex justify-content-end">
<button class="btn btn-info me-2" @click="handleSyncList" :disabled="isFetching">
<font-awesome-icon icon="sync" :spin="isFetching"></font-awesome-icon>
<span>{{ t$('questionBankApp.examAttempt.home.refreshListLabel') }}</span>
</button>
<router-link :to="{ name: 'ExamAttemptCreate' }" custom v-slot="{ navigate }">
<button
@click="navigate"
id="jh-create-entity"
data-cy="entityCreateButton"
class="btn btn-primary jh-create-entity create-exam-attempt"
>
<font-awesome-icon icon="plus"></font-awesome-icon>
<span>{{ t$('questionBankApp.examAttempt.home.createLabel') }}</span>
</button>
</router-link>
</div>
</h2>
<br />
<div class="alert alert-warning" v-if="!isFetching && examAttempts?.length === 0">
<span>{{ t$('questionBankApp.examAttempt.home.notFound') }}</span>
</div>
<div class="table-responsive" v-if="examAttempts?.length > 0">
<table class="table table-striped" aria-describedby="examAttempts">
<thead>
<tr>
<th scope="col" @click="changeOrder('id')">
<span>{{ t$('global.field.id') }}</span>
<jhi-sort-indicator :current-order="propOrder" :reverse="reverse" :field-name="'id'"></jhi-sort-indicator>
</th>
<th scope="col" @click="changeOrder('startedAt')">
<span>{{ t$('questionBankApp.examAttempt.startedAt') }}</span>
<jhi-sort-indicator :current-order="propOrder" :reverse="reverse" :field-name="'startedAt'"></jhi-sort-indicator>
</th>
<th scope="col" @click="changeOrder('submittedAt')">
<span>{{ t$('questionBankApp.examAttempt.submittedAt') }}</span>
<jhi-sort-indicator :current-order="propOrder" :reverse="reverse" :field-name="'submittedAt'"></jhi-sort-indicator>
</th>
<th scope="col" @click="changeOrder('status')">
<span>{{ t$('questionBankApp.examAttempt.status') }}</span>
<jhi-sort-indicator :current-order="propOrder" :reverse="reverse" :field-name="'status'"></jhi-sort-indicator>
</th>
<th scope="col" @click="changeOrder('score')">
<span>{{ t$('questionBankApp.examAttempt.score') }}</span>
<jhi-sort-indicator :current-order="propOrder" :reverse="reverse" :field-name="'score'"></jhi-sort-indicator>
</th>
<th scope="col" @click="changeOrder('subject.name')">
<span>{{ t$('questionBankApp.examAttempt.subject') }}</span>
<jhi-sort-indicator :current-order="propOrder" :reverse="reverse" :field-name="'subject.name'"></jhi-sort-indicator>
</th>
<th scope="col" @click="changeOrder('user.login')">
<span>{{ t$('questionBankApp.examAttempt.user') }}</span>
<jhi-sort-indicator :current-order="propOrder" :reverse="reverse" :field-name="'user.login'"></jhi-sort-indicator>
</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="examAttempt in examAttempts" :key="examAttempt.id" data-cy="entityTable">
<td>
<router-link :to="{ name: 'ExamAttemptView', params: { examAttemptId: examAttempt.id } }">{{ examAttempt.id }}</router-link>
</td>
<td>{{ formatDateShort(examAttempt.startedAt) || '' }}</td>
<td>{{ formatDateShort(examAttempt.submittedAt) || '' }}</td>
<td>{{ t$('questionBankApp.AttemptStatus.' + examAttempt.status) }}</td>
<td>{{ examAttempt.score }}</td>
<td>
<div v-if="examAttempt.subject">
<router-link :to="{ name: 'SubjectView', params: { subjectId: examAttempt.subject.id } }">{{
examAttempt.subject.name
}}</router-link>
</div>
</td>
<td>
{{ examAttempt.user ? examAttempt.user.login : '' }}
</td>
<td class="text-end">
<div class="btn-group">
<router-link :to="{ name: 'ExamAttemptView', params: { examAttemptId: examAttempt.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-info btn-sm details" data-cy="entityDetailsButton">
<font-awesome-icon icon="eye"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.view') }}</span>
</button>
</router-link>
<router-link :to="{ name: 'ExamAttemptEdit', params: { examAttemptId: examAttempt.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-primary btn-sm edit" data-cy="entityEditButton">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.edit') }}</span>
</button>
</router-link>
<b-button @click="prepareRemove(examAttempt)" variant="danger" class="btn btn-sm" data-cy="entityDeleteButton">
<font-awesome-icon icon="times"></font-awesome-icon>
<span class="d-none d-md-inline">{{ t$('entity.action.delete') }}</span>
</b-button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<b-modal ref="removeEntity" id="removeEntity">
<template #title>
<span id="questionBankApp.examAttempt.delete.question" data-cy="examAttemptDeleteDialogHeading">{{
t$('entity.delete.title')
}}</span>
</template>
<div class="modal-body">
<p id="jhi-delete-examAttempt-heading">{{ t$('questionBankApp.examAttempt.delete.question', { id: removeId }) }}</p>
</div>
<template #footer>
<div>
<button type="button" class="btn btn-secondary" @click="closeDialog()">{{ t$('entity.action.cancel') }}</button>
<button
type="button"
class="btn btn-primary"
id="jhi-confirm-delete-examAttempt"
data-cy="entityConfirmDeleteButton"
@click="removeExamAttempt"
>
{{ t$('entity.action.delete') }}
</button>
</div>
</template>
</b-modal>
<div v-show="examAttempts?.length > 0">
<div class="d-flex justify-content-center">
<jhi-item-count :page="page" :total="queryCount" :items-per-page="itemsPerPage"></jhi-item-count>
</div>
<div class="d-flex justify-content-center">
<b-pagination size="md" :total-rows="totalItems" v-model="page" :per-page="itemsPerPage"></b-pagination>
</div>
</div>
</div>
</template>
<script lang="ts" src="./exam-attempt.component.ts"></script>
@@ -0,0 +1,91 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import QuestionDetails from './question-details.vue';
type QuestionDetailsComponentType = InstanceType<typeof QuestionDetails>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const questionSample = { id: 123 };
describe('Component Tests', () => {
let alertService: AlertService;
afterEach(() => {
vi.resetAllMocks();
});
describe('Question Management Detail Component', () => {
let questionServiceStub: any;
let mountOptions: MountingOptions<QuestionDetailsComponentType>['global'];
beforeEach(() => {
route = {};
questionServiceStub = {
find: vi.fn(),
};
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'router-link': true,
},
provide: {
alertService,
questionService: () => questionServiceStub,
},
};
});
describe('Navigate to details', () => {
it('Should call load all on init', async () => {
// GIVEN
questionServiceStub.find.mockResolvedValue(questionSample);
route = {
params: {
questionId: `${123}`,
},
};
const wrapper = shallowMount(QuestionDetails, { global: mountOptions });
const comp = wrapper.vm;
// WHEN
await comp.$nextTick();
// THEN
expect(comp.question).toMatchObject(questionSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
questionServiceStub.find.mockResolvedValue(questionSample);
const wrapper = shallowMount(QuestionDetails, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,42 @@
import { type Ref, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useAlertService } from '@/shared/alert/alert.service';
import { type IQuestion } from '@/shared/model/question.model';
import QuestionService from './question.service';
export default defineComponent({
name: 'QuestionDetails',
setup() {
const questionService = inject('questionService', () => new QuestionService());
const alertService = inject('alertService', () => useAlertService(), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const question: Ref<IQuestion> = ref({});
const retrieveQuestion = async questionId => {
try {
const res = await questionService().find(questionId);
question.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.questionId) {
retrieveQuestion(route.params.questionId);
}
return {
question,
previousState,
t$: useI18n().t,
};
},
});
@@ -0,0 +1,51 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<div v-if="question">
<h2 class="jh-entity-heading" data-cy="questionDetailsHeading">
<span>{{ t$('questionBankApp.question.detail.title') }}</span> {{ question.id }}
</h2>
<dl class="row-md jh-entity-details">
<dt>
<span>{{ t$('questionBankApp.question.prompt') }}</span>
</dt>
<dd>
<span>{{ question.prompt }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.question.explanation') }}</span>
</dt>
<dd>
<span>{{ question.explanation }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.question.active') }}</span>
</dt>
<dd>
<span>{{ question.active }}</span>
</dd>
<dt>
<span>{{ t$('questionBankApp.question.subject') }}</span>
</dt>
<dd>
<div v-if="question.subject">
<router-link :to="{ name: 'SubjectView', params: { subjectId: question.subject.id } }">{{
question.subject.name
}}</router-link>
</div>
</dd>
</dl>
<button type="submit" @click.prevent="previousState()" class="btn btn-info" data-cy="entityDetailsBackButton">
<font-awesome-icon icon="arrow-left"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.back') }}</span>
</button>
<router-link v-if="question.id" :to="{ name: 'QuestionEdit', params: { questionId: question.id } }" custom v-slot="{ navigate }">
<button @click="navigate" class="btn btn-primary">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.edit') }}</span>
</button>
</router-link>
</div>
</div>
</div>
</template>
<script lang="ts" src="./question-details.component.ts"></script>
@@ -0,0 +1,139 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { type RouteLocation } from 'vue-router';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import QuestionUpdate from './question-update.vue';
type QuestionUpdateComponentType = InstanceType<typeof QuestionUpdate>;
let route: Partial<RouteLocation>;
const routerGoMock = vi.fn();
vi.mock('vue-router', () => ({
useRoute: () => route,
useRouter: () => ({ go: routerGoMock }),
}));
const questionSample = { id: 123 };
describe('Component Tests', () => {
let mountOptions: MountingOptions<QuestionUpdateComponentType>['global'];
let alertService: AlertService;
describe('Question Management Update Component', () => {
let comp: QuestionUpdateComponentType;
let questionServiceStub: any;
beforeEach(() => {
route = {};
questionServiceStub = {
retrieve: vi.fn(),
find: vi.fn(),
update: vi.fn(),
create: vi.fn(),
};
questionServiceStub.retrieve.mockResolvedValueOnce([]);
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
'font-awesome-icon': true,
'b-input-group': true,
'b-input-group-prepend': true,
'b-form-datepicker': true,
'b-form-input': true,
},
provide: {
alertService,
questionService: () => questionServiceStub,
subjectService: () => ({
retrieve: vi.fn().mockResolvedValue({}),
}),
},
};
});
afterEach(() => {
vi.resetAllMocks();
});
describe('save', () => {
it('Should call update service on save for existing entity', async () => {
// GIVEN
const wrapper = shallowMount(QuestionUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.question = questionSample;
questionServiceStub.update.mockResolvedValue(questionSample);
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(questionServiceStub.update).toHaveBeenCalledWith(questionSample);
expect(comp.isSaving).toEqual(false);
});
it('Should call create service on save for new entity', async () => {
// GIVEN
const entity = {};
questionServiceStub.create.mockResolvedValue(entity);
const wrapper = shallowMount(QuestionUpdate, { global: mountOptions });
comp = wrapper.vm;
comp.question = entity;
// WHEN
comp.save();
await comp.$nextTick();
// THEN
expect(questionServiceStub.create).toHaveBeenCalledWith(entity);
expect(comp.isSaving).toEqual(false);
});
});
describe('Before route enter', () => {
it('Should retrieve data', async () => {
// GIVEN
questionServiceStub.find.mockResolvedValue(questionSample);
questionServiceStub.retrieve.mockResolvedValue([questionSample]);
// WHEN
route = {
params: {
questionId: `${questionSample.id}`,
},
};
const wrapper = shallowMount(QuestionUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(comp.question).toMatchObject(questionSample);
});
});
describe('Previous state', () => {
it('Should go previous state', async () => {
questionServiceStub.find.mockResolvedValue(questionSample);
const wrapper = shallowMount(QuestionUpdate, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
comp.previousState();
await comp.$nextTick();
expect(routerGoMock).toHaveBeenCalledWith(-1);
});
});
});
});
@@ -0,0 +1,122 @@
import { type Ref, computed, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useVuelidate } from '@vuelidate/core';
import SubjectService from '@/entities/subject/subject.service';
import { useAlertService } from '@/shared/alert/alert.service';
import { useValidation } from '@/shared/composables';
import { type IQuestion, Question } from '@/shared/model/question.model';
import { type ISubject } from '@/shared/model/subject.model';
import QuestionService from './question.service';
export default defineComponent({
name: 'QuestionUpdate',
setup() {
const questionService = inject('questionService', () => new QuestionService());
const alertService = inject('alertService', () => useAlertService(), true);
const question: Ref<IQuestion> = ref(new Question());
const subjectService = inject('subjectService', () => new SubjectService());
const subjects: Ref<ISubject[]> = ref([]);
const isSaving = ref(false);
const currentLanguage = inject('currentLanguage', () => computed(() => navigator.language ?? 'en'), true);
const route = useRoute();
const router = useRouter();
const previousState = () => router.go(-1);
const retrieveQuestion = async questionId => {
try {
const res = await questionService().find(questionId);
question.value = res;
} catch (error) {
alertService.showHttpError(error.response);
}
};
if (route.params?.questionId) {
retrieveQuestion(route.params.questionId);
}
const initRelationships = () => {
subjectService()
.retrieve({ page: 0, size: 100 })
.then(res => {
subjects.value = res.data;
});
};
initRelationships();
const { t: t$ } = useI18n();
const validations = useValidation();
const validationRules = {
prompt: {
required: validations.required(t$('entity.validation.required').toString()),
minLength: validations.minLength(t$('entity.validation.minlength', { min: 5 }).toString(), 5),
maxLength: validations.maxLength(t$('entity.validation.maxlength', { max: 2000 }).toString(), 2000),
},
explanation: {
maxLength: validations.maxLength(t$('entity.validation.maxlength', { max: 2000 }).toString(), 2000),
},
active: {
required: validations.required(t$('entity.validation.required').toString()),
},
choices: {},
subject: {
required: validations.required(t$('entity.validation.required').toString()),
},
};
const v$ = useVuelidate(validationRules, question as any);
v$.value.$validate();
return {
questionService,
alertService,
question,
previousState,
isSaving,
currentLanguage,
subjects,
v$,
t$,
};
},
created(): void {},
methods: {
save(): void {
this.isSaving = true;
if (this.question.id) {
this.questionService()
.update(this.question)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showInfo(this.t$('questionBankApp.question.updated', { param: param.id }));
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
} else {
this.questionService()
.create(this.question)
.then(param => {
this.isSaving = false;
this.previousState();
this.alertService.showSuccess(this.t$('questionBankApp.question.created', { param: param.id }).toString());
})
.catch(error => {
this.isSaving = false;
this.alertService.showHttpError(error.response);
});
}
},
},
});
@@ -0,0 +1,95 @@
<template>
<div class="d-flex justify-content-center">
<div class="col-8">
<form name="editForm" novalidate @submit.prevent="save()">
<h2 id="questionBankApp.question.home.createOrEditLabel" data-cy="QuestionCreateUpdateHeading">
{{ t$('questionBankApp.question.home.createOrEditLabel') }}
</h2>
<div>
<div class="mb-3" v-if="question.id">
<label for="id">{{ t$('global.field.id') }}</label>
<input type="text" class="form-control" id="id" name="id" v-model="question.id" readonly />
</div>
<div class="mb-3">
<label class="form-control-label" for="question-prompt">{{ t$('questionBankApp.question.prompt') }}</label>
<input
type="text"
class="form-control"
name="prompt"
id="question-prompt"
data-cy="prompt"
:class="{ valid: !v$.prompt.$invalid, invalid: v$.prompt.$invalid }"
v-model="v$.prompt.$model"
required
/>
<div v-if="v$.prompt.$anyDirty && v$.prompt.$invalid">
<small class="form-text text-danger" v-for="error of v$.prompt.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="question-explanation">{{ t$('questionBankApp.question.explanation') }}</label>
<input
type="text"
class="form-control"
name="explanation"
id="question-explanation"
data-cy="explanation"
:class="{ valid: !v$.explanation.$invalid, invalid: v$.explanation.$invalid }"
v-model="v$.explanation.$model"
/>
<div v-if="v$.explanation.$anyDirty && v$.explanation.$invalid">
<small class="form-text text-danger" v-for="error of v$.explanation.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3 form-check">
<input
type="checkbox"
class="form-check-input"
name="active"
id="question-active"
data-cy="active"
:class="{ valid: !v$.active.$invalid, invalid: v$.active.$invalid }"
v-model="v$.active.$model"
required
/>
<label class="form-check-label" for="question-active">{{ t$('questionBankApp.question.active') }}</label>
<div v-if="v$.active.$anyDirty && v$.active.$invalid">
<small class="form-text text-danger" v-for="error of v$.active.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div class="mb-3">
<label class="form-control-label" for="question">{{ t$('questionBankApp.question.subject') }}</label>
<select class="form-control" id="question-subject" data-cy="subject" name="subject" v-model="question.subject" required>
<option v-if="!question.subject" :value="null" selected></option>
<option
:value="question.subject && subjectOption.id === question.subject.id ? question.subject : subjectOption"
v-for="subjectOption in subjects"
:key="subjectOption.id"
>
{{ subjectOption.name }}
</option>
</select>
</div>
<div v-if="v$.subject.$anyDirty && v$.subject.$invalid">
<small class="form-text text-danger" v-for="error of v$.subject.$errors" :key="error.$uid">{{ error.$message }}</small>
</div>
</div>
<div>
<button type="button" id="cancel-save" data-cy="entityCreateCancelButton" class="btn btn-secondary" @click="previousState()">
<font-awesome-icon icon="ban"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.cancel') }}</span>
</button>
<button
type="submit"
id="save-entity"
data-cy="entityCreateSaveButton"
:disabled="v$.$invalid || isSaving"
class="btn btn-primary"
>
<font-awesome-icon icon="save"></font-awesome-icon>&nbsp;<span>{{ t$('entity.action.save') }}</span>
</button>
</div>
</form>
</div>
</div>
</template>
<script lang="ts" src="./question-update.component.ts"></script>
@@ -0,0 +1,167 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { type MountingOptions, shallowMount } from '@vue/test-utils';
import AlertService from '@/shared/alert/alert.service';
import Question from './question.vue';
type QuestionComponentType = InstanceType<typeof Question>;
const bModalStub = {
render: () => {},
methods: {
hide: () => {},
show: () => {},
},
};
describe('Component Tests', () => {
let alertService: AlertService;
describe('Question Management Component', () => {
let questionServiceStub: any;
let mountOptions: MountingOptions<QuestionComponentType>['global'];
beforeEach(() => {
questionServiceStub = {
retrieve: vi.fn(),
delete: vi.fn(),
};
questionServiceStub.retrieve.mockResolvedValue({ headers: {} });
alertService = new AlertService({
i18n: { t: vi.fn() } as any,
toast: {
create: vi.fn(),
} as any,
});
mountOptions = {
stubs: {
jhiItemCount: true,
bPagination: true,
bModal: bModalStub as any,
'font-awesome-icon': true,
'b-badge': true,
'jhi-sort-indicator': true,
'b-button': true,
'router-link': true,
},
directives: {
'b-modal': {},
},
provide: {
alertService,
questionService: () => questionServiceStub,
},
};
});
describe('Mount', () => {
it('Should call load all on init', async () => {
// GIVEN
questionServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
const wrapper = shallowMount(Question, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(questionServiceStub.retrieve).toHaveBeenCalledOnce();
expect(comp.questions[0]).toEqual(expect.objectContaining({ id: 123 }));
});
it('should calculate the sort attribute for an id', async () => {
// WHEN
const wrapper = shallowMount(Question, { global: mountOptions });
const comp = wrapper.vm;
await comp.$nextTick();
// THEN
expect(questionServiceStub.retrieve.mock.lastCall?.[0]).toMatchObject({
sort: ['id,asc'],
});
});
});
describe('Handles', () => {
let comp: QuestionComponentType;
beforeEach(async () => {
const wrapper = shallowMount(Question, { global: mountOptions });
comp = wrapper.vm;
await comp.$nextTick();
questionServiceStub.retrieve.mockReset();
questionServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [] });
});
it('should load a page', async () => {
// GIVEN
questionServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
comp.page = 2;
await comp.$nextTick();
// THEN
expect(questionServiceStub.retrieve).toHaveBeenCalled();
expect(comp.questions[0]).toEqual(expect.objectContaining({ id: 123 }));
});
it('should not load a page if the page is the same as the previous page', () => {
// WHEN
comp.page = 1;
// THEN
expect(questionServiceStub.retrieve).not.toHaveBeenCalled();
});
it('should re-initialize the page', async () => {
// GIVEN
comp.page = 2;
await comp.$nextTick();
questionServiceStub.retrieve.mockReset();
questionServiceStub.retrieve.mockResolvedValue({ headers: {}, data: [{ id: 123 }] });
// WHEN
comp.clear();
await comp.$nextTick();
// THEN
expect(comp.page).toEqual(1);
expect(questionServiceStub.retrieve).toHaveBeenCalledTimes(1);
expect(comp.questions[0]).toEqual(expect.objectContaining({ id: 123 }));
});
it('should calculate the sort attribute for a non-id attribute', async () => {
// WHEN
comp.propOrder = 'name';
await comp.$nextTick();
// THEN
expect(questionServiceStub.retrieve.mock.lastCall?.[0]).toMatchObject({
sort: ['name,asc', 'id'],
});
});
it('Should call delete service on confirmDelete', async () => {
// GIVEN
questionServiceStub.delete.mockResolvedValue({});
// WHEN
comp.prepareRemove({ id: 123 });
comp.removeQuestion();
await comp.$nextTick(); // clear components
// THEN
expect(questionServiceStub.delete).toHaveBeenCalled();
// THEN
await comp.$nextTick(); // handle component clear watch
expect(questionServiceStub.retrieve).toHaveBeenCalledTimes(1);
});
});
});
});
@@ -0,0 +1,134 @@
import { type Ref, defineComponent, inject, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlertService } from '@/shared/alert/alert.service';
import { type IQuestion } from '@/shared/model/question.model';
import QuestionService from './question.service';
export default defineComponent({
name: 'Question',
setup() {
const { t: t$ } = useI18n();
const questionService = inject('questionService', () => new QuestionService());
const alertService = inject('alertService', () => useAlertService(), true);
const itemsPerPage = ref(20);
const queryCount: Ref<number> = ref(null);
const page: Ref<number> = ref(1);
const propOrder = ref('id');
const reverse = ref(false);
const totalItems = ref(0);
const questions: Ref<IQuestion[]> = ref([]);
const isFetching = ref(false);
const clear = () => {
page.value = 1;
};
const sort = (): Array<any> => {
const result = [`${propOrder.value},${reverse.value ? 'desc' : 'asc'}`];
if (propOrder.value !== 'id') {
result.push('id');
}
return result;
};
const retrieveQuestions = async () => {
isFetching.value = true;
try {
const paginationQuery = {
page: page.value - 1,
size: itemsPerPage.value,
sort: sort(),
};
const res = await questionService().retrieve(paginationQuery);
totalItems.value = Number(res.headers['x-total-count']);
queryCount.value = totalItems.value;
questions.value = res.data;
} catch (err) {
alertService.showHttpError(err.response);
} finally {
isFetching.value = false;
}
};
const handleSyncList = () => {
retrieveQuestions();
};
onMounted(async () => {
await retrieveQuestions();
});
const removeId: Ref<number> = ref(null);
const removeEntity = ref<any>(null);
const prepareRemove = (instance: IQuestion) => {
removeId.value = instance.id;
removeEntity.value.show();
};
const closeDialog = () => {
removeEntity.value.hide();
};
const removeQuestion = async () => {
try {
await questionService().delete(removeId.value);
const message = t$('questionBankApp.question.deleted', { param: removeId.value }).toString();
alertService.showInfo(message, { variant: 'danger' });
removeId.value = null;
retrieveQuestions();
closeDialog();
} catch (error) {
alertService.showHttpError(error.response);
}
};
const changeOrder = (newOrder: string) => {
if (propOrder.value === newOrder) {
reverse.value = !reverse.value;
} else {
reverse.value = false;
}
propOrder.value = newOrder;
};
// Whenever order changes, reset the pagination
watch([propOrder, reverse], async () => {
if (page.value === 1) {
// first page, retrieve new data
await retrieveQuestions();
} else {
// reset the pagination
clear();
}
});
// Whenever page changes, switch to the new page.
watch(page, async () => {
await retrieveQuestions();
});
return {
questions,
handleSyncList,
isFetching,
retrieveQuestions,
clear,
removeId,
removeEntity,
prepareRemove,
closeDialog,
removeQuestion,
itemsPerPage,
queryCount,
page,
propOrder,
reverse,
totalItems,
changeOrder,
t$,
};
},
});
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff. Show more