diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..858e410 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,27 @@ +name: .NET + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 5.0.x + - name: Change Directory + run: cd Tyche + - name: Restore dependencies + run: dotnet restore Tyche/Tyche.sln + - name: Build + run: dotnet build Tyche/Tyche.sln --no-restore + - name: Test + run: dotnet test Tyche/Tyche.sln --no-build --verbosity normal diff --git a/README.md b/README.md index 0b69018..0b4a01d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,58 @@ -# Tyche -Deck sorter project: a simple project that implements a RESTful API service for sorting a deck of cards by certain parameters +

Tyche

+ +

+ + + +

+ + +

Описание:

+

Простой проект, реализующий сервис RESTful API для сортировки колоды карт по определенным параметрам.

+

Этот проект состоит из серверной части, которая представляет из себя монолитный REST web API проект, включает:

+ +

И из клиентской части, которая представляет из себя консольное приложение с простым интерфейсом взаимодествия:

+ +

Основной функционал приложения:

+ +``` +1 - Создать именованную колоду карт; +2 - Получить созданную колоду карт, выбранную по названию колоды; +3 - Получить список названий созданных колод карт; +4 - Получить все созданные колоды карт; +5 - Удалить все созданные колоды карт; +6 - Удалить созданную колоду карт, выбранную по названию колоды; +7 - Перетасовать колоду карт, выбранную по названию колоды. +``` +

Как запускать:

+

Строка подключения располагается в Tyche.API/appsettings.json (Можно заменить на свою строку подключения)

+ +``` +"DeckContext": "Data Source=(LocalDb)\\MSSQLLocalDB;Database=Deck_DB;Trusted_Connection=True;MultipleActiveResultSets=true" +``` + +

Для успешного запуска приложения необходимо выполнить команду на выбор:

+ + +

Используемые технологии:

+

Серверная и клиентская часть выполненны на c# .NET Core 5, ASP Web API

+

Для хранения данных используется LocalDb

+

Для работы с базой данных используется ORM entity framework core 5

diff --git a/Tyche/Client.CLI/Client.CLI.csproj b/Tyche/Client.CLI/Client.CLI.csproj new file mode 100644 index 0000000..9a4397d --- /dev/null +++ b/Tyche/Client.CLI/Client.CLI.csproj @@ -0,0 +1,13 @@ + + + + Exe + net5.0 + + + + + + + + diff --git a/Tyche/Client.CLI/Client.cs b/Tyche/Client.CLI/Client.cs new file mode 100644 index 0000000..755f143 --- /dev/null +++ b/Tyche/Client.CLI/Client.cs @@ -0,0 +1,248 @@ +using Client.CLI.Infrastructure; +using Client.CLI.Interfaces; +using System; + + +namespace Client.CLI +{ + internal partial class Client + { + private IDeckHttpClient _deckHttpClient; + private const int WIDTH_CONSOLE = 120; + private const int HIGHT_CONSOLE = 30; + + public Client(IDeckHttpClient deckHttpClient) + { + _deckHttpClient = deckHttpClient; + Console.SetWindowSize(WIDTH_CONSOLE, HIGHT_CONSOLE); + + } + + internal void Start() + { + Console.Title= "Tyche Client"; + Console.WriteLine("Client started"); + + var isUserContinue = true; + string userAnswer; + + do + { + Console.Clear(); + + PrintLine(); + PrintMenu(); + PrintLine(); + + Console.Write("\n\tSelect a menu item: "); + + userAnswer = Console.ReadLine(); + + Console.Clear(); + + switch (userAnswer) + { + case "1": + CreateNamedDeckAsync(); + break; + case "2": + GetDeckByNameAsync(); + break; + case "3": + GetCreatedDecksNamesAsync(); + break; + case "4": + GetDecksAsync(); + break; + case "5": + DeleteDecksAsync(); + break; + case "6": + DeleteDeckByNameAsync(); + break; + case "7": + ShuffleDeckByNameAsync(); + break; + case "8": + break; + default : + continue; + } + + Console.ForegroundColor = ConsoleColor.Red; + Console.Write("\n\tAre you sure you want to quit? (y/n): "); + userAnswer = Console.ReadLine(); + Console.ResetColor(); + + isUserContinue = userAnswer.ToLower() == "n"; + + } while (isUserContinue); + } + + public void CreateNamedDeckAsync() + { + var mesg = "\tEnter Decks name: "; + PrintLineGreen(mesg); + + var name = Console.ReadLine(); + + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("\tDecks Type:" + + "\r\n\t\"52\" if you want create StandartDeck = 52 cards " + + "\r\n\t\"36\" if you want create SmallDeck = 36 cards"); + Console.Write("\r\n\tEnter Decks Type: "); + Console.ResetColor(); + var type = Console.ReadLine(); + + var deckType = type.ToDeckType(); + + var response = _deckHttpClient.CreateNamedDeckAsync(name, deckType); + + if(response.Result != null) + Console.WriteLine("\r\n\t" + response.Result); + } + + public void GetCreatedDecksNamesAsync() + { + Console.WriteLine(); + + var response = _deckHttpClient.GetCreatedDecksNamesAsync(); + var count = 1; + + if(response.Result != null) + { + var mesg = "\r\n\tNames of deck(s):"; + PrintGreen(mesg); + + foreach (var name in response.Result) + { + Console.WriteLine($"\r\n\t{count}) " + name); + count++; + } + } + else + { + Console.WriteLine("\r\n\tNo decks of cards created"); + } + } + + public void GetDeckByNameAsync() + { + var mesg = "\tEnter Decks name: "; + PrintLineGreen(mesg); + + var name = Console.ReadLine(); + + var response = _deckHttpClient.GetDeckByNameAsync(name); + if (response.Result != null) + { + response.Result.ShowDeck(); + } + else + { + Console.WriteLine($"\r\n\tNo deck of cards whit name {name} created"); + } + } + + public void GetDecksAsync() + { + var response = _deckHttpClient.GetDecksAsync(); + + if (response.Result != null) + { + foreach (var deck in response.Result) + { + deck.ShowDeck(); + } + } + else + { + Console.WriteLine($"\r\n\tHave no decks of cards"); + } + } + + public void DeleteDeckByNameAsync() + { + var mesg = "\tEnter Decks name: "; + PrintLineGreen(mesg); + var name = Console.ReadLine(); + + var response = _deckHttpClient.DeleteDeckByNameAsync(name); + + if (response.Result != null) + Console.WriteLine("\r\n\t" + response.Result); + } + + public void DeleteDecksAsync() + { + var response = _deckHttpClient.DeleteDecksAsync(); + if (response.Result != null) + Console.WriteLine("\r\n\t" + response.Result); + } + + public void ShuffleDeckByNameAsync() + { + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("\tEnter Decks name: "); + Console.ResetColor(); + + var name = Console.ReadLine(); + + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("\tShuffle Option:" + + "\r\n\t\"1\" if you want to shuffle a deck of cards" + + "\r\n\t\"2\" if you want to shuffle the deck of cards in order"); + Console.Write("\r\n\tEnter Shuffle Option: "); + Console.ResetColor(); + var type = Console.ReadLine(); + + var shuffleType = type.ToShuffleOption(); + + var response = _deckHttpClient.ShuffleDeckByNameAsync(shuffleType, name); + + if (response.Result != null) + Console.WriteLine("\r\n\t" + response.Result); + } + + private static void PrintLineGreen(string mesg) + { + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Green; + Console.Write(mesg); + Console.ResetColor(); + } + + private static void PrintGreen(string mesg) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("\r\n\tNames of deck(s):"); + Console.ResetColor(); + } + + private static void PrintLine() + { + Console.WriteLine(); + Console.BackgroundColor = ConsoleColor.Cyan; + Console.WriteLine(new string(' ', WIDTH_CONSOLE)); + Console.ResetColor(); + Console.WriteLine(); + } + + private static void PrintMenu() + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("\t1 - Create named deck;" + + "\n\t2 - Getting a deck of cards by name;" + + "\n\t3 - Get list of deck names;" + + "\n\t4 - Getting all decks of cards;" + + "\n\t5 - Deleting all deck of cards;" + + "\n\t6 - Deleting deck of cards by name;" + + "\n\t7 - Shuffle named deck in the selected way;" + + "\n\t8 - Exit;"); + Console.ResetColor(); + } + } +} diff --git a/Tyche/Client.CLI/DeckHttpClient.cs b/Tyche/Client.CLI/DeckHttpClient.cs new file mode 100644 index 0000000..4630c44 --- /dev/null +++ b/Tyche/Client.CLI/DeckHttpClient.cs @@ -0,0 +1,184 @@ +using Client.CLI.Infrastructure; +using Client.CLI.Interfaces; +using Client.CLI.Models; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + + +namespace Client.CLI +{ + public class DeckHttpClient : IDeckHttpClient + { + private readonly HttpClient _client; + + public DeckHttpClient(HttpClient client, Uri baseUri) + { + _client = client; + _client.BaseAddress = baseUri; + } + + public async Task CreateNamedDeckAsync(string name, DeckType deckType) + { + var request = new DeckRequestDto + { + Name = name, + DeckType = deckType + }; + + var url = _client.BaseAddress.ToString() + "Deck"; + + var data = JsonConvert.SerializeObject(request); + var content = new StringContent(data, Encoding.UTF8, "application/json"); + + try + { + var response = await _client.PostAsync(url, content); + + var responseBody = await response.Content.ReadAsStringAsync(); + return responseBody; + } + catch (Exception ex) + { + return ex.Message; + } + } + + public async Task DeleteDeckByNameAsync(string name) + { + var url = _client.BaseAddress.ToString() + $"Deck/DeckByName/{name}"; + + try + { + var response = await _client.DeleteAsync(url); + + var responseBody = await response.Content.ReadAsStringAsync(); + return responseBody; + } + catch (Exception ex) + { + return ex.Message; + } + } + + public async Task DeleteDecksAsync() + { + var url = _client.BaseAddress.ToString() + $"Deck/Decks"; + + try + { + var response = await _client.DeleteAsync(url); + + var responseBody = await response.Content.ReadAsStringAsync(); + return responseBody; + } + catch (Exception ex) + { + return ex.Message; + } + } + + public async Task GetCreatedDecksNamesAsync() + { + var url = _client.BaseAddress.ToString() + "Deck/Names"; + + try + { + var response = _client.GetAsync(url).Result; + var json = await response.Content.ReadAsStringAsync(); + + return JsonConvert.DeserializeObject(json); + } + catch (Exception) + { + return null; + } + } + + public async Task GetDeckByNameAsync(string name) + { + var url = _client.BaseAddress.ToString() + $"Deck/DeckByName/{name}"; + try + { + var response = _client.GetAsync(url).Result; + var json = await response.Content.ReadAsStringAsync(); + + var deckResponse = JsonConvert.DeserializeObject(json); + + if(deckResponse != null) + { + var deck = Automapper.MappingToDeck(deckResponse); + return deck; + } + else + { + return null; + } + } + catch (Exception) + { + return null; + } + } + + public async Task GetDecksAsync() + { + var url = _client.BaseAddress.ToString() + $"Deck/Decks"; + try + { + var response = _client.GetAsync(url).Result; + var json = await response.Content.ReadAsStringAsync(); + + var decksResponse = JsonConvert.DeserializeObject(json); + + if (decksResponse != null) + { + var decks = new List(); + foreach (var deckResponse in decksResponse) + { + var deck = Automapper.MappingToDeck(deckResponse); + decks.Add(deck); + } + return decks.ToArray(); + } + else + { + return null; + } + } + catch (Exception) + { + return null; + } + } + + public async Task ShuffleDeckByNameAsync(ShuffleOption shuffleOption, string name) + { + var request = new ShuffleRequest + { + Name = name, + ShuffleOption = (int)shuffleOption + }; + + var url = _client.BaseAddress.ToString() + "Deck/Shuffle"; + + var data = JsonConvert.SerializeObject(request); + var content = new StringContent(data, Encoding.UTF8, "application/json"); + + try + { + var response = await _client.PutAsync(url, content); + + var responseBody = await response.Content.ReadAsStringAsync(); + return responseBody; + } + catch (Exception ex) + { + return ex.Message; + } + } + } +} diff --git a/Tyche/Client.CLI/Infrastructure/Automapper.cs b/Tyche/Client.CLI/Infrastructure/Automapper.cs new file mode 100644 index 0000000..a14a43d --- /dev/null +++ b/Tyche/Client.CLI/Infrastructure/Automapper.cs @@ -0,0 +1,32 @@ +using Client.CLI.Models; +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace Client.CLI.Infrastructure +{ + public static class Automapper + { + public static Deck MappingToDeck(DeckResponse deckResponse) + { + var cardsResponse = deckResponse.Cards.ToList(); + var cards = new List(); + + foreach (var cardResponse in cardsResponse) + { + var splitResult = cardResponse.Split(';'); + + var sequenceNumber = int.Parse(splitResult[0]); + var rank = (Rank)Enum.Parse(typeof(Rank), splitResult[1]); + var suit = (Suit)Enum.Parse(typeof(Suit), splitResult[2]); + + cards.Add(new Card(rank, suit, sequenceNumber)); + } + + var name = deckResponse.Name; + + return new Deck(cards.ToArray(), name); + } + } +} diff --git a/Tyche/Client.CLI/Infrastructure/ClientExtensions.cs b/Tyche/Client.CLI/Infrastructure/ClientExtensions.cs new file mode 100644 index 0000000..322cb6c --- /dev/null +++ b/Tyche/Client.CLI/Infrastructure/ClientExtensions.cs @@ -0,0 +1,51 @@ +using Client.CLI.Models; +using System; + + +namespace Client.CLI.Infrastructure +{ + public static class ClientExtensions + { + public static DeckType ToDeckType(this string value) + { + if (string.IsNullOrWhiteSpace(value)) + return DeckType.SmalDeck; + + if (int.TryParse(value, out var res)) + { + if (res == 36) + { + Enum.TryParse(value, out DeckType smalDeck); + return smalDeck; + } + else if (res == 52) + { + Enum.TryParse(value, out DeckType standartDeck); + return standartDeck; + } + } + return DeckType.SmalDeck; + } + + public static ShuffleOption ToShuffleOption(this string value) + { + if (string.IsNullOrWhiteSpace(value)) + return ShuffleOption.InOrder; + + if (int.TryParse(value, out var res)) + { + if (res == 1) + { + Enum.TryParse(value, out ShuffleOption simpleShuffle); + return simpleShuffle; + } + else if (res == 2) + { + Enum.TryParse(value, out ShuffleOption inOrder); + return inOrder; + } + } + return ShuffleOption.InOrder; + } + } +} diff --git a/Tyche/Client.CLI/Infrastructure/DisplayAttribute.cs b/Tyche/Client.CLI/Infrastructure/DisplayAttribute.cs new file mode 100644 index 0000000..537ba21 --- /dev/null +++ b/Tyche/Client.CLI/Infrastructure/DisplayAttribute.cs @@ -0,0 +1,15 @@ +using System; + + +namespace Client.CLI.Infrastructure +{ + public class DisplayAttribute : Attribute + { + public string Name { get; set; } + + public DisplayAttribute() + { + + } + } +} diff --git a/Tyche/Client.CLI/Infrastructure/EnumDisplayExtensions.cs b/Tyche/Client.CLI/Infrastructure/EnumDisplayExtensions.cs new file mode 100644 index 0000000..d77c85d --- /dev/null +++ b/Tyche/Client.CLI/Infrastructure/EnumDisplayExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + + +namespace Client.CLI.Infrastructure +{ + public static class EnumDisplayExtensions + { + public static string GetDisplayName(this Enum enumValue) + { + var attributeType = typeof(DisplayAttribute); + + FieldInfo fi = enumValue.GetType().GetField(enumValue.ToString()); + + var attributes = fi.GetCustomAttributes(attributeType, false) + .Cast() + .ToList(); + + List names = new List(); + foreach (var attribute in attributes) + { + string name = attribute.Name; + names.Add(name); + } + + return String.Join("", names); + } + } +} diff --git a/Tyche/Client.CLI/Interfaces/IDeckHttpClient.cs b/Tyche/Client.CLI/Interfaces/IDeckHttpClient.cs new file mode 100644 index 0000000..70dfa27 --- /dev/null +++ b/Tyche/Client.CLI/Interfaces/IDeckHttpClient.cs @@ -0,0 +1,24 @@ +using Client.CLI.Models; +using System.Threading.Tasks; + + +namespace Client.CLI.Interfaces +{ + public interface IDeckHttpClient + { + Task CreateNamedDeckAsync(string name, DeckType deckType); + + Task GetDeckByNameAsync(string name); + + Task GetDecksAsync(); + + Task GetCreatedDecksNamesAsync(); + + Task DeleteDeckByNameAsync(string name); + + Task ShuffleDeckByNameAsync(ShuffleOption shuffleRequest, string name); + + Task DeleteDecksAsync(); + + } +} diff --git a/Tyche/Client.CLI/Models/Card.cs b/Tyche/Client.CLI/Models/Card.cs new file mode 100644 index 0000000..091814f --- /dev/null +++ b/Tyche/Client.CLI/Models/Card.cs @@ -0,0 +1,78 @@ +using Client.CLI.Infrastructure; +using System; +using System.Text; + +namespace Client.CLI.Models +{ + public class Card + { + public int SequenceNumber { get; private set; } + + public ConsoleColor Color { get; private set; } + + public char UnicodeSign { get; private set; } + + public Rank Rank { get; } + + public Suit Suit { get; } + + public Card(Rank rank, Suit suit, int sequenceNumber) + { + if (suit == Suit.Hearts || suit == Suit.Diamonds) + Color = ConsoleColor.Red; + else + Color = ConsoleColor.White; + + Rank = rank; + Suit = suit; + + SequenceNumber = sequenceNumber; + + UnicodeSign = SetUnicodeSign(); + } + + private char SetUnicodeSign() + { + switch (Suit) + { + case Suit.Spades: + return '\u2660'; + case Suit.Clubs: + return '\u2663'; + case Suit.Hearts: + return '\u2665'; + case Suit.Diamonds: + return '\u2666'; + default: + return '-'; + } + } + + public void ShowCard() + { + Console.OutputEncoding = Encoding.Unicode; + + Console.ForegroundColor = this.Color; + + string rank = Rank.GetDisplayName(); + + string output = $"{UnicodeSign}{rank}"; + + if (output.Length < 9) + { + output = String.Concat(output, new string(' ', 9 - output.Length)); + } + + Console.WriteLine($"\t[{output}]"); + } + + public override string ToString() + { + string rank = Rank.GetDisplayName(); + + var result = $"{Suit}-{rank}"; + + return result; + } + } +} diff --git a/Tyche/Client.CLI/Models/Deck.cs b/Tyche/Client.CLI/Models/Deck.cs new file mode 100644 index 0000000..849edc5 --- /dev/null +++ b/Tyche/Client.CLI/Models/Deck.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + + +namespace Client.CLI.Models +{ + public class Deck + { + private const int WIDTH_CONSOLE = 120; + + private Stack _cards; + + public Stack Cards { get { return _cards; } } + + public int Count { get; private set; } + + public string Name { get; private set; } + + public Deck(Card[] cards, string name) + { + if (cards == null || cards.Length == 0) + throw new ArgumentException(nameof(cards)); + + Count = cards.Length; + _cards = new Stack(cards); + Name = name; + } + + public Card Pull() + { + Count = _cards.Count - 1; + return _cards.Pop(); + } + + public virtual void ShowDeck() + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("\r\n\tDeck name: "); + Console.ResetColor(); + Console.WriteLine(Name + "\r\n"); + + + while (Cards.Count > 0) + { + var card = Cards.Pop(); + card.ShowCard(); + } + + PrintLine(); + } + + private static void PrintLine() + { + Console.WriteLine(); + Console.BackgroundColor = ConsoleColor.Cyan; + Console.WriteLine(new string(' ', WIDTH_CONSOLE)); + Console.ResetColor(); + Console.WriteLine(); + } + } +} diff --git a/Tyche/Client.CLI/Models/DeckRequestDto.cs b/Tyche/Client.CLI/Models/DeckRequestDto.cs new file mode 100644 index 0000000..6863525 --- /dev/null +++ b/Tyche/Client.CLI/Models/DeckRequestDto.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + + +namespace Client.CLI.Models +{ + public class DeckRequestDto + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("deckType")] + public DeckType DeckType { get; set; } + } +} diff --git a/Tyche/Client.CLI/Models/DeckResponse.cs b/Tyche/Client.CLI/Models/DeckResponse.cs new file mode 100644 index 0000000..40f5b94 --- /dev/null +++ b/Tyche/Client.CLI/Models/DeckResponse.cs @@ -0,0 +1,10 @@ + +namespace Client.CLI.Models +{ + public class DeckResponse + { + public string[] Cards { get; set; } + + public string Name { get; set; } + } +} diff --git a/Tyche/Client.CLI/Models/DeckType.cs b/Tyche/Client.CLI/Models/DeckType.cs new file mode 100644 index 0000000..bab3db5 --- /dev/null +++ b/Tyche/Client.CLI/Models/DeckType.cs @@ -0,0 +1,9 @@ + +namespace Client.CLI.Models +{ + public enum DeckType + { + StandartDeck = 52, + SmalDeck = 36 + } +} diff --git a/Tyche/Client.CLI/Models/Rank.cs b/Tyche/Client.CLI/Models/Rank.cs new file mode 100644 index 0000000..abc74f9 --- /dev/null +++ b/Tyche/Client.CLI/Models/Rank.cs @@ -0,0 +1,35 @@ +using Client.CLI.Infrastructure; + + +namespace Client.CLI.Models +{ + public enum Rank + { + [Display(Name = "2")] + Two, + [Display(Name = "3")] + Three, + [Display(Name = "4")] + Four, + [Display(Name = "5")] + Five, + [Display(Name = "6")] + Six, + [Display(Name = "7")] + Seven, + [Display(Name = "8")] + Eight, + [Display(Name = "9")] + Nine, + [Display(Name = "10")] + Ten, + [Display(Name = "Валет")] + Jack, + [Display(Name = "Дама")] + Queen, + [Display(Name = "Король")] + King, + [Display(Name = "Туз")] + Ace + } +} diff --git a/Tyche/Client.CLI/Models/ShuffleOption.cs b/Tyche/Client.CLI/Models/ShuffleOption.cs new file mode 100644 index 0000000..3a2d24a --- /dev/null +++ b/Tyche/Client.CLI/Models/ShuffleOption.cs @@ -0,0 +1,10 @@ + + +namespace Client.CLI.Models +{ + public enum ShuffleOption + { + SimpleShuffle, + InOrder + } +} diff --git a/Tyche/Client.CLI/Models/ShuffleRequest.cs b/Tyche/Client.CLI/Models/ShuffleRequest.cs new file mode 100644 index 0000000..2bfcab2 --- /dev/null +++ b/Tyche/Client.CLI/Models/ShuffleRequest.cs @@ -0,0 +1,11 @@ + + +namespace Client.CLI.Models +{ + public class ShuffleRequest + { + public int ShuffleOption { get; set; } + + public string Name { get; set; } + } +} diff --git a/Tyche/Client.CLI/Models/Suit.cs b/Tyche/Client.CLI/Models/Suit.cs new file mode 100644 index 0000000..226e1a2 --- /dev/null +++ b/Tyche/Client.CLI/Models/Suit.cs @@ -0,0 +1,17 @@ +using Client.CLI.Infrastructure; + + +namespace Client.CLI.Models +{ + public enum Suit + { + [Display(Name = "Пики")] + Spades, + [Display(Name = "Трефы")] + Clubs, + [Display(Name = "Червы")] + Hearts, + [Display(Name = "Бубны")] + Diamonds + } +} diff --git a/Tyche/Client.CLI/Program.cs b/Tyche/Client.CLI/Program.cs new file mode 100644 index 0000000..49ec181 --- /dev/null +++ b/Tyche/Client.CLI/Program.cs @@ -0,0 +1,18 @@ +using System; +using System.Net.Http; + +namespace Client.CLI +{ + internal class Program + { + static void Main(string[] args) + { + var httpClient = new HttpClient(); + var baseUri = new Uri("https://localhost:5001"); + var deckHttpClient = new DeckHttpClient(httpClient, baseUri); + + var application = new Client(deckHttpClient); + application.Start(); + } + } +} diff --git a/Tyche/Tyche.API/Controllers/DeckController.cs b/Tyche/Tyche.API/Controllers/DeckController.cs index 1079b7f..02e56e2 100644 --- a/Tyche/Tyche.API/Controllers/DeckController.cs +++ b/Tyche/Tyche.API/Controllers/DeckController.cs @@ -1,34 +1,40 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Tyche.API.Infrastructure; +using Tyche.API.Models; using Tyche.Domain.Interfaces; -using Tyche.Domain.Models; + namespace Tyche.API.Controllers { + ///

+ /// Controller for creating, receiving, working with a deck of cards + /// [ApiController] [Route("[controller]")] public class DeckController : ControllerBase { private readonly IDeckService _deckService; + private readonly Automapper _automapper = new Automapper(); public DeckController(IDeckService deckService) { _deckService = deckService; } + /// + /// Create a named deck of cards + /// + /// + /// [HttpPost] - [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] - public ActionResult Create(Suit suit) + public async Task Create(DeckRequest request) { - if (ModelState.IsValid == false) - return BadRequest(); - try { - var response = _deckService.CreateNamedDeck(suit); + var response = await _deckService.CreateNamedDeckAsync(request.Name, request.DeckType); return Ok(response); } catch (Exception ex) @@ -37,10 +43,95 @@ public ActionResult Create(Suit suit) } } - [HttpGet("{DeckSuit:int}")] - public IActionResult Get(int suit) + /// + /// Getting a deck of cards by name + /// + /// + /// + [HttpGet("DeckByName/{name}")] + public async Task> GetDeckByName(string name) + { + + var deck = await _deckService.GetDeckByNameAsync(name); + if (deck != null) + { + var response = _automapper.MappingToDeckResponse(deck); + return Ok(response); + } + else + { + return Ok($"Have no decks of cards {name}"); + } + } + + /// + /// Get a list of deck names + /// + /// + [HttpGet("Names/")] + public async Task GetNames() + { + var names = await _deckService.GetCreatedDecksNamesAsync(); + if (names.Length != 0) + return Ok(names); + else + return Ok("No decks of cards"); + } + + /// + /// Getting all decks of cards + /// + /// + [HttpGet("Decks/")] + public async Task> GetDecks() + { + var decks = await _deckService.GetDecksAsync(); + if (decks != null) + { + var response = new List(); + + foreach (var deck in decks) + { + response.Add(_automapper.MappingToDeckResponse(deck)); + } + return Ok(response.ToArray()); + } + else + return Ok("No decks of cards"); + } + + /// + /// Deleting deck of cards by name + /// + /// + [HttpDelete("DeckByName/{name}")] + public async Task Delete(string name) + { + var response = await _deckService.DeleteDeckByNameAsync(name); + return Ok(response); + } + + /// + /// Deleting all deck of cards + /// + /// + /// + [HttpDelete("Decks/")] + public async Task Delete() + { + var response = await _deckService.DeleteDecksAsync(); + return Ok(response); + } + + /// + /// Shuffle dekcs of cards in the selected way + /// + /// + /// + [HttpPut("Shuffle/")] + public async Task Update(ShuffleRequest request) { - var response = _deckService.GetNamedDeck(suit); + var response = await _deckService.ShuffleDeckByNameAsync(request.ShuffleOption, request.Name); return Ok(response); } } diff --git a/Tyche/Tyche.API/Infrastructure/Automapper.cs b/Tyche/Tyche.API/Infrastructure/Automapper.cs new file mode 100644 index 0000000..a4735d5 --- /dev/null +++ b/Tyche/Tyche.API/Infrastructure/Automapper.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Tyche.API.Models; +using Tyche.Domain.Models; + +namespace Tyche.API.Infrastructure +{ + internal class Automapper + { + public DeckResponse MappingToDeckResponse(Deck deck) + { + var cards = deck.Cards; + var cardsResponse = new List(); + + foreach (var card in cards) + { + var cardResponse = $"{card.SequenceNumber};{card.Rank.ToString()};{card.Suit.ToString()}"; + cardsResponse.Add(cardResponse); + } + + return new DeckResponse + { + Cards = cardsResponse.ToArray(), + Name = deck.Name + }; + } + } +} diff --git a/Tyche/Tyche.API/Models/DeckRequest.cs b/Tyche/Tyche.API/Models/DeckRequest.cs new file mode 100644 index 0000000..b5590c2 --- /dev/null +++ b/Tyche/Tyche.API/Models/DeckRequest.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using Tyche.Domain.Models; + +namespace Tyche.API.Models +{ + public class DeckRequest + { + [Required] + [StringLength(50, MinimumLength = 3, ErrorMessage = "Name length must be in the family from {2}-{1} characters")] + public string Name { get; set; } + + [Required] + public DeckType DeckType { get; set; } + } +} diff --git a/Tyche/Tyche.API/Models/DeckResponse.cs b/Tyche/Tyche.API/Models/DeckResponse.cs new file mode 100644 index 0000000..eccdf6f --- /dev/null +++ b/Tyche/Tyche.API/Models/DeckResponse.cs @@ -0,0 +1,10 @@ + +namespace Tyche.API.Models +{ + public class DeckResponse + { + public string[] Cards { get; set; } + + public string Name { get; set; } + } +} diff --git a/Tyche/Tyche.API/Models/ShuffleRequest.cs b/Tyche/Tyche.API/Models/ShuffleRequest.cs new file mode 100644 index 0000000..19d8200 --- /dev/null +++ b/Tyche/Tyche.API/Models/ShuffleRequest.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + + +namespace Tyche.API.Models +{ + public class ShuffleRequest + { + [Range(1, 2)] + public int ShuffleOption { get; set; } + + [StringLength(50, MinimumLength = 3, ErrorMessage = "Name length must be in the family from {2}-{1} characters")] + public string Name { get; set; } + } +} diff --git a/Tyche/Tyche.API/Program.cs b/Tyche/Tyche.API/Program.cs index 9c78a54..8979996 100644 --- a/Tyche/Tyche.API/Program.cs +++ b/Tyche/Tyche.API/Program.cs @@ -1,11 +1,8 @@ using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Tyche.DataAccess.MsSql.Infrastructure; + namespace Tyche.API { @@ -13,7 +10,11 @@ public class Program { public static void Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + Console.Title = "Tyche Server"; + CreateHostBuilder(args) + .Build() + .MigrateDatabase() + .Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => diff --git a/Tyche/Tyche.API/Startup.cs b/Tyche/Tyche.API/Startup.cs index b428a82..3aa2d79 100644 --- a/Tyche/Tyche.API/Startup.cs +++ b/Tyche/Tyche.API/Startup.cs @@ -1,19 +1,17 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.IO; using Tyche.BusinessLogic.Services; +using Tyche.DataAccess.MsSql.Context; +using Tyche.DataAccess.MsSql.Repository; using Tyche.Domain.Interfaces; + namespace Tyche.API { public class Startup @@ -25,21 +23,24 @@ public Startup(IConfiguration configuration) public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddControllers(); services.AddScoped(); + services.AddScoped(); + + services.AddDbContext(x => + x.UseSqlServer(Configuration.GetConnectionString("DeckContext"))); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Tyche.API", Version = "v1" }); - }); + var filePath = Path.Combine(System.AppContext.BaseDirectory, "Tyche.API.xml"); + c.IncludeXmlComments(filePath); + }); } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) diff --git a/Tyche/Tyche.API/Tyche.API.csproj b/Tyche/Tyche.API/Tyche.API.csproj index b49eb3e..47713c4 100644 --- a/Tyche/Tyche.API/Tyche.API.csproj +++ b/Tyche/Tyche.API/Tyche.API.csproj @@ -2,19 +2,29 @@ net5.0 + True + ..\\Tyche\Tyche.API\Tyche.API.xml + + + + 1701;1702;1591; + + + + 1701;1702;1591; + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - - + diff --git a/Tyche/Tyche.API/Tyche.API.xml b/Tyche/Tyche.API/Tyche.API.xml new file mode 100644 index 0000000..d2eadbb --- /dev/null +++ b/Tyche/Tyche.API/Tyche.API.xml @@ -0,0 +1,53 @@ + + + + Tyche.API + + + + + Controller for creating, receiving, working with a deck of cards + + + + + Create a named deck of cards + + + + + + + Getting a deck of cards by suit + + + + + + + Get a list of deck names + + + + + + Getting a all decks of cards + + + + + + Deleting a deck of cards by suit + + + + + + + Shuffle a named deck of cards in the selected way + + + + + + diff --git a/Tyche/Tyche.API/appsettings.json b/Tyche/Tyche.API/appsettings.json index d9d9a9b..d394ec8 100644 --- a/Tyche/Tyche.API/appsettings.json +++ b/Tyche/Tyche.API/appsettings.json @@ -6,5 +6,8 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "DeckContext": "Data Source=(LocalDb)\\MSSQLLocalDB;Database=Deck_DB;Trusted_Connection=True;MultipleActiveResultSets=true" + } } diff --git a/Tyche/Tyche.BusinessLogic/Infrasturcure/CardShuffler.cs b/Tyche/Tyche.BusinessLogic/Infrasturcure/CardShuffler.cs new file mode 100644 index 0000000..3f1abc1 --- /dev/null +++ b/Tyche/Tyche.BusinessLogic/Infrasturcure/CardShuffler.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Tyche.Domain.Models; + + +namespace Tyche.BusinessLogic.Infrasturcure +{ + internal class CardShuffler + { + protected static Random _random = new Random(); + + public Deck SimpleShuffle(Deck deck) + { + var sequenceNumbersRandom = Enumerable.Range(1, deck.Count + 1) + .OrderBy(n => _random.Next(1, deck.Count + 1)) + .ToArray(); + + List cards = new List(); + var count = 0; + foreach (var card in deck.Cards) + { + cards.Add(new Card(card.Rank, card.Suit, sequenceNumbersRandom[count])); + count++; + } + + return new Deck(cards.ToArray(), deck.Name); + } + } +} diff --git a/Tyche/Tyche.BusinessLogic/Services/DeckService.cs b/Tyche/Tyche.BusinessLogic/Services/DeckService.cs index e45629c..41d52d3 100644 --- a/Tyche/Tyche.BusinessLogic/Services/DeckService.cs +++ b/Tyche/Tyche.BusinessLogic/Services/DeckService.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Tyche.BusinessLogic.Infrasturcure; using Tyche.Domain.Interfaces; using Tyche.Domain.Models; @@ -7,14 +10,134 @@ namespace Tyche.BusinessLogic.Services { public class DeckService : IDeckService { - public string CreateNamedDeck(Suit suit) + private const int SIMPLE_SHUFFLING = 1; + private const int ASCENDING_ORDER = 2; + + private readonly IDeckRepository _deckRepository; + private readonly CardShuffler _cardShuffler = new(); + + public DeckService(IDeckRepository deckRepository) + { + _deckRepository = deckRepository; + } + + public async Task CreateNamedDeckAsync(string name, DeckType deckType) + { + try + { + var existingDeck = await GetDeckByNameAsync(name); + if (existingDeck != null) return $"Deck whit this name {name} already exists"; + + var cards = GetCardsArray(deckType); + var deck = new Deck(cards, name); + + var isCreate = await _deckRepository.AddAsync(deck, name); + if (isCreate) + return "Success created"; + else + return "Deck not created"; + } + catch (Exception ex) + { + return ex.Message; + } + } + + public async Task GetDeckByNameAsync(string name) + { + var deck = await _deckRepository.GetDeckAsync(name); + return deck; + } + + public async Task GetCreatedDecksNamesAsync() + { + return await _deckRepository.GetDecksNamesAsync(); + } + + public async Task GetDecksAsync() { - throw new NotImplementedException(); + return await _deckRepository.GetDecksAsync(); } - public Deck GetNamedDeck(int suit) + public async Task DeleteDeckByNameAsync(string name) { - throw new NotImplementedException(); + try + { + var existingDeck = await GetDeckByNameAsync(name); + if (existingDeck != null) + { + await _deckRepository.DeleteAsync(name); + return "Success deleted"; + } + else + return "This deck was not created"; + } + catch (Exception ex) + { + return ex.Message; + } + } + + public async Task DeleteDecksAsync() + { + var decks = await _deckRepository.GetDecksAsync(); + if (decks == null) return "Decks was not created"; + + await _deckRepository.DeleteDecksAsync(); + return "Success deleted"; + } + + public async Task ShuffleDeckByNameAsync(int sortOption, string name) + { + var existingDeck = await GetDeckByNameAsync(name); + + if (existingDeck != null) + { + switch (sortOption) + { + case SIMPLE_SHUFFLING: + await UpdateDeckAsync(name, _cardShuffler.SimpleShuffle(existingDeck)); + break; + case ASCENDING_ORDER: + await DeleteDeckByNameAsync(name); + await CreateNamedDeckAsync(name, (DeckType)existingDeck.Count); + break; + default: + await UpdateDeckAsync(name, _cardShuffler.SimpleShuffle(existingDeck)); + break; + } + return "Successfully shuffled"; + } + else + return "This decks was not created"; + } + + private async Task UpdateDeckAsync(string name, Deck deck) + { + await DeleteDeckByNameAsync(name); + await _deckRepository.AddAsync(deck, name); + } + + private static Card[] GetCardsArray(DeckType deckType) + { + var cards = new List(); + var sequenceNumber = 1; + + foreach (Suit suit in Enum.GetValues(typeof(Suit))) + { + foreach (Rank rank in Enum.GetValues(typeof(Rank))) + { + if (deckType == DeckType.SmalDeck) + { + if (rank < Rank.Six) + continue; + } + + cards.Add(new Card(rank, suit, sequenceNumber)); + sequenceNumber++; + } + } + return cards.ToArray(); } } -} +} \ No newline at end of file diff --git a/Tyche/Tyche.DataAccess.MsSql/Context/DeckContext.cs b/Tyche/Tyche.DataAccess.MsSql/Context/DeckContext.cs new file mode 100644 index 0000000..e125202 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Context/DeckContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Tyche.DataAccess.MsSql.Entities; + +namespace Tyche.DataAccess.MsSql.Context +{ + public class DeckContext : DbContext + { + public DbSet Decks { get; set; } + public DbSet Cards { get; set; } + + public DeckContext(DbContextOptions options) : base(options) + { + + } + + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Entities/CardEntity.cs b/Tyche/Tyche.DataAccess.MsSql/Entities/CardEntity.cs new file mode 100644 index 0000000..7c5e084 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Entities/CardEntity.cs @@ -0,0 +1,22 @@ +Ы +using System.ComponentModel.DataAnnotations; + +namespace Tyche.DataAccess.MsSql.Entities +{ + public class CardEntity + { + public int Id { get; set; } + + public int SequenceNumber { get; set; } + + [StringLength(20)] + public string Rank { get; set; } + + [StringLength(20)] + public string Suit { get; set; } + + public int DeckId { get; set; } + + public DeckEntity Deck { get; set; } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Entities/DeckEntity.cs b/Tyche/Tyche.DataAccess.MsSql/Entities/DeckEntity.cs new file mode 100644 index 0000000..2c8d189 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Entities/DeckEntity.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Tyche.DataAccess.MsSql.Entities +{ + public class DeckEntity + { + public int Id { get; set; } + + [StringLength(50, MinimumLength = 3)] + public string Name { get; set; } + + public ICollection Deck { get; set; } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Infrastructure/Automapper.cs b/Tyche/Tyche.DataAccess.MsSql/Infrastructure/Automapper.cs new file mode 100644 index 0000000..e4a7d74 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Infrastructure/Automapper.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Tyche.DataAccess.MsSql.Entities; +using Tyche.Domain.Models; + + +namespace Tyche.DataAccess.MsSql.Infrastructure +{ + internal class Automapper + { + public DeckEntity MappingToDeckEntity(Deck deck, string name) + { + var cards = new List(); + + foreach (var card in deck.Cards) + { + var cardEntity = new CardEntity + { + Rank = card.Rank.ToString(), + Suit = card.Suit.ToString(), + SequenceNumber = card.SequenceNumber + }; + cards.Add(cardEntity); + } + + return new DeckEntity + { + Name = name, + Deck = cards.ToArray() + }; + } + + public Deck MappingToDeck(DeckEntity deckEntity, string name) + { + var cards = new List(); + var decks = deckEntity.Deck; + var sequenceNumber = 1; + + foreach (var deck in decks) + { + var rank = (Rank)Enum.Parse(typeof(Rank), deck.Rank); + var suit = (Suit)Enum.Parse(typeof(Suit), deck.Suit); + + var card = new Card(rank, suit, deck.SequenceNumber); + sequenceNumber++; + cards.Add(card); + } + + return new Deck(cards.ToArray(), name); + } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Infrastructure/MigrationManager.cs b/Tyche/Tyche.DataAccess.MsSql/Infrastructure/MigrationManager.cs new file mode 100644 index 0000000..ea6f8e3 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Infrastructure/MigrationManager.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using Tyche.DataAccess.MsSql.Context; + + + +namespace Tyche.DataAccess.MsSql.Infrastructure +{ + public static class MigrationManager + { + public static IHost MigrateDatabase(this IHost host) + { + using (var scope = host.Services.CreateScope()) + { + using (var appContext = scope.ServiceProvider.GetRequiredService()) + { + try + { + appContext.Database.Migrate(); + } + catch (Exception) + { + //Log errors or do anything you think it's needed + throw; + } + } + } + + return host; + } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Migrations/20220503171037_First.Designer.cs b/Tyche/Tyche.DataAccess.MsSql/Migrations/20220503171037_First.Designer.cs new file mode 100644 index 0000000..ecdb105 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Migrations/20220503171037_First.Designer.cs @@ -0,0 +1,85 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tyche.DataAccess.MsSql.Context; + +namespace Tyche.DataAccess.MsSql.Migrations +{ + [DbContext(typeof(DeckContext))] + [Migration("20220503171037_First")] + partial class First + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.16") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.CardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DeckId") + .HasColumnType("int"); + + b.Property("Rank") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("SequenceNumber") + .HasColumnType("int"); + + b.Property("Suit") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeckId"); + + b.ToTable("Cards"); + }); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.DeckEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Decks"); + }); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.CardEntity", b => + { + b.HasOne("Tyche.DataAccess.MsSql.Entities.DeckEntity", "Deck") + .WithMany("Deck") + .HasForeignKey("DeckId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deck"); + }); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.DeckEntity", b => + { + b.Navigation("Deck"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Migrations/20220503171037_First.cs b/Tyche/Tyche.DataAccess.MsSql/Migrations/20220503171037_First.cs new file mode 100644 index 0000000..be7e41c --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Migrations/20220503171037_First.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tyche.DataAccess.MsSql.Migrations +{ + public partial class First : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Decks", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Decks", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Cards", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + SequenceNumber = table.Column(type: "int", nullable: false), + Rank = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Suit = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + DeckId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Cards", x => x.Id); + table.ForeignKey( + name: "FK_Cards_Decks_DeckId", + column: x => x.DeckId, + principalTable: "Decks", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Cards_DeckId", + table: "Cards", + column: "DeckId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Cards"); + + migrationBuilder.DropTable( + name: "Decks"); + } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Migrations/DeckContextModelSnapshot.cs b/Tyche/Tyche.DataAccess.MsSql/Migrations/DeckContextModelSnapshot.cs new file mode 100644 index 0000000..339f470 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Migrations/DeckContextModelSnapshot.cs @@ -0,0 +1,83 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tyche.DataAccess.MsSql.Context; + +namespace Tyche.DataAccess.MsSql.Migrations +{ + [DbContext(typeof(DeckContext))] + partial class DeckContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.16") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.CardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DeckId") + .HasColumnType("int"); + + b.Property("Rank") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("SequenceNumber") + .HasColumnType("int"); + + b.Property("Suit") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("DeckId"); + + b.ToTable("Cards"); + }); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.DeckEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Decks"); + }); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.CardEntity", b => + { + b.HasOne("Tyche.DataAccess.MsSql.Entities.DeckEntity", "Deck") + .WithMany("Deck") + .HasForeignKey("DeckId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Deck"); + }); + + modelBuilder.Entity("Tyche.DataAccess.MsSql.Entities.DeckEntity", b => + { + b.Navigation("Deck"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Repository/DeckRepository.cs b/Tyche/Tyche.DataAccess.MsSql/Repository/DeckRepository.cs new file mode 100644 index 0000000..c77c572 --- /dev/null +++ b/Tyche/Tyche.DataAccess.MsSql/Repository/DeckRepository.cs @@ -0,0 +1,109 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tyche.DataAccess.MsSql.Context; +using Tyche.DataAccess.MsSql.Infrastructure; +using Tyche.Domain.Interfaces; +using Tyche.Domain.Models; + + +namespace Tyche.DataAccess.MsSql.Repository +{ + public class DeckRepository : IDeckRepository + { + private readonly DeckContext _context; + private readonly Automapper _automapper = new Automapper(); + + public DeckRepository(DeckContext context) + { + _context = context; + } + + public async Task AddAsync(Deck deck, string name) + { + if (deck == null) return false; + + if (String.IsNullOrWhiteSpace(name)) return false; + + var deckEntity = _automapper.MappingToDeckEntity(deck, name); + + await _context.AddAsync(deckEntity); + await _context.SaveChangesAsync(); + + return true; + } + + public async Task GetDeckAsync(string name) + { + var deckEntity = await _context.Decks.FirstOrDefaultAsync(deck => deck.Name == name); + if (deckEntity == null) return null; + + deckEntity.Deck = await _context.Cards.Where(card => card.DeckId == deckEntity.Id).OrderByDescending(card => card.SequenceNumber).ToArrayAsync(); + + var deck = _automapper.MappingToDeck(deckEntity, name); + return deck; + } + + public async Task GetDecksNamesAsync() + { + var decksEntity = await _context.Decks.ToListAsync(); + var decks = new List(); + + foreach (var deck in decksEntity) + { + decks.Add(deck.Name); + } + return decks.ToArray(); + } + + public async Task GetDecksAsync() + { + var decksEntity = await _context.Decks.ToListAsync(); + if (decksEntity.Count == 0) return null; + + foreach (var deckEntity in decksEntity) + { + deckEntity.Deck = await _context.Cards.Where(card => card.DeckId == deckEntity.Id).OrderByDescending(card => card.SequenceNumber).ToArrayAsync(); + } + + var decksResponse = new List(); + + foreach (var deck in decksEntity) + { + decksResponse.Add(_automapper.MappingToDeck(deck, deck.Name)); + } + + return decksResponse.ToArray(); + } + + public async Task DeleteAsync(string name) + { + var deck = await _context.Decks.FirstOrDefaultAsync(deck => deck.Name == name); + if (deck == null) return false; + + _context.Decks.Remove(deck); + + var cardsEntity = await _context.Cards.Where(card => card.DeckId == deck.Id).ToArrayAsync(); + if (cardsEntity == null) return false; + + _context.Cards.RemoveRange(cardsEntity); + + await _context.SaveChangesAsync(); + return true; + } + + public async Task DeleteDecksAsync() + { + var decksEntity = _context.Decks.ToList(); + if (decksEntity.Count == 0) return false; + + foreach (var deckEntity in decksEntity) + { + await DeleteAsync(deckEntity.Name); + } + return true; + } + } +} diff --git a/Tyche/Tyche.DataAccess.MsSql/Tyche.DataAccess.MsSql.csproj b/Tyche/Tyche.DataAccess.MsSql/Tyche.DataAccess.MsSql.csproj index aefaff0..2496076 100644 --- a/Tyche/Tyche.DataAccess.MsSql/Tyche.DataAccess.MsSql.csproj +++ b/Tyche/Tyche.DataAccess.MsSql/Tyche.DataAccess.MsSql.csproj @@ -4,6 +4,20 @@ net5.0 + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Tyche/Tyche.Domain/Interfaces/IDeckRepository.cs b/Tyche/Tyche.Domain/Interfaces/IDeckRepository.cs new file mode 100644 index 0000000..4197163 --- /dev/null +++ b/Tyche/Tyche.Domain/Interfaces/IDeckRepository.cs @@ -0,0 +1,21 @@ + +using System.Threading.Tasks; +using Tyche.Domain.Models; + +namespace Tyche.Domain.Interfaces +{ + public interface IDeckRepository + { + Task AddAsync(Deck deck, string name); + + Task GetDeckAsync(string name); + + Task DeleteAsync(string name); + + Task GetDecksAsync(); + + Task GetDecksNamesAsync(); + + Task DeleteDecksAsync(); + } +} diff --git a/Tyche/Tyche.Domain/Interfaces/IDeckService.cs b/Tyche/Tyche.Domain/Interfaces/IDeckService.cs index da74af5..d891d92 100644 --- a/Tyche/Tyche.Domain/Interfaces/IDeckService.cs +++ b/Tyche/Tyche.Domain/Interfaces/IDeckService.cs @@ -1,12 +1,23 @@ -using Tyche.Domain.Models; +using System.Threading.Tasks; +using Tyche.Domain.Models; namespace Tyche.Domain.Interfaces { public interface IDeckService { - Deck GetNamedDeck(int suit); + Task CreateNamedDeckAsync(string name, DeckType deckType); - string CreateNamedDeck(Suit suit); + Task GetDeckByNameAsync(string name); + + Task GetDecksAsync(); + + Task GetCreatedDecksNamesAsync(); + + Task DeleteDeckByNameAsync(string name); + + Task ShuffleDeckByNameAsync(int sortOption, string name); + + Task DeleteDecksAsync(); } } diff --git a/Tyche/Tyche.Domain/Models/Card.cs b/Tyche/Tyche.Domain/Models/Card.cs index 999c669..9a8d0b0 100644 --- a/Tyche/Tyche.Domain/Models/Card.cs +++ b/Tyche/Tyche.Domain/Models/Card.cs @@ -3,10 +3,17 @@ namespace Tyche.Domain.Models { public class Card { - public int Id { get; set; } + public int SequenceNumber { get; } - public Rank Rank { get; set; } + public Rank Rank { get; } - public Suit Suit { get; set; } + public Suit Suit { get; } + + public Card(Rank rank, Suit suit, int sequenceNumber) + { + Rank = rank; + Suit = suit; + SequenceNumber = sequenceNumber; + } } } diff --git a/Tyche/Tyche.Domain/Models/Deck.cs b/Tyche/Tyche.Domain/Models/Deck.cs index c7a5d89..87a0cd1 100644 --- a/Tyche/Tyche.Domain/Models/Deck.cs +++ b/Tyche/Tyche.Domain/Models/Deck.cs @@ -8,20 +8,25 @@ public class Deck { private Stack _cards; - public int Id { get; set; } + public Stack Cards { get { return _cards; } } - public Suit Name { get; set; } + public int Count { get; private set; } - public Deck(Card[] cards) + public string Name { get; private set; } + + public Deck(Card[] cards, string name) { if (cards == null || cards.Length == 0) throw new ArgumentException(nameof(cards)); + Count = cards.Length; _cards = new Stack(cards); + Name = name; } public Card Pull() { + Count = _cards.Count - 1; return _cards.Pop(); } } diff --git a/Tyche/Tyche.Domain/Models/DeckType.cs b/Tyche/Tyche.Domain/Models/DeckType.cs new file mode 100644 index 0000000..c36bbf4 --- /dev/null +++ b/Tyche/Tyche.Domain/Models/DeckType.cs @@ -0,0 +1,9 @@ + +namespace Tyche.Domain.Models +{ + public enum DeckType + { + StandartDeck = 52, + SmalDeck = 36 + } +} diff --git a/Tyche/Tyche.Domain/Models/Rank.cs b/Tyche/Tyche.Domain/Models/Rank.cs index d97fffb..8f0ab6b 100644 --- a/Tyche/Tyche.Domain/Models/Rank.cs +++ b/Tyche/Tyche.Domain/Models/Rank.cs @@ -1,35 +1,20 @@ -using System.ComponentModel.DataAnnotations; - - + namespace Tyche.Domain.Models { public enum Rank { - [Display(Name = "Ace")] - Ace, - [Display(Name = "2")] Two, - [Display(Name = "3")] Three, - [Display(Name = "4")] Four, - [Display(Name = "5")] Five, - [Display(Name = "6")] Six, - [Display(Name = "7")] Seven, - [Display(Name = "8")] Eight, - [Display(Name = "9")] Nine, - [Display(Name = "10")] Ten, - [Display(Name = "Jack")] Jack, - [Display(Name = "Queen")] Queen, - [Display(Name = "King")] - King + King, + Ace } } diff --git a/Tyche/Tyche.IntegrationTests/Tyche.IntegrationTests.csproj b/Tyche/Tyche.IntegrationTests/Tyche.IntegrationTests.csproj deleted file mode 100644 index b43a489..0000000 --- a/Tyche/Tyche.IntegrationTests/Tyche.IntegrationTests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net5.0 - - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/Tyche/Tyche.IntegrationTests/UnitTest1.cs b/Tyche/Tyche.IntegrationTests/UnitTest1.cs deleted file mode 100644 index 3d61659..0000000 --- a/Tyche/Tyche.IntegrationTests/UnitTest1.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Xunit; - -namespace Tyche.IntegrationTests -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } -} diff --git a/Tyche/Tyche.UnitTests/DeckServiceTests.cs b/Tyche/Tyche.UnitTests/DeckServiceTests.cs new file mode 100644 index 0000000..5698977 --- /dev/null +++ b/Tyche/Tyche.UnitTests/DeckServiceTests.cs @@ -0,0 +1,82 @@ +using Moq; +using NUnit.Framework; +using System.Collections.Generic; +using Tyche.BusinessLogic.Services; +using Tyche.Domain.Interfaces; +using Tyche.Domain.Models; + + +namespace Tyche.UnitTests +{ + public class DeckServiceTests + { + private DeckService _service; + private Mock _deckRepositoryMock; + + [SetUp] + public void SetUp() + { + _deckRepositoryMock = new Mock(); + _service = new DeckService(_deckRepositoryMock.Object); + } + + [Test] + public void GetDeckByName_ShouldReturnTrue() + { + //arrange + var expectedDeckName = "TestDeck"; + + var Cards = new List(); + var card1 = new Card(Rank.Ace, Suit.Diamonds, 1); + var card2 = new Card(Rank.Ace, Suit.Spades, 2); + + Cards.Add(card1); + Cards.Add(card2); + + var deck = new Deck(Cards.ToArray(), expectedDeckName); + + _deckRepositoryMock + .Setup(x => x.GetDeckAsync(expectedDeckName)) + .ReturnsAsync(() => deck) + .Verifiable(); + + //act + var result = _service.GetDeckByNameAsync(expectedDeckName); + + //assert + _deckRepositoryMock.VerifyAll(); + + Assert.AreEqual(expectedDeckName, result.Result.Name); + } + + public void GetCreatedDecksNames_ShouldReturnTrue() + { + //arrange + var expectedDeckName = "TestDeck"; + + var Cards = new List(); + var card1 = new Card(Rank.Ace, Suit.Diamonds, 1); + var card2 = new Card(Rank.Ace, Suit.Spades, 2); + + Cards.Add(card1); + Cards.Add(card2); + + var deck = new Deck(Cards.ToArray(), expectedDeckName); + + _deckRepositoryMock + .Setup(x => x.GetDecksNamesAsync()) + .ReturnsAsync(() => new string[] { expectedDeckName }) + .Verifiable(); + + //act + var result = _service.GetCreatedDecksNamesAsync(); + + //assert + _deckRepositoryMock.VerifyAll(); + + Assert.NotNull(result.Result); + Assert.IsNotEmpty(result.Result); + Assert.AreEqual(expectedDeckName, result.Result[0]); + } + } +} \ No newline at end of file diff --git a/Tyche/Tyche.UnitTests/Tyche.UnitTests.csproj b/Tyche/Tyche.UnitTests/Tyche.UnitTests.csproj index 7b2e2b0..95efe8b 100644 --- a/Tyche/Tyche.UnitTests/Tyche.UnitTests.csproj +++ b/Tyche/Tyche.UnitTests/Tyche.UnitTests.csproj @@ -8,15 +8,10 @@ - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + + + + diff --git a/Tyche/Tyche.UnitTests/UnitTest1.cs b/Tyche/Tyche.UnitTests/UnitTest1.cs deleted file mode 100644 index b7caf15..0000000 --- a/Tyche/Tyche.UnitTests/UnitTest1.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Xunit; - -namespace Tyche.UnitTests -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } -} diff --git a/Tyche/Tyche.sln b/Tyche/Tyche.sln index 178f6df..62213ce 100644 --- a/Tyche/Tyche.sln +++ b/Tyche/Tyche.sln @@ -9,11 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tyche.Domain", "Tyche.Domai EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tyche.BusinessLogic", "Tyche.BusinessLogic\Tyche.BusinessLogic.csproj", "{5FC62F53-A220-4483-A965-12B214712FF8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tyche.DataAccess.MsSql", "Tyche.DataAccess.Postgres\Tyche.DataAccess.MsSql.csproj", "{67AB4D45-4003-42C5-99DA-CD7361ECCB58}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tyche.DataAccess.MsSql", "Tyche.DataAccess.MsSql\Tyche.DataAccess.MsSql.csproj", "{DF99F54A-0C13-47AC-BF39-06183CD40417}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tyche.UnitTests", "Tyche.UnitTests\Tyche.UnitTests.csproj", "{80151682-6D95-4A4F-8D30-C370E9FEC971}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client.CLI", "Client.CLI\Client.CLI.csproj", "{146160C3-F8F7-4A03-A3DE-AD7A396EC808}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tyche.IntegrationTests", "Tyche.IntegrationTests\Tyche.IntegrationTests.csproj", "{45CEE789-234D-4ED9-A11C-8876D4C5DDB5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tyche.UnitTests", "Tyche.UnitTests\Tyche.UnitTests.csproj", "{8609CFE9-D770-45CF-B692-AF12A9BD5E85}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,18 +33,18 @@ Global {5FC62F53-A220-4483-A965-12B214712FF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {5FC62F53-A220-4483-A965-12B214712FF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FC62F53-A220-4483-A965-12B214712FF8}.Release|Any CPU.Build.0 = Release|Any CPU - {67AB4D45-4003-42C5-99DA-CD7361ECCB58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {67AB4D45-4003-42C5-99DA-CD7361ECCB58}.Debug|Any CPU.Build.0 = Debug|Any CPU - {67AB4D45-4003-42C5-99DA-CD7361ECCB58}.Release|Any CPU.ActiveCfg = Release|Any CPU - {67AB4D45-4003-42C5-99DA-CD7361ECCB58}.Release|Any CPU.Build.0 = Release|Any CPU - {80151682-6D95-4A4F-8D30-C370E9FEC971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80151682-6D95-4A4F-8D30-C370E9FEC971}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80151682-6D95-4A4F-8D30-C370E9FEC971}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80151682-6D95-4A4F-8D30-C370E9FEC971}.Release|Any CPU.Build.0 = Release|Any CPU - {45CEE789-234D-4ED9-A11C-8876D4C5DDB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45CEE789-234D-4ED9-A11C-8876D4C5DDB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45CEE789-234D-4ED9-A11C-8876D4C5DDB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45CEE789-234D-4ED9-A11C-8876D4C5DDB5}.Release|Any CPU.Build.0 = Release|Any CPU + {DF99F54A-0C13-47AC-BF39-06183CD40417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF99F54A-0C13-47AC-BF39-06183CD40417}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF99F54A-0C13-47AC-BF39-06183CD40417}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF99F54A-0C13-47AC-BF39-06183CD40417}.Release|Any CPU.Build.0 = Release|Any CPU + {146160C3-F8F7-4A03-A3DE-AD7A396EC808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {146160C3-F8F7-4A03-A3DE-AD7A396EC808}.Debug|Any CPU.Build.0 = Debug|Any CPU + {146160C3-F8F7-4A03-A3DE-AD7A396EC808}.Release|Any CPU.ActiveCfg = Release|Any CPU + {146160C3-F8F7-4A03-A3DE-AD7A396EC808}.Release|Any CPU.Build.0 = Release|Any CPU + {8609CFE9-D770-45CF-B692-AF12A9BD5E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8609CFE9-D770-45CF-B692-AF12A9BD5E85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8609CFE9-D770-45CF-B692-AF12A9BD5E85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8609CFE9-D770-45CF-B692-AF12A9BD5E85}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tyche/Tyche/Tyche.API/Tyche.API.xml b/Tyche/Tyche/Tyche.API/Tyche.API.xml new file mode 100644 index 0000000..7d0c147 --- /dev/null +++ b/Tyche/Tyche/Tyche.API/Tyche.API.xml @@ -0,0 +1,59 @@ + + + + Tyche.API + + + + + Controller for creating, receiving, working with a deck of cards + + + + + Create a named deck of cards + + + + + + + Getting a deck of cards by name + + + + + + + Get a list of deck names + + + + + + Getting all decks of cards + + + + + + Deleting deck of cards by name + + + + + + Deleting all deck of cards + + + + + + + Shuffle dekcs of cards in the selected way + + + + + +