Skip to content

Commit

Permalink
Issue053/cards refactor sonar (#68)
Browse files Browse the repository at this point in the history
* add card mapper test

* add card service test,

fix delete and some refactorings

* refactor entities to records implementing Card interface

also rename volume to allow for purge to work again, add ValidatorUnitTests

* fix update for different types &

add controller IT and validation test
  • Loading branch information
wisskirchenj authored Feb 11, 2024
1 parent 9c2d80a commit 958f058
Show file tree
Hide file tree
Showing 21 changed files with 1,107 additions and 132 deletions.
4 changes: 2 additions & 2 deletions docker/arch/compose_amd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
ports:
- '27017:27017'
volumes:
- 'mongo-data:/data/db'
- 'flashcards-mongo:/data/db'
networks:
- flashcards-network
authserver:
Expand All @@ -21,7 +21,7 @@ services:
- flashcards-network

volumes:
mongo-data:
flashcards-mongo:

networks:
flashcards-network:
Expand Down
4 changes: 2 additions & 2 deletions docker/arch/compose_arm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
ports:
- '27017:27017'
volumes:
- 'mongo-data:/data/db'
- 'flashcards-mongo:/data/db'
networks:
- flashcards-network
authserver:
Expand All @@ -21,7 +21,7 @@ services:
- flashcards-network

volumes:
mongo-data:
flashcards-mongo:

networks:
flashcards-network:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import org.hyperskill.community.flashcards.card.request.MultipleChoiceQuizRequestDto;
import org.hyperskill.community.flashcards.card.request.QuestionAndAnswerRequestDto;
import org.hyperskill.community.flashcards.card.request.SingleChoiceQuizRequestDto;
import org.hyperskill.community.flashcards.card.response.CardType;
import org.hyperskill.community.flashcards.category.CategoryService;
import org.hyperskill.community.flashcards.category.model.Category;
import org.hyperskill.community.flashcards.category.model.CategoryAccess;
import org.hyperskill.community.flashcards.common.exception.IllegalModificationException;
import org.hyperskill.community.flashcards.common.exception.ResourceNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
Expand Down Expand Up @@ -44,7 +46,7 @@ public Page<Card> getCardsByCategory(String username, String categoryId, int pag
var cards = mongoTemplate.find(query.with(pageRequest), Card.class, category.name());

var permissions = getPermissions(username, category);
cards.forEach(card -> card.setPermissions(permissions));
cards = cards.stream().map(card -> card.setPermissions(permissions)).toList();
return new PageImpl<>(cards, pageRequest, count);
}

Expand All @@ -65,7 +67,7 @@ public long getCardCountOfCategory(String username, String categoryId) {
public String createCard(String username, CardRequest request, String categoryId) {
var collection = getCollectionName(username, categoryId, "w");
var card = mapper.toDocument(request);
return mongoTemplate.insert(card, collection).getId();
return mongoTemplate.insert(card, collection).id();
}

public Card getCardById(String username, String cardId, String categoryId) {
Expand All @@ -75,30 +77,32 @@ public Card getCardById(String username, String cardId, String categoryId) {
var category = categoryService.findById(username, categoryId);
var card = Optional.ofNullable(mongoTemplate.findById(cardId, Card.class, category.name()))
.orElseThrow(ResourceNotFoundException::new);
card.setPermissions(getPermissions(username, category));
return card;
return card.setPermissions(getPermissions(username, category));
}

public void deleteCardById(String username, String cardId, String categoryId) {
public long deleteCardById(String username, String cardId, String categoryId) {
Objects.requireNonNull(cardId, "Card ID cannot be null");

var collection = getCollectionName(username, categoryId, "d");
var query = Query.query(Criteria.where("id").is(cardId));
mongoTemplate.remove(query, collection);
var query = Query.query(Criteria.where(Card.ID_KEY).is(cardId));
return mongoTemplate.remove(query, collection).getDeletedCount();
}

public Card updateCardById(String username, String cardId, CardRequest request, String categoryId) {
Objects.requireNonNull(cardId, "Card ID cannot be null");
Objects.requireNonNull(categoryId, "Category ID cannot be null");

var category = categoryService.findById(username, categoryId, "w");
var query = Query.query(Criteria.where("_id").is(cardId));
mongoTemplate.updateFirst(query, updateFrom(request), category.name());
var cardBeforeUpdate = getCardById(username, cardId, categoryId);
if (CardType.fromCard(cardBeforeUpdate) != CardType.fromRequest(request)) {
throw new IllegalModificationException();
}

var query = Query.query(Criteria.where(Card.ID_KEY).is(cardId));
mongoTemplate.updateFirst(query, updateFrom(request), category.name());
var updatedCard = Optional.ofNullable(mongoTemplate.findOne(query, Card.class, category.name()))
.orElseThrow(ResourceNotFoundException::new);
updatedCard.setPermissions(getPermissions(username, category));
return updatedCard;
return updatedCard.setPermissions(getPermissions(username, category));
}

private String getCollectionName(String username, String categoryId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class ExampleDataInitializer {
*/
@PostConstruct
public void init() {

log.info("Inserting sample data to the database...");
if (isCollectionNotEmpty(USER)) {
log.warn(ABORTING_DATABASE_INITIALIZATION, USER);
Expand Down Expand Up @@ -82,7 +83,6 @@ public void init() {

JsonNode jsonNode = objectMapper.readTree(flashcardsJson.getFile());

// TODO remove loop after testing
for (int i = 0; i < 5; i++) {
List<SingleChoiceQuiz> scqCards = parseCards(jsonNode.get("scq_cards"), SingleChoiceQuiz.class);
List<MultipleChoiceQuiz> mcqCards = parseCards(jsonNode.get("mcq_cards"), MultipleChoiceQuiz.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,12 @@
public class CardMapper {

public CardItemProjection map(Card card) {

var type = switch (card) {
case QuestionAndAnswer ignoredC -> CardType.QNA;
case SingleChoiceQuiz ignoredC -> CardType.SCQ;
case MultipleChoiceQuiz ignoredC -> CardType.MCQ;
};
return new CardItemProjection(card.getId(), card.getTitle(), type);
return new CardItemProjection(card.id(), card.title(), CardType.fromCard(card));
}

public CardResponse map(Card card, String categoryId) {
var uri = "/api/cards/" + card.getId() + "?categoryId=" + categoryId;
var actions = ActionsParser.fromPermissions(card.getPermissions(), uri);
var uri = "/api/cards/" + card.id() + "?categoryId=" + categoryId;
var actions = ActionsParser.fromPermissions(card.permissions(), uri);

return switch (card) {
case QuestionAndAnswer qna -> toDto(qna, actions);
Expand Down Expand Up @@ -83,42 +77,42 @@ private MultipleChoiceQuiz toDocument(MultipleChoiceQuizRequestDto request) {

private QuestionAndAnswerResponseDto toDto(QuestionAndAnswer card, Set<PermittedAction> actions) {
return QuestionAndAnswerResponseDto.builder()
.id(card.getId())
.title(card.getTitle())
.id(card.id())
.title(card.title())
.type(CardType.QNA)
.question(card.getQuestion())
.answer(card.getAnswer())
.tags(card.getTags())
.question(card.question())
.answer(card.answer())
.tags(card.tags())
.actions(actions)
.createdAt(card.getCreatedAt())
.createdAt(card.createdAt())
.build();
}

private SingleChoiceQuizResponseDto toDto(SingleChoiceQuiz card, Set<PermittedAction> actions) {
return SingleChoiceQuizResponseDto.builder()
.id(card.getId())
.title(card.getTitle())
.id(card.id())
.title(card.title())
.type(CardType.SCQ)
.question(card.getQuestion())
.options(card.getOptions())
.correctOption(card.getCorrectOption())
.tags(card.getTags())
.question(card.question())
.options(card.options())
.correctOption(card.correctOption())
.tags(card.tags())
.actions(actions)
.createdAt(card.getCreatedAt())
.createdAt(card.createdAt())
.build();
}

private MultipleChoiceQuizResponseDto toDto(MultipleChoiceQuiz card, Set<PermittedAction> actions) {
return MultipleChoiceQuizResponseDto.builder()
.id(card.getId())
.title(card.getTitle())
.id(card.id())
.title(card.title())
.type(CardType.MCQ)
.question(card.getQuestion())
.options(card.getOptions())
.correctOptions(card.getCorrectOptions())
.tags(card.getTags())
.question(card.question())
.options(card.options())
.correctOptions(card.correctOptions())
.tags(card.tags())
.actions(actions)
.createdAt(card.getCreatedAt())
.createdAt(card.createdAt())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;
Expand All @@ -18,31 +10,37 @@
/**
* Basic class for all flashcards, contains fields shared among all subclasses
*/
@Getter
@Setter
@ToString
@NoArgsConstructor
@SuperBuilder

@Document
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = QuestionAndAnswer.class, name = "qna"),
@JsonSubTypes.Type(value = SingleChoiceQuiz.class, name = "scq"),
@JsonSubTypes.Type(value = MultipleChoiceQuiz.class, name = "mcq"),
@JsonSubTypes.Type(value = QuestionAndAnswer.class, name = "QNA"),
@JsonSubTypes.Type(value = SingleChoiceQuiz.class, name = "SCQ"),
@JsonSubTypes.Type(value = MultipleChoiceQuiz.class, name = "MCQ")
})
public abstract sealed class Card permits QuestionAndAnswer, SingleChoiceQuiz, MultipleChoiceQuiz {
@Id
private String id;
private String title;
private Set<String> tags;
private String question;
@CreatedDate
private Instant createdAt;
@Transient
private String permissions;
public sealed interface Card permits QuestionAndAnswer, SingleChoiceQuiz, MultipleChoiceQuiz {

String ID_KEY = "_id";

String id();

String title();

Set<String> tags();

String question();

Instant createdAt();

String permissions();

/**
* updates permissions of the card.
* @param permissions new permissions
* @return new instance of the card with updated permissions
*/
Card setPermissions(String permissions);
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
package org.hyperskill.community.flashcards.card.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import lombok.Builder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.TypeAlias;

import java.time.Instant;
import java.util.List;
import java.util.Set;

/**
* Represents a multiple choice quiz card, i.e. where the user is provided with
* multiple options of which at least one is correct. Commonly visualized as
* having options with checkboxes
*/
@Getter
@Setter
@ToString(callSuper = true)
@NoArgsConstructor
@SuperBuilder
@Builder
@TypeAlias("MCQ")
public final class MultipleChoiceQuiz extends Card {
private List<String> options;
private List<Integer> correctOptions;
public record MultipleChoiceQuiz(
@Id String id,
String title,
Set<String> tags,
String question,
@CreatedDate Instant createdAt,
@Transient String permissions,
List<String> options,
List<Integer> correctOptions
) implements Card {

public MultipleChoiceQuiz setPermissions(String permissions) {
return new MultipleChoiceQuiz(id, title, tags, question, createdAt, permissions, options, correctOptions);
}
@PersistenceCreator
@SuppressWarnings("unused")
public MultipleChoiceQuiz(String id, String title, Set<String> tags, String question, List<String> options,
List<Integer> correctOptions, Instant createdAt) {
this(id, title, tags, question, createdAt, null, options, correctOptions);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
package org.hyperskill.community.flashcards.card.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import lombok.Builder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.TypeAlias;

@Getter
@Setter
@ToString(callSuper = true)
@NoArgsConstructor
@SuperBuilder
import java.time.Instant;
import java.util.Set;

@Builder
@TypeAlias("QNA")
public final class QuestionAndAnswer extends Card {
private String answer;
public record QuestionAndAnswer(
@Id String id,
String title,
Set<String> tags,
String question,
@CreatedDate Instant createdAt,
@Transient String permissions,
String answer
) implements Card {

public QuestionAndAnswer setPermissions(String permissions) {
return new QuestionAndAnswer(id, title, tags, question, createdAt, permissions, answer);
}

@PersistenceCreator
@SuppressWarnings("unused")
public QuestionAndAnswer(String id, String title, Set<String> tags, String question, String answer, Instant createdAt) {
this(id, title, tags, question, createdAt, null, answer);
}
}
Loading

0 comments on commit 958f058

Please sign in to comment.