From 2d2f8b6b7651249e57114b885bf2214bad050219 Mon Sep 17 00:00:00 2001 From: luxick Date: Thu, 27 Feb 2020 23:11:36 +0100 Subject: [PATCH] Improved client handling and episode models --- EstusShots.Client/EstusShotsClient.cs | 41 ++++- .../Controls/BindableListControl.cs | 168 ++++++++++++++++++ EstusShots.Gtk/Controls/BindableListView.cs | 135 -------------- EstusShots.Gtk/Controls/DataColumn.cs | 26 ++- EstusShots.Gtk/Controls/LoadingMode.cs | 26 +++ EstusShots.Gtk/EstusShots.Gtk.csproj | 4 - EstusShots.Gtk/MainWindow.cs | 117 ++++++------ EstusShots.Gtk/MainWindow.glade | 70 ++++---- .../Controllers/EpisodeController.cs | 52 ++++++ .../Controllers/EpisodesController.cs | 38 ++++ .../Controllers/SeasonController.cs | 19 +- .../Controllers/SeasonsController.cs | 2 +- EstusShots.Server/Mapping/Profiles.cs | 7 +- EstusShots.Server/Models/Episode.cs | 24 +++ EstusShots.Server/Models/Season.cs | 3 + EstusShots.Server/Program.cs | 1 + .../Services/EstusShotsContext.cs | 2 + EstusShots.Server/Startup.cs | 1 + EstusShots.Shared/Dto/Episode.cs | 25 +++ EstusShots.Shared/{Models => Dto}/Season.cs | 2 +- EstusShots.Shared/EstusShots.Shared.csproj | 2 +- EstusShots.Shared/Models/OperationResult.cs | 32 ++++ 22 files changed, 540 insertions(+), 257 deletions(-) create mode 100644 EstusShots.Gtk/Controls/BindableListControl.cs delete mode 100644 EstusShots.Gtk/Controls/BindableListView.cs create mode 100644 EstusShots.Gtk/Controls/LoadingMode.cs create mode 100644 EstusShots.Server/Controllers/EpisodeController.cs create mode 100644 EstusShots.Server/Controllers/EpisodesController.cs create mode 100644 EstusShots.Server/Models/Episode.cs create mode 100644 EstusShots.Shared/Dto/Episode.cs rename EstusShots.Shared/{Models => Dto}/Season.cs (91%) create mode 100644 EstusShots.Shared/Models/OperationResult.cs diff --git a/EstusShots.Client/EstusShotsClient.cs b/EstusShots.Client/EstusShotsClient.cs index dba29d2..83af933 100644 --- a/EstusShots.Client/EstusShotsClient.cs +++ b/EstusShots.Client/EstusShotsClient.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Text; using System.Text.Json; using System.Threading.Tasks; +using EstusShots.Shared.Dto; using EstusShots.Shared.Models; namespace EstusShots.Client @@ -13,7 +15,7 @@ namespace EstusShots.Client { PropertyNameCaseInsensitive = true, }; - + private string ApiUrl { get; } private HttpClient HttpClient { get; } @@ -23,12 +25,39 @@ namespace EstusShots.Client HttpClient = new HttpClient {Timeout = TimeSpan.FromSeconds(10)}; } - public async Task> GetSeasons() + public async Task<(OperationResult, List)> GetSeasons() { - var response = HttpClient.GetAsync(ApiUrl + "seasons").Result; - var jsonData = await response.Content.ReadAsStringAsync(); - var data = JsonSerializer.Deserialize>(jsonData, _serializerOptions); - return data; + try + { + var response = await HttpClient.GetAsync(ApiUrl + "seasons"); + var jsonData = await response.Content.ReadAsStringAsync(); + var data = JsonSerializer.Deserialize>(jsonData, _serializerOptions); + return (new OperationResult(), data); + } + catch (Exception e) + { + return (new OperationResult(e), new List()); + } } + + public async Task<(OperationResult, Guid)> CreateSeason(Season season) + { + try + { + var content = new StringContent(JsonSerializer.Serialize(season), Encoding.UTF8, "application/json"); + var response = await HttpClient.PostAsync(ApiUrl + "season", content); + if (!response.IsSuccessStatusCode) + { + return (new OperationResult(false, response.ReasonPhrase), Guid.Empty); + } + // TODO should give the created id + return (new OperationResult(), Guid.Empty); + } + catch (Exception e) + { + return (new OperationResult(e), Guid.Empty); + } + } + } } \ No newline at end of file diff --git a/EstusShots.Gtk/Controls/BindableListControl.cs b/EstusShots.Gtk/Controls/BindableListControl.cs new file mode 100644 index 0000000..53643ac --- /dev/null +++ b/EstusShots.Gtk/Controls/BindableListControl.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GLib; +using Gtk; +using DateTime = GLib.DateTime; + +namespace EstusShots.Gtk.Controls +{ + public class SelectionChangedEventArgs : EventArgs + { + public SelectionChangedEventArgs(object selection) + { + Selection = selection; + } + + public object Selection { get; } + } + + public delegate void SelectionChangedEventHandler(object o, SelectionChangedEventArgs args); + + public class BindableListControl + { + /// + /// Initialize a new BindableListView with an existing TreeView Widget + /// + /// The columns of the grid + /// Unique key field in the item sources type + /// An instance of an existing TreeView Widget + public BindableListControl(List columns, string keyField, TreeView treeView = null) + { + TreeView = treeView ?? new TreeView(); + Columns = columns; + KeyField = keyField; + InitTreeViewColumns(); + InitListStore(); + TreeView.Model = ListStore; + Items = new List(); + + TreeView.Selection.Changed += TreeView_SelectionChanged; + } + + /// The GTK ListStore that is managed by this . + public ListStore ListStore { get; internal set; } + + /// The GTK TreeView control that is managed by this . + public TreeView TreeView { get; } + + /// Property of the element type that is used as a unique identifier for accessing elements. + public string KeyField { get; } + + /// The collection of all elements, that should be shown in the list view. + public List Items { get; set; } + + /// The currently selected item in the view. + public T SelectedItem { get; set; } + + /// All columns that are displayed in the list. + public List Columns { get; } + + /// + /// Event will be invoked when the selected item in the has changed. + /// + public event SelectionChangedEventHandler OnSelectionChanged; + + /// + /// Set elements from the property in the . + /// + /// + public void DataBind() + { + ListStore.Clear(); + Items.ForEach(BindItem); + } + + private void BindItem(T item) + { + var row = new List(); + foreach (var column in Columns) + { + var prop = item.GetType().GetProperty(column.PropertyName); + if (prop == null) + throw new TypeLoadException( + $"Property '{column.PropertyName}' does not exist on Type '{item.GetType()}'"); + var val = prop.GetValue(item); + if (column.Format != null) val = column.Format(val); + row.Add(val.ToString()); + } + + // The key value must be the first value in the row + var key = item.GetType().GetProperty(KeyField)?.GetValue(item); + row.Insert(0, key); + try + { + ListStore.AppendValues(row.ToArray()); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + private void TreeView_SelectionChanged(object sender, EventArgs e) + { + if (!(sender is TreeSelection selection)) return; + selection.GetSelected(out var model, out var iter); + var key = model.GetValue(iter, 0); + var item = Items.FirstOrDefault(x => + { + var prop = x.GetType().GetProperty(KeyField); + var value = prop?.GetValue(x); + return value != null && value.Equals(key); + }); + + if (item == null) + { + Console.WriteLine($"No item for key '{key}' found in data store"); + return; + } + + SelectedItem = item; + OnSelectionChanged?.Invoke(this, new SelectionChangedEventArgs(SelectedItem)); + } + + private void InitTreeViewColumns() + { + foreach (var dataColumn in Columns) + { + // Offset by one, because the first column in the data store is fixed to the key value of the row + var index = Columns.IndexOf(dataColumn) + 1; + var column = new TreeViewColumn( + dataColumn.Title, + new CellRendererText(), + "text", index) + { + Resizable = true, + Reorderable = true + }; + TreeView.AppendColumn(column); + } + } + + private void InitListStore() + { + var types = Columns + .Select(x => + { + var propType = typeof(T).GetProperty(x.PropertyName)?.PropertyType; + var gType = (GType) propType; + if (gType.ToString() == "GtkSharpValue") gType = MapType(propType); + return gType; + }); + var data = new DateTime(); + // The first column in the data store is always the key field. + var columns = new List {(GType) typeof(T).GetProperty(KeyField)?.PropertyType}; + columns.AddRange(types); + ListStore = new ListStore(columns.ToArray()); + } + + private static GType MapType(Type type) + { + return type.Name switch + { + _ => GType.String + }; + } + } +} \ No newline at end of file diff --git a/EstusShots.Gtk/Controls/BindableListView.cs b/EstusShots.Gtk/Controls/BindableListView.cs deleted file mode 100644 index 6baf06f..0000000 --- a/EstusShots.Gtk/Controls/BindableListView.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Gtk; - -namespace EstusShots.Gtk.Controls -{ - public class SelectionChangedEventArgs : EventArgs - { - public object Selection { get; } - - public SelectionChangedEventArgs(object selection) - { - Selection = selection; - } - } - - public delegate void SelectionChangedEventHandler(object o, SelectionChangedEventArgs args); - - public class BindableListView - { - public ListStore ListStore { get; internal set; } - - public TreeView TreeView { get; } - - public string KeyField { get; } - - public IEnumerable Items { get; set; } - - public T SelectedItem { get; set; } - - public List Columns { get; } - - public event SelectionChangedEventHandler OnSelectionChanged; - - /// - /// Initialize a new BindableListView with an existing TreeView Widget - /// - /// The columns of the grid - /// Unique key field in the item sources type - /// An instance of an existing TreeView Widget - public BindableListView(List columns, string keyField, TreeView treeView = null) - { - TreeView = treeView ?? new TreeView(); - Columns = columns; - KeyField = keyField; - InitTreeViewColumns(); - InitListStore(); - TreeView.Model = ListStore; - Items = new List(); - - TreeView.Selection.Changed += TreeView_SelectionChanged; - } - - private void TreeView_SelectionChanged(object sender, EventArgs e) - { - if (!(sender is TreeSelection selection)) return; - selection.GetSelected(out var model, out var iter); - var key = model.GetValue(iter, 0); - var item = Items.FirstOrDefault(x => - { - var prop = x.GetType().GetProperty(KeyField); - var value = prop?.GetValue(x); - return value != null && value.Equals(key); - }); - - if (item == null) - { - Console.WriteLine($"No item for key '{key}' found in data store"); - return; - } - - SelectedItem = item; - OnSelectionChanged?.Invoke(this, new SelectionChangedEventArgs(SelectedItem)); - } - - public void DataBind() - { - ListStore.Clear(); - foreach (var item in Items) - { - var row = new List(); - foreach (var column in Columns) - { - var prop = item.GetType().GetProperty(column.PropertyName); - - if (prop == null) - throw new TypeLoadException( - $"Property '{column.PropertyName}' does not exist on Type '{item.GetType()}'"); - - row.Add(prop.GetValue(item)); - } - - // The key value must be the first value in the row - var key = item.GetType().GetProperty(KeyField)?.GetValue(item); - row.Insert(0, key); - - try - { - ListStore.AppendValues(row.ToArray()); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } - - private void InitTreeViewColumns() - { - foreach (var dataColumn in Columns) - { - // Offset by one, because the first column in the data store is fixed to the key value of the row - var index = Columns.IndexOf(dataColumn) + 1; - var column = new TreeViewColumn( - dataColumn.Title, - new CellRendererText(), - "text", index); - TreeView.AppendColumn(column); - } - } - - private void InitListStore() - { - var types = Columns - .Select(x => typeof(T).GetProperty(x.PropertyName)?.PropertyType); - - // The first column in the data store is always the key field. - var columns = new List {typeof(T).GetProperty(KeyField)?.PropertyType}; - columns.AddRange(types); - - ListStore = new ListStore(columns.ToArray()); - } - } -} \ No newline at end of file diff --git a/EstusShots.Gtk/Controls/DataColumn.cs b/EstusShots.Gtk/Controls/DataColumn.cs index 375e054..df2f6a7 100644 --- a/EstusShots.Gtk/Controls/DataColumn.cs +++ b/EstusShots.Gtk/Controls/DataColumn.cs @@ -1,16 +1,32 @@ +using System; + namespace EstusShots.Gtk.Controls { public class DataColumn { - public string PropertyName { get; set; } - - public string Title { get; set; } - - public DataColumn() { } + public DataColumn() + { + } public DataColumn(string propertyName) { PropertyName = propertyName; } + + /// + /// The name of the property in the data source, that should be show nin the view + /// + public string PropertyName { get; } + + /// + /// The column header. + /// + public string Title { get; set; } + + /// + /// Applies the given transformation on each item in the column. + /// This changes only the display of the value. + /// + public Func Format { get; set; } } } \ No newline at end of file diff --git a/EstusShots.Gtk/Controls/LoadingMode.cs b/EstusShots.Gtk/Controls/LoadingMode.cs new file mode 100644 index 0000000..2d50341 --- /dev/null +++ b/EstusShots.Gtk/Controls/LoadingMode.cs @@ -0,0 +1,26 @@ +using System; + +namespace EstusShots.Gtk.Controls +{ + internal class LoadingMode : IDisposable + { + private MainWindow Window { get; set; } + + public LoadingMode(MainWindow window) + { + Window = window; + Window.LoadButton.Sensitive = false; + Window.NewSeasonButton.Sensitive = false; + Window.SeasonsView.Sensitive = false; + Window.SeasonsOverlay.AddOverlay(Window.LoadingSpinner); + } + + public void Dispose() + { + Window.LoadButton.Sensitive = true; + Window.NewSeasonButton.Sensitive = true; + Window.SeasonsView.Sensitive = true; + Window.SeasonsOverlay.Remove(Window.LoadingSpinner); + } + } +} \ No newline at end of file diff --git a/EstusShots.Gtk/EstusShots.Gtk.csproj b/EstusShots.Gtk/EstusShots.Gtk.csproj index 0c3735a..4ddfaa2 100644 --- a/EstusShots.Gtk/EstusShots.Gtk.csproj +++ b/EstusShots.Gtk/EstusShots.Gtk.csproj @@ -21,8 +21,4 @@ - - - - diff --git a/EstusShots.Gtk/MainWindow.cs b/EstusShots.Gtk/MainWindow.cs index 29ff01f..929536f 100644 --- a/EstusShots.Gtk/MainWindow.cs +++ b/EstusShots.Gtk/MainWindow.cs @@ -1,35 +1,28 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; -using System.Text; -using System.Text.Json; +using System.Threading.Tasks; using EstusShots.Client; using EstusShots.Gtk.Controls; -using EstusShots.Shared.Models; +using EstusShots.Shared.Dto; using Gtk; -using Application = Gtk.Application; -using DateTime = System.DateTime; -using Task = System.Threading.Tasks.Task; using UI = Gtk.Builder.ObjectAttribute; namespace EstusShots.Gtk { - class MainWindow : Window + internal class MainWindow : Window { private const string ApiUrl = "http://localhost:5000/api/"; - - private EstusShotsClient Client { get; } - private BindableListView SeasonsView { get; } - - [UI] private readonly TreeView _seasonsView = null; - [UI] private readonly Button _loadButton = null; - [UI] private readonly Button _newSeasonButton = null; [UI] private readonly Label _infoLabel = null; - [UI] private readonly Overlay _seasonsOverlay = null; - [UI] private readonly Box _loadingSpinner = null; + [UI] public readonly Button LoadButton = null; + [UI] public readonly Box LoadingSpinner = null; + [UI] public readonly Button NewSeasonButton = null; + [UI] public readonly Overlay SeasonsOverlay = null; + [UI] public readonly TreeView SeasonsView = null; - public MainWindow() : this(new Builder("MainWindow.glade")) { } + public MainWindow() : this(new Builder("MainWindow.glade")) + { + } private MainWindow(Builder builder) : base(builder.GetObject("MainWindow").Handle) { @@ -37,18 +30,30 @@ namespace EstusShots.Gtk Client = new EstusShotsClient("http://localhost:5000/api/"); DeleteEvent += Window_DeleteEvent; - _loadButton.Clicked += LoadButtonClicked; - _newSeasonButton.Clicked += NewSeasonButtonOnClicked; + LoadButton.Clicked += LoadButtonClicked; + NewSeasonButton.Clicked += NewSeasonButtonOnClicked; var seasonsColumns = new List { - new DataColumn(nameof(Season.DisplayName)){Title = "Name"} + new DataColumn(nameof(Season.DisplayName)) {Title = "Name"}, + new DataColumn(nameof(Season.Description)) {Title = "Description"}, + new DataColumn(nameof(Season.Start)) + { + Title = "Start", + Format = date => (date as DateTime?)?.ToString("dd.MM.yyyy hh:mm") + } }; - SeasonsView = new BindableListView(seasonsColumns, nameof(Season.SeasonId) ,_seasonsView); - SeasonsView.OnSelectionChanged += SeasonsViewOnOnSelectionChanged; + SeasonsControl = new BindableListControl(seasonsColumns, nameof(Season.SeasonId), SeasonsView); + SeasonsControl.OnSelectionChanged += SeasonsViewOnOnSelectionChanged; Info("Application Started"); + + // No need to wait for the loading to finnish + var _ = ReloadSeasons(); } + private EstusShotsClient Client { get; } + private BindableListControl SeasonsControl { get; } + private void SeasonsViewOnOnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (!(e.Selection is Season season)) return; @@ -57,70 +62,56 @@ namespace EstusShots.Gtk private async void NewSeasonButtonOnClicked(object sender, EventArgs e) { - var nextNum = SeasonsView.Items.Any() ? SeasonsView.Items.Max(x => x.Number) + 1 : 1 ; + using var _ = new LoadingMode(this); var season = new Season { Game = "Test Game", - Number = nextNum, - Start = DateTime.Now - }; - var content = new StringContent(JsonSerializer.Serialize(season), Encoding.UTF8, "application/json"); - var client = new HttpClient(); - try - { - var response = await client.PostAsync(ApiUrl + "season", content); + Number = SeasonsControl.Items.Any() ? SeasonsControl.Items.Max(x => x.Number) + 1 : 1, + Start = DateTime.Now, + Description = "This is a demo description!" + }; - if (!response.IsSuccessStatusCode) - { - _infoLabel.Text = $"Error while creating Season: {response.ReasonPhrase}"; - return; - } - - await ReloadSeasons(); - Info("Created new Season"); - } - catch (Exception ex) + var (res, _) = await Client.CreateSeason(season); + if (!res.Success) { - _infoLabel.Text = $"Exception Occured: {ex.Message}"; - Console.WriteLine(ex.Message); + _infoLabel.Text = $"Error while creating Season: {res.ShortMessage}"; + return; } + + await ReloadSeasons(); + Info("Created new Season"); } - + private async void LoadButtonClicked(object sender, EventArgs a) { + using var _ = new LoadingMode(this); Info("Loading Seasons..."); await ReloadSeasons(); - Info("List Refreshed"); } private async Task ReloadSeasons() { - LoadingMode(true); - var seasons = await Task.Factory.StartNew(() => Client.GetSeasons().Result); - SeasonsView.Items = seasons; - SeasonsView.DataBind(); - LoadingMode(false); + var (res, seasons) = await Task.Factory.StartNew(() => Client.GetSeasons().Result); + if (!res.Success) + { + _infoLabel.Text = $"Refresh Failed: {res.ShortMessage}"; + return; + } + + SeasonsControl.Items = seasons; + SeasonsControl.DataBind(); + Info("List Refreshed"); } - + private void Window_DeleteEvent(object sender, DeleteEventArgs a) { Application.Quit(); } - private void LoadingMode(bool active) - { - _loadButton.Sensitive = !active; - _newSeasonButton.Sensitive = !active; - _seasonsView.Sensitive = !active; - if (active) - _seasonsOverlay.AddOverlay(_loadingSpinner); - else - _seasonsOverlay.Remove(_loadingSpinner); - } - private void Info(string message) { _infoLabel.Text = message; } + } } \ No newline at end of file diff --git a/EstusShots.Gtk/MainWindow.glade b/EstusShots.Gtk/MainWindow.glade index 47d977e..1f67c95 100644 --- a/EstusShots.Gtk/MainWindow.glade +++ b/EstusShots.Gtk/MainWindow.glade @@ -2,6 +2,37 @@ + + True + False + center + center + 5 + + + True + False + True + + + False + True + 0 + + + + + True + False + Loading.... + + + False + True + 1 + + + False Estus Shots @@ -20,7 +51,7 @@ 2 vertical - + True False @@ -29,7 +60,7 @@ True in - + True True horizontal @@ -56,7 +87,7 @@ False end - + Reload True True @@ -69,7 +100,7 @@ - + New Season True True @@ -132,35 +163,4 @@ - - True - False - center - center - 5 - - - True - False - True - - - False - True - 0 - - - - - True - False - Loading.... - - - False - True - 1 - - - diff --git a/EstusShots.Server/Controllers/EpisodeController.cs b/EstusShots.Server/Controllers/EpisodeController.cs new file mode 100644 index 0000000..776bf3f --- /dev/null +++ b/EstusShots.Server/Controllers/EpisodeController.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using EstusShots.Server.Models; +using EstusShots.Server.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace EstusShots.Server.Controllers +{ + [ApiController] + [Route("/api/[controller]")] + public class EpisodeController : ControllerBase + { + private readonly EstusShotsContext _context; + private readonly IMapper _mapper; + private readonly ILogger _logger; + + public EpisodeController(EstusShotsContext context, IMapper mapper, ILogger logger) + { + _context = context; + _mapper = mapper; + _logger = logger; + } + + [HttpGet("{id}")] + public async Task> GetEpisode(Guid id) + { + var episode = await _context.Seasons.FindAsync(id); + if (episode == null) {return NotFound();} + + var episodeDto = _mapper.Map(episode); + return episodeDto; + } + + [HttpPost] + public async Task> CreateSeason(Shared.Dto.Episode episodeDto) + { + var episode = _mapper.Map(episodeDto); + _context.Episodes.Add(episode); + try + { + await _context.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Error while saving object"); + } + return CreatedAtAction(nameof(GetEpisode), new {id = episode.EpisodeId}, episode); + } + } +} \ No newline at end of file diff --git a/EstusShots.Server/Controllers/EpisodesController.cs b/EstusShots.Server/Controllers/EpisodesController.cs new file mode 100644 index 0000000..b81693b --- /dev/null +++ b/EstusShots.Server/Controllers/EpisodesController.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using EstusShots.Server.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Dto = EstusShots.Shared.Dto; + +namespace EstusShots.Server.Controllers +{ + [ApiController] + [Route("/api/[controller]")] + public class EpisodesController : ControllerBase + { + private readonly EstusShotsContext _context; + private readonly IMapper _mapper; + private readonly ILogger _logger; + + public EpisodesController(ILogger logger, IMapper mapper, EstusShotsContext context) + { + _logger = logger; + _mapper = mapper; + _context = context; + } + + [HttpGet("seasonId")] + public async Task>> GetEpisodes(Guid seasonId) + { + _logger.LogDebug($"All"); + var episodes = await _context.Episodes.Where(x => x.SeasonId == seasonId).ToListAsync(); + var dtos = _mapper.Map>(episodes); + return dtos; + } + } +} \ No newline at end of file diff --git a/EstusShots.Server/Controllers/SeasonController.cs b/EstusShots.Server/Controllers/SeasonController.cs index c848499..697cc38 100644 --- a/EstusShots.Server/Controllers/SeasonController.cs +++ b/EstusShots.Server/Controllers/SeasonController.cs @@ -4,7 +4,8 @@ using AutoMapper; using EstusShots.Server.Models; using EstusShots.Server.Services; using Microsoft.AspNetCore.Mvc; -using Dto = EstusShots.Shared.Models; +using Microsoft.Extensions.Logging; +using Dto = EstusShots.Shared.Dto; namespace EstusShots.Server.Controllers { @@ -14,18 +15,20 @@ namespace EstusShots.Server.Controllers { private readonly EstusShotsContext _context; private readonly IMapper _mapper; + private readonly ILogger _logger; - public SeasonController(EstusShotsContext context, IMapper mapper) + public SeasonController(EstusShotsContext context, IMapper mapper, ILogger logger) { _context = context; _mapper = mapper; + _logger = logger; } [HttpGet("{id}")] public async Task> GetSeason(Guid id) { var season = await _context.Seasons.FindAsync(id); - if (season == null) return NotFound(); + if (season == null) {return NotFound();} var seasonDto = _mapper.Map(season); return seasonDto; @@ -36,7 +39,15 @@ namespace EstusShots.Server.Controllers { var dbSeason = _mapper.Map(season); _context.Seasons.Add(dbSeason); - await _context.SaveChangesAsync(); + try + { + await _context.SaveChangesAsync(); + _logger.LogInformation("New season created"); + } + catch (Exception e) + { + _logger.LogError(e, "Error while saving Season"); + } return CreatedAtAction(nameof(GetSeason), new {id = dbSeason.SeasonId}, dbSeason); } } diff --git a/EstusShots.Server/Controllers/SeasonsController.cs b/EstusShots.Server/Controllers/SeasonsController.cs index 3ce9ad3..1932ba7 100644 --- a/EstusShots.Server/Controllers/SeasonsController.cs +++ b/EstusShots.Server/Controllers/SeasonsController.cs @@ -4,7 +4,7 @@ using AutoMapper; using EstusShots.Server.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Dto = EstusShots.Shared.Models; +using Dto = EstusShots.Shared.Dto; namespace EstusShots.Server.Controllers { diff --git a/EstusShots.Server/Mapping/Profiles.cs b/EstusShots.Server/Mapping/Profiles.cs index 8ccb991..0b9efc2 100644 --- a/EstusShots.Server/Mapping/Profiles.cs +++ b/EstusShots.Server/Mapping/Profiles.cs @@ -7,8 +7,11 @@ namespace EstusShots.Server.Mapping { public Profiles() { - CreateMap(); - CreateMap(); + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); } } } \ No newline at end of file diff --git a/EstusShots.Server/Models/Episode.cs b/EstusShots.Server/Models/Episode.cs new file mode 100644 index 0000000..21d7413 --- /dev/null +++ b/EstusShots.Server/Models/Episode.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace EstusShots.Server.Models +{ + public class Episode + { + public Guid EpisodeId { get; set; } + + public int Number { get; set; } + + [MaxLength(50)] public string Title { get; set; } = default!; + + public DateTime DateTime { get; set; } + + public DateTime Start { get; set; } + + public DateTime? End { get; set; } + + public Guid SeasonId { get; set; } + + public Season Season { get; set; } = default!; + } +} \ No newline at end of file diff --git a/EstusShots.Server/Models/Season.cs b/EstusShots.Server/Models/Season.cs index 08b449e..423854e 100644 --- a/EstusShots.Server/Models/Season.cs +++ b/EstusShots.Server/Models/Season.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace EstusShots.Server.Models @@ -16,5 +17,7 @@ namespace EstusShots.Server.Models public DateTime Start { get; set; } public DateTime? End { get; set; } + + public List Episodes { get; set; } = default!; } } \ No newline at end of file diff --git a/EstusShots.Server/Program.cs b/EstusShots.Server/Program.cs index e25fef7..db7d953 100644 --- a/EstusShots.Server/Program.cs +++ b/EstusShots.Server/Program.cs @@ -18,6 +18,7 @@ namespace EstusShots.Server public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .ConfigureLogging(builder => { builder.AddConsole(); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } \ No newline at end of file diff --git a/EstusShots.Server/Services/EstusShotsContext.cs b/EstusShots.Server/Services/EstusShotsContext.cs index 7a4b617..d397451 100644 --- a/EstusShots.Server/Services/EstusShotsContext.cs +++ b/EstusShots.Server/Services/EstusShotsContext.cs @@ -7,8 +7,10 @@ namespace EstusShots.Server.Services { public EstusShotsContext(DbContextOptions options) : base(options) { + ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } public DbSet Seasons { get; set; } = default!; + public DbSet Episodes { get; set; } = default!; } } \ No newline at end of file diff --git a/EstusShots.Server/Startup.cs b/EstusShots.Server/Startup.cs index da66e13..483c991 100644 --- a/EstusShots.Server/Startup.cs +++ b/EstusShots.Server/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace EstusShots.Server { diff --git a/EstusShots.Shared/Dto/Episode.cs b/EstusShots.Shared/Dto/Episode.cs new file mode 100644 index 0000000..9709088 --- /dev/null +++ b/EstusShots.Shared/Dto/Episode.cs @@ -0,0 +1,25 @@ +using System; + +namespace EstusShots.Shared.Dto +{ + public class Episode + { + public Guid EpisodeId { get; set; } + + public int Number { get; set; } + + public string Title { get; set; } + + public DateTime DateTime { get; set; } + + public DateTime Start { get; set; } + + public DateTime? End { get; set; } + + public Guid SeasonId { get; set; } + + public Season Season { get; set; } + + public string Displayname => $"S{Season.Number:D2}E{Number:D2}"; + } +} \ No newline at end of file diff --git a/EstusShots.Shared/Models/Season.cs b/EstusShots.Shared/Dto/Season.cs similarity index 91% rename from EstusShots.Shared/Models/Season.cs rename to EstusShots.Shared/Dto/Season.cs index a7dbcc0..9323d14 100644 --- a/EstusShots.Shared/Models/Season.cs +++ b/EstusShots.Shared/Dto/Season.cs @@ -1,6 +1,6 @@ using System; -namespace EstusShots.Shared.Models +namespace EstusShots.Shared.Dto { public class Season { diff --git a/EstusShots.Shared/EstusShots.Shared.csproj b/EstusShots.Shared/EstusShots.Shared.csproj index adf3d2b..67ec3d9 100644 --- a/EstusShots.Shared/EstusShots.Shared.csproj +++ b/EstusShots.Shared/EstusShots.Shared.csproj @@ -2,7 +2,7 @@ netcoreapp3.1 - enable + disable diff --git a/EstusShots.Shared/Models/OperationResult.cs b/EstusShots.Shared/Models/OperationResult.cs new file mode 100644 index 0000000..18a9a77 --- /dev/null +++ b/EstusShots.Shared/Models/OperationResult.cs @@ -0,0 +1,32 @@ +using System; + +namespace EstusShots.Shared.Models +{ + public class OperationResult + { + public bool Success { get; } + public string ShortMessage { get; set; } + public string DetailedMessage { get; set; } + public string StackTrace { get; set; } + + public OperationResult() + { + Success = true; + } + + public OperationResult(bool success, string shortMessage, string detailedMessage = null) + { + Success = success; + ShortMessage = shortMessage; + DetailedMessage = detailedMessage; + } + + public OperationResult(Exception e) + { + Success = false; + ShortMessage = e.Message; + DetailedMessage = e.InnerException?.Message; + StackTrace = e.StackTrace; + } + } +} \ No newline at end of file