Skip to content

Commit

Permalink
add db initialization with sample data (#20)
Browse files Browse the repository at this point in the history
* add db initialization with sample data

* fix according to comments

* fix ExampleDataInitializer description
  • Loading branch information
alfabravo2013 authored Jan 7, 2024
1 parent 3f30196 commit f932251
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,83 +1,110 @@
package org.hyperskill.community.flashcards.card;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hyperskill.community.flashcards.card.model.Card;
import org.hyperskill.community.flashcards.card.model.MultipleChoiceQuiz;
import org.hyperskill.community.flashcards.card.model.QuestionAndAnswerCard;
import org.hyperskill.community.flashcards.card.model.SingleChoiceQuiz;
import org.hyperskill.community.flashcards.category.model.Category;
import org.hyperskill.community.flashcards.category.model.CategoryAccess;
import org.springframework.beans.factory.annotation.Qualifier;
import org.hyperskill.community.flashcards.registration.User;
import org.hyperskill.community.flashcards.registration.UserDto;
import org.hyperskill.community.flashcards.registration.UserMapper;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
* Initializes the 'example' database with sample cards.
* Initializes the 'cards' database with sample cards.
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class ExampleDataInitializer {
private static final String exampleCollection = "example";
private static final String userCollection = "user";
private static final String categoryCollection = "category";
private static final String userJsonPath = "/json/users.json";
private static final String flashcardsJsonPath = "/json/flashcards.json";
private final ObjectMapper objectMapper = new ObjectMapper();
private final UserMapper userMapper;
private final MongoTemplate mongoTemplate;

public ExampleDataInitializer(@Qualifier("example") MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}

/**
* This method checks if collections to be initialized are empty, and if yes
* it reads data from <code>/resources/json</code> folder and insert it
* in the said collections. This ensures that empty database is initialized
* with the same data, while non-empty database state remains unchanged.
*/
@PostConstruct
public void init() {
List<Card> mathCards = List.of(
QuestionAndAnswerCard.builder()
.title("card 1")
.question("Calculate 2 + 2 = ?")
.answer("4")
.tags(Set.of("equations"))
.build(),
SingleChoiceQuiz.builder()
.title("card 2")
.question("Solve the equation: 2x + 3 = 9")
.options(List.of("2", "5", "6", "3"))
.correctOption(3)
.build(),
MultipleChoiceQuiz.builder()
.title("card 3")
.question("Select all correct statements about the equation: 2x + 3 = 9")
.options(List.of("It's a square equation", "It's a linear equation", "It doesn't have a root", "It's root is 3"))
.correctOptions(List.of(1, 3))
.build()
);
var geographyCards = List.of(
QuestionAndAnswerCard.builder()
.title("card 4")
.question("Capital of Spain")
.answer("Madrid")
.tags(Set.of("Europe", "cities"))
.build()
);
var chemistryCards = List.of(
SingleChoiceQuiz.builder()
.title("card 5")
.question("Which of the elements has the lowest atomic weight?")
.options(List.of("Au", "Fe", "Li", "Pt"))
.correctOption(2)
.tags(Set.of("elements", "atoms"))
.build()
);
if (isCollectionNotEmpty(userCollection)) {
log.warn("Collection {} is not empty, aborting database initialization", userCollection);
return;
}
if (isCollectionNotEmpty(categoryCollection)) {
log.warn("Collection {} is not empty, aborting database initialization", categoryCollection);
return;
}
if (isCollectionNotEmpty(exampleCollection)) {
log.warn("Collection {} is not empty, aborting database initialization", exampleCollection);
return;
}

Resource usersJson = new ClassPathResource(userJsonPath);
Resource flashcardsJson = new ClassPathResource(flashcardsJsonPath);

try {
List<UserDto> userDTOs = objectMapper.readValue(usersJson.getFile(), new TypeReference<>() {});
List<User> users = userDTOs.stream().map(userMapper::toDocument).toList();
if (users.size() == 0) {
throw new IllegalStateException("No users to insert");
}
mongoTemplate.insertAll(users);

var categoryAccess = new CategoryAccess(users.get(0).getUsername(), "rwd");
mongoTemplate.insert(new Category(null, exampleCollection, Set.of(categoryAccess)));

log.info("Dropping any existing collections in 'example' database...");
mongoTemplate.getDb().listCollectionNames().forEach(mongoTemplate::dropCollection);
JsonNode jsonNode = objectMapper.readTree(flashcardsJson.getFile());
List<SingleChoiceQuiz> scqCards = parseCards(jsonNode.get("scq_cards"), SingleChoiceQuiz.class);
List<MultipleChoiceQuiz> mcqCards = parseCards(jsonNode.get("mcq_cards"), MultipleChoiceQuiz.class);
List<QuestionAndAnswerCard> qnaCards = parseCards(jsonNode.get("qna_cards"), QuestionAndAnswerCard.class);

mongoTemplate.insert(scqCards, exampleCollection);
mongoTemplate.insert(mcqCards, exampleCollection);
mongoTemplate.insert(qnaCards, exampleCollection);

} catch (Exception e) {
log.error("Error updating database", e);
mongoTemplate.getDb().drop();
}
}

private <T extends Card> List<T> parseCards(JsonNode jsonNode, Class<T> clazz) throws JsonProcessingException {
List<T> list = new ArrayList<>();
for (JsonNode node : jsonNode) {
T card = objectMapper.treeToValue(node, clazz);
list.add(card);
}
return list;
}

log.info("Inserting sample data to 'example' database...");
var categoryAccess = new CategoryAccess("[email protected]", "rwd");
mongoTemplate.insert(new Category(null, "math", Set.of(categoryAccess)));
mongoTemplate.insert(new Category(null, "geography", Set.of(categoryAccess)));
mongoTemplate.insert(new Category(null, "chemistry", Set.of(categoryAccess)));
private boolean isCollectionNotEmpty(String collection) {
var collectionExists = mongoTemplate.getDb().listCollectionNames()
.into(new ArrayList<>())
.contains(collection);

mathCards.forEach(card -> mongoTemplate.insert(card, "math"));
geographyCards.forEach(card -> mongoTemplate.insert(card, "geography"));
chemistryCards.forEach(card -> mongoTemplate.insert(card, "chemistry"));
return collectionExists && mongoTemplate.getDb().getCollection(collection).countDocuments() > 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public final class MultipleChoiceQuiz extends Card {
private List<String> options;
private List<Integer> correctOptions;

public MultipleChoiceQuiz() {
super(null, null, null);
}

@Builder
public MultipleChoiceQuiz(String title, Set<String> tags, String question,
List<String> options, List<Integer> correctOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
public final class QuestionAndAnswerCard extends Card {
private String answer;

public QuestionAndAnswerCard() {
super(null, null, null);
}

@Builder
public QuestionAndAnswerCard(String title, Set<String> tags, String question, String answer) {
super(title, tags, question);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public final class SingleChoiceQuiz extends Card {
private List<String> options;
private Integer correctOption;

public SingleChoiceQuiz() {
super(null, null, null);
}

@Builder
public SingleChoiceQuiz(String title, Set<String> tags, String question,
List<String> options, Integer correctOption) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.hyperskill.community.flashcards.config;

import com.mongodb.client.MongoClients;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
Expand All @@ -15,10 +14,4 @@ public class MongoConfiguration {
public MongoTemplate mongoTemplate() {
return new MongoTemplate(MongoClients.create(), "cards");
}

@Bean
@Qualifier("example")
public MongoTemplate exampleMongoTemplate() {
return new MongoTemplate(MongoClients.create(), "example");
}
}
94 changes: 94 additions & 0 deletions server/src/main/resources/json/flashcards.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"qna_cards": [
{
"title": "card 1",
"question": "Capital of Brazil",
"answer": "Brasilia",
"tags": [
"america", "cities"
]
},
{
"title": "card 2",
"question": "Capital of Spain",
"answer": "Madrid",
"tags": [
"europe", "cities"
]
},
{
"title": "card 3",
"question": "Name this chemical element: Tl",
"answer": "Thallium",
"tags": [
"elements", "symbols"
]
},
{
"title": "card 4",
"question": "What is the square root of 144",
"answer": "12",
"tags": []
}
],
"scq_cards": [
{
"title": "card 5",
"question": "What is the root of this equation: 3x + 2 = 14",
"options": ["5", "9", "3", "4"],
"correctOption": 3,
"tags": ["equations"]
},
{
"title": "card 6",
"question": "Name the largest country among the following:",
"options": ["Portugal", "Sweden", "Australia", "Japan"],
"correctOption": 2,
"tags": ["countries"]
},
{
"title": "card 7",
"question": "What is the type of this reaction: NaOH + HCl = NaCl + H2O",
"options": ["Oxidation", "Reduction", "Neutralization", "Neither"],
"correctOption": 3,
"tags": ["reactions"]
},
{
"title": "card 8",
"question": "Name the capital of Luxembourg",
"options": ["Luxembourg", "Strasbourg", "Andorra la Vella", "London"],
"correctOption": 3,
"tags": ["europe", "cities"]
}
],
"mcq_cards": [
{
"title": "card 9",
"question": "Select all correct statements about this equation: ax + by = c",
"options": ["It's a quadratic equation", "It's a linear equation", "It describes a line", "It describes a curve"],
"correctOptions": [1, 2],
"tags": ["geometry", "equations"]
},
{
"title": "card 10",
"question": "Which of the following is true about DNA?",
"options": ["DNA is a double-stranded molecule", "DNA contains genetic information", "DNA stands for Deoxyribonucleic acid", "DNA is found only in animals"],
"correctOptions": [0, 1, 2],
"tags": ["genetics"]
},
{
"title": "card 11",
"question": "Which of the following elements is a noble gas?",
"options": ["Oxygen", "Helium", "Carbon", "Nitrogen"],
"correctOptions": [1],
"tags": ["elements"]
},
{
"title": "card 12",
"question": "Which of the following is a fundamental force in nature?",
"options": ["Gravity", "Friction", "Light", "Heat"],
"correctOptions": [0],
"tags": ["forces"]
}
]
}
10 changes: 10 additions & 0 deletions server/src/main/resources/json/users.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"email": "[email protected]",
"password": "12345678"
},
{
"email": "[email protected]",
"password": "12345678"
}
]

0 comments on commit f932251

Please sign in to comment.