Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Flash Cards App

This is a simple, console based application for creating and studying stacks of flash cards.

## Description

The core unit in the Flash Cards App is a stack. A stack consists of individual, unique cards that have text on both sides.
A stack can be created or deleted by the user and must have a unique name. Users can add, edit, or delete cards within each stack.
A stack can be studied to test the user's recall. Assessment in performed by the user and reported to the console.
This is used to store the study session data, which can be retreived later.
Additionally, reports can be generated to provide the total number of sessions per mon, per year, by stack, as well as the average score per month, per year, by stack.

## Getting Started

### Technologies

* C# Console Project
* Spectre.Console
* SQL Server
* Dapper

### Initial Setup

* Clone Repository
* In FlashCards.App > appsettings.json update connection string to a valid SQL Server database
* Run the application to build the database and see with starter data

## Program Operation

### Main Menu
The main menu gets the list of stacks from the database and prints the name, card count, and study session count.
It then provides the following options:
* Review Cards in Stack
* Create New Stack
* Delete Stack
* Begin Study Session
* View Past Study Sessions
* View Reports
* Exit

Review Stack takes the user to the Review Stack Menu (see next section).

Creating a stack allows the user to enter a new stack name. Stack names must be unique and cannot be blank.

Deleting a stack will delete the stack, all cards contained in that stack, as well as all study sessions associated with it. This cannot be undone.

Beginning a study session will enter the study mode. In this mode, a stack is selected and the cards are randomly shuffled and shown to the user.
The user is able to assess their own results and the session is stored in the database.

Viewing past sessions prints a paginated list of all study sessions stored in the database.
Users can navigate through pages to view all sessions.
Sessions can be sorted by stack name, time, or score.

View reports will prompt the user for a year (based on existing study sessions within the database) and use it to generate two reports.
One will display the total number of sessions per month for that year organized by stack.
The other will display the average score per month for that year organized by stack.

Exit will close the application.

### Review Stack Menu
The Review Stack Menu is a submenu that enables the user to manage a single stack.
It provides the following options:

* Review Cards in Stack
* Add Card to Stack
* Edit Card Text
* Delete Card from Stack
* Return to Main Menu

Review Stack provides a way for the user to practice their ability to recall the card information.
Results are not recorded.

Create, Edit, And Delete cards allows the user to manage the individual cards within a stack.
All cards must have text on both sides.
Each card's front text must be unique compared to the front text of all other cards wtihin the stack, as should the back text.

Return to Main Menu returns to the main menu.

## Project Requirements
This project follows the guidelines for The C Sharp Academy Intermediate Console Application Flash Cards as found here: https://www.thecsharpacademy.com/project/14/flashcards
28 changes: 28 additions & 0 deletions jzhartman.FlashCards/FlashCards.App/FlashCards.App.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FlashCards.Application\FlashCards.Application.csproj" />
<ProjectReference Include="..\FlashCards.ConsoleUI\FlashCards.ConsoleUI.csproj" />
<ProjectReference Include="..\FlashCards.Infrastructure\FlashCards.Infrastructure.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
31 changes: 31 additions & 0 deletions jzhartman.FlashCards/FlashCards.App/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using FlashCards.Application;
using FlashCards.ConsoleUI;
using FlashCards.ConsoleUI.Controllers;
using FlashCards.Infrastructure;
using FlashCards.Infrastructure.Initialization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace FlashCards.App;

internal class Program
{
static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();

var services = new ServiceCollection();
services.AddApplication();
services.AddInfrastructure(config);
services.AddConsoleUI();

var provider = services.BuildServiceProvider();
var initializer = provider.GetRequiredService<DbInitializer>();
initializer.Initialize();

var mainMenu = provider.GetRequiredService<MainMenuService>();
mainMenu.Run();
}
}
6 changes: 6 additions & 0 deletions jzhartman.FlashCards/FlashCards.App/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ConnectionStrings": {
"Default": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=FlashCardsAppDb;Integrated Security=True;Connect Timeout=30;Encrypt=True;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace FlashCards.Application.Cards.Add;

public record AddCardCommand(int StackId, string FrontText, string BackText);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using FlashCards.Application.Interfaces;
using FlashCards.Core.Entities;
using FlashCards.Core.Validation;

namespace FlashCards.Application.Cards.Add;

public class AddCardHandler
{
private readonly ICardRepository _cardRepo;

public AddCardHandler(ICardRepository cardRepo)
{
_cardRepo = cardRepo;
}

public Result<CardResponse> Handle(AddCardCommand cardCommand)
{
var card = new Card(cardCommand.StackId, cardCommand.FrontText, cardCommand.BackText);
var id = _cardRepo.Add(card);
card.SetId(id);

if (id > 0) return Result<CardResponse>.Success(new(id, cardCommand.StackId, cardCommand.FrontText, cardCommand.BackText, 0, 0, 0));
else return Result<CardResponse>.Failure(Errors.InvalidId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using FlashCards.Application.Interfaces;

namespace FlashCards.Application.Cards.Delete;

public class DeleteCardByIdHandler
{
private readonly ICardRepository _repo;

public DeleteCardByIdHandler(ICardRepository repo)
{
_repo = repo;
}

public void Handle(int id)
{
_repo.DeleteById(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace FlashCards.Application.Cards.EditTextBySide;

public record EditCardCommand(int StackId, string FrontText, string BackText);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FlashCards.Application.Cards.EditTextBySide;
using FlashCards.Application.Interfaces;

namespace FlashCards.Application.Cards.EditCard;

public class EditCardHandler
{
private readonly ICardRepository _cardRepo;
private readonly IStackRepository _stackRepo;

public EditCardHandler(ICardRepository cardRepo, IStackRepository stackRepo)
{
_cardRepo = cardRepo;
_stackRepo = stackRepo;
}
public void Handle(CardResponse card, EditCardCommand editedCard)
{
int id = _cardRepo.GetIdByTextAndStackId(card.StackId, card.FrontText, card.BackText);

_cardRepo.UpdateCardText(id, editedCard.FrontText, editedCard.BackText);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using FlashCards.Application.Enums;

namespace FlashCards.Application.Cards.EditTextBySide;

public record EditCardTextBySideCommand(string Text, CardSide Side);
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using FlashCards.Application.Enums;
using FlashCards.Application.Interfaces;
using FlashCards.Core.Validation;

namespace FlashCards.Application.Cards.EditTextBySide;

public class EditCardTextBySideHandler
{
private readonly ICardRepository _cardRepo;

public EditCardTextBySideHandler(ICardRepository cardRepo)
{
_cardRepo = cardRepo;
}

public Result<string> Handle(CardResponse card, EditCardTextBySideCommand editedCard)
{
var cardId = _cardRepo.GetIdByTextAndStackId(card.StackId, card.FrontText, card.BackText);

if (editedCard.Side == CardSide.Front)
{
if (string.IsNullOrWhiteSpace(editedCard.Text))
return Result<string>.Success(card.FrontText);

if (_cardRepo.ExistsByFrontTextExcludingId(editedCard.Text, card.StackId, cardId))
return Result<string>.Failure(Errors.CardFrontTextExists);

if (editedCard.Text.Length > 250)
return Result<string>.Failure(Errors.CardTextLengthTooLong);
}
if (editedCard.Side == CardSide.Back)
{
if (string.IsNullOrWhiteSpace(editedCard.Text))
return Result<string>.Success(card.BackText);

if (_cardRepo.ExistsByBackTextExcludingId(editedCard.Text, card.StackId, cardId))
return Result<string>.Failure(Errors.CardBackTextExists);

if (editedCard.Text.Length > 250)
return Result<string>.Failure(Errors.CardTextLengthTooLong);
}

return Result<string>.Success(editedCard.Text);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace FlashCards.Application.Cards;

public record CardResponse(int Id, int StackId, string FrontText, string BackText, int TimesStudied, int TimesCorrect, int TimesIncorrect);
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using FlashCards.Application.Interfaces;
using FlashCards.Core.Entities;
using FlashCards.Core.Validation;

namespace FlashCards.Application.Cards.GetAllByStackId;

public class GetAllByStackId

{
private readonly ICardRepository _cardRepo;
private readonly IStackRepository _stackRepo;

public GetAllByStackId(ICardRepository cardRepo, IStackRepository stackRepo)
{
_cardRepo = cardRepo;
_stackRepo = stackRepo;
}

public Result<List<CardResponse>> Handle(int stackId)
{
if (_stackRepo.ExistsById(stackId) == false)
return Result<List<CardResponse>>.Failure(Errors.NoStacksExist);

var cards = _cardRepo.GetAllByStackId(stackId);

if (cards.Count == 0)
return Result<List<CardResponse>>.Failure(Errors.NoCardsExist);
else
return Result<List<CardResponse>>.Success(BuildResponse(cards, stackId));
}

private List<CardResponse> BuildResponse(List<Card> cards, int stackId)
{
var outputs = new List<CardResponse>();

foreach (var card in cards)
{
var cardResponse = new CardResponse(card.Id, stackId, card.FrontText, card.BackText, card.TimesStudied, card.TimesCorrect, card.TimesIncorrect);
outputs.Add(cardResponse);
}

return outputs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using FlashCards.Application.Interfaces;

namespace FlashCards.Application.Cards.UpdateCardCounters;

public class UpdateCardCountersHandler
{
private readonly ICardRepository _repo;

public UpdateCardCountersHandler(ICardRepository repo)
{
_repo = repo;
}

public void Handle(List<int> cardsCorrect, List<int> cardsIncorrect)
{
foreach (var card in cardsCorrect)
{
_repo.UpdateCardCounters(card, 1, 1, 0);
}
foreach (var card in cardsIncorrect)
{
_repo.UpdateCardCounters(card, 1, 0, 1);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using FlashCards.Application.Enums;
using FlashCards.Application.Interfaces;
using FlashCards.Core.Validation;

namespace FlashCards.Application.Cards.ValidateCardTextBySide;

public class ValidateCardTextBySide
{
private readonly ICardRepository _cardRepo;

public ValidateCardTextBySide(ICardRepository cardRepo)
{
_cardRepo = cardRepo;
}

public Result<string> Handle(ValidateCardTextBySideCommand card)
{
if (card.Side == CardSide.Front)
{
if (_cardRepo.ExistsByFrontText(card.Text, card.StackId))
return Result<string>.Failure(Errors.CardFrontTextExists);

if (string.IsNullOrWhiteSpace(card.Text))
return Result<string>.Failure(Errors.CardFrontTextRequired);

if (card.Text.Length > 250)
return Result<string>.Failure(Errors.CardTextLengthTooLong);
}
if (card.Side == CardSide.Back)
{
if (_cardRepo.ExistsByBackText(card.Text, card.StackId))
return Result<string>.Failure(Errors.CardBackTextExists);

if (string.IsNullOrWhiteSpace(card.Text))
return Result<string>.Failure(Errors.CardBackTextRequired);

if (card.Text.Length > 250)
return Result<string>.Failure(Errors.CardTextLengthTooLong);
}

return Result<string>.Success(card.Text);
}
}
Loading