From a0c672a1c245eb8e6700defcb9ea62b51e7e8902 Mon Sep 17 00:00:00 2001 From: Linkslumps Date: Sun, 28 Apr 2019 15:07:59 -0300 Subject: [PATCH 01/13] Issue #35 Curate the Live Channels Display on Hompage Changes made to show live channels sorted by view count, also displaying time online. Modified the Feeling Lucky button to display a random live channel. --- .../Data/ICrudRepository.cs | 1 + .../Twitch/ChannelLiveState.cs | 7 ++- .../Twitch/TwitchResult.cs | 26 +++++++++ .../DapperCrudRepository.cs | 30 +++++++++++ .../TwitchStreamService.cs | 53 +++++++++++-------- .../Controllers/IsLiveController.cs | 37 +++++++++++-- .../Pages/Index.cshtml | 9 ++-- .../Pages/Index.cshtml.cs | 19 ++++--- 8 files changed, 144 insertions(+), 38 deletions(-) diff --git a/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs b/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs index 1b23715..9229368 100644 --- a/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs +++ b/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs @@ -11,6 +11,7 @@ public interface ICrudRepository Task> GetAll(); Task> GetAll(string filter, object args); Task> GetAll(string filter, string orderBy, object args); + Task> GetAllChannelInfo(); Task Update(T model) where T : DataEntity; Task Delete(int id) where T : DataEntity; Task Delete(T model) where T : DataEntity; diff --git a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs index e53d874..b459572 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs @@ -1,8 +1,13 @@ -namespace DevChatter.DevStreams.Core.Twitch +using System; + +namespace DevChatter.DevStreams.Core.Twitch { public class ChannelLiveState { public string TwitchId { get; set; } public bool IsLive { get; set; } + public DateTime startedAt { get; set; } + public int viewerCount { get; set; } + } } diff --git a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs index c3d13cd..5a042fc 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs @@ -50,4 +50,30 @@ public class StreamResult public List Data { get; set; } public Pagination Pagination { get; set; } } + public class ChannelResultData + { + public int _total { get; set; } + public List follows { get; set; } + } + + public class ChannelResult + { + public DateTime created_at { get; set; } + public bool notifications { get; set; } + public ChannelFollowResult channel { get; set; } + } + + public class ChannelFollowResult + { + public int _id { get; set; } + public DateTime created_at { get; set; } + public string display_name { get; set; } + public int followers { get; set; } + public string game { get; set; } + public string logo { get; set; } + public DateTime updated_at { get; set; } + public string url { get; set; } + public string video_banner { get; set; } + public int views { get; set; } + } } diff --git a/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs b/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs index a310fb2..a4a627c 100644 --- a/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs +++ b/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs @@ -86,6 +86,36 @@ public async Task> GetAll(string filter, string orderBy, object args) } } + /// + /// Returns all the channel data for a channel object. + /// + /// All Channel Info in a List Object + public async Task> GetAllChannelInfo() + { + string sql = "SELECT * FROM Channels;"; + string sql2 = "SELECT * FROM TwitchChannels;"; + + string combinedSQL = sql + sql2; + + using (IDbConnection connection = new SqlConnection(_dbSettings.DefaultConnection)) + { + var dbQuery = await connection.QueryMultipleAsync(combinedSQL); + List channels = dbQuery.Read().ToList(); + var twitchChannels = dbQuery.Read(); + + foreach (var channel in channels) + { + if (twitchChannels.Where(x => x?.ChannelId == channel?.Id).Any()) + { + channel.Twitch = twitchChannels.Where(x => x?.ChannelId == channel?.Id).First(); + } + } + + return channels; + } + + } + public async Task Update(T model) where T : DataEntity { using (IDbConnection connection = new SqlConnection(_dbSettings.DefaultConnection)) diff --git a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs index 0bfec52..78889eb 100644 --- a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs +++ b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using DevChatter.DevStreams.Core.Model; namespace DevChatter.DevStreams.Infra.Twitch { @@ -19,11 +20,20 @@ public TwitchStreamService(IOptions twitchSettings) _twitchSettings = twitchSettings.Value; } - /// - /// Returns the subset of the channels which are currently live on Twitch. - /// - /// Names of the Channels to check for live status. - /// The names of the subset of channels that are currently live. + + + public async Task IsLive(string twitchId) + { + // TODO: Have this just check cache or do a refresh based on getting *all* data. + + var url = $"{_twitchSettings.BaseApiUrl}/streams?user_id={twitchId}"; + var jsonResult = await Get(url); + + var result = JsonConvert.DeserializeObject(jsonResult); + + return new ChannelLiveState{TwitchId = twitchId, IsLive = result.Data.Any()}; + } + public async Task> GetChannelLiveStates(List twitchIds) { if (!twitchIds.Any()) @@ -37,27 +47,24 @@ public async Task> GetChannelLiveStates(List twit var result = JsonConvert.DeserializeObject(jsonResult); - var liveChannels = result.Data.Where(x => x.Type == "live").ToList(); + var liveChannels = result.Data.ToList(); - return twitchIds - .Select(twitchId => new ChannelLiveState - { - TwitchId = twitchId, - IsLive = liveChannels.Any(x => x.User_id == twitchId) - }) - .ToList(); - } + var returnStat = new List(); - public async Task IsLive(string twitchId) - { - // TODO: Have this just check cache or do a refresh based on getting *all* data. - - var url = $"{_twitchSettings.BaseApiUrl}/streams?user_id={twitchId}"; - var jsonResult = await Get(url); - - var result = JsonConvert.DeserializeObject(jsonResult); + if (twitchIds.Any()) + { + returnStat = twitchIds + .Select(twitchId => new ChannelLiveState + { + TwitchId = twitchId, + IsLive = liveChannels.Any(x => x.User_id == twitchId), + startedAt = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Started_at.ToUniversalTime()).DefaultIfEmpty().First(), + viewerCount = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Viewer_count).DefaultIfEmpty().First() - return new ChannelLiveState{TwitchId = twitchId, IsLive = result.Data.Any()}; + }) + .ToList(); + } + return returnStat; } // TODO: Extract to composed dependency diff --git a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs index 37f47ce..702c57d 100644 --- a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs +++ b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System; namespace DevChatter.DevStreams.Web.Controllers { @@ -26,20 +27,46 @@ public IsLiveController(ICrudRepository crudRepository, /// - /// Get all live streams. + /// Get all live streams info. /// - /// + /// TwitchName, isLive = True, started_At, View Count [HttpGet] public async Task Get() { List channels = await _crudRepository.GetAll(); List twitchIds = channels.Select(x => x.TwitchId).ToList(); - var liveTwitchIds = (await _twitchService.GetChannelLiveStates(twitchIds)) + var liveTwitchData = (await _twitchService.GetChannelLiveStates(twitchIds)); + + var sortedLiveData = liveTwitchData.OrderByDescending(o => o.viewerCount).ToList(); + + var liveTwitchIds = sortedLiveData .Where(x => x.IsLive) .Select(x => x.TwitchId) .ToList(); - var liveChannelNames = channels.Where(c => liveTwitchIds.Contains(c.TwitchId)).Select(c => c.TwitchName); - return Ok(liveChannelNames); + + var liveChannelSorted = sortedLiveData + .OrderByDescending(x => x.viewerCount) + .Select(x => channels.Where(y => y.TwitchId == x.TwitchId).Where(v => x.IsLive).Select(z => z.TwitchName)) + .Where(x => x.Any()) + .ToList(); + + var timeDifference = sortedLiveData + .Where(x => liveTwitchIds.Contains(x.TwitchId)) + .Select(x => (DateTime.UtcNow - x.startedAt.ToUniversalTime())); + + var viewerCount = sortedLiveData + .Where(x => liveTwitchIds.Contains(x.TwitchId)) + .Select(x => x.viewerCount) + .ToList(); + + var responseObject = new + { + Channel = liveChannelSorted, + viewCount = viewerCount, + timeOnline = timeDifference + }; + + return Ok(responseObject); } [HttpGet, Route("{twitchId}")] diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml index 2f3c04e..9ef0b5a 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml @@ -8,9 +8,12 @@

Live Now

@*TODO: Show channels that are live right now.*@
    -
  • - {{liveChannel}} -
  • +
  • + {{liveChannel[0]}} + views: {{ liveChannels.viewCount[index] }} + Online: {{ liveChannels.timeOnline[index] }} + +
diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs index 8c6adc3..45b9f3c 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs @@ -51,19 +51,26 @@ public async Task OnGetAsync() public async Task OnGetLuckyAsync() { - List channels = await _repo.GetAll(); + List channels = await _repo.GetAllChannelInfo(); + List twitchIds = channels.Select(x => x?.Twitch?.TwitchId) .Where(x => !string.IsNullOrWhiteSpace(x)) .ToList(); - var liveChannelIds = (await _twitchService.GetChannelLiveStates(twitchIds)) + + var liveTwitchId = (await _twitchService.GetChannelLiveStates(twitchIds)) .Where(x => x.IsLive) .Select(x => x.TwitchId) - .ToList(); - var result = new Result(); + .ToList().PickOneRandomElement(); + + var liveChannel = channels + .Where(x => x?.Twitch?.TwitchId == liveTwitchId) + .Select(x => x?.Name); + + var result = new Result(); ; - if (liveChannelIds.Any()) + if (liveChannel.Any()) { - result.ChannelName = liveChannelIds.PickOneRandomElement(); + result.ChannelName = liveChannel.First(); } else { From f4e1bdf63f0c3b4966bfdf4edc8861040ecb6b8a Mon Sep 17 00:00:00 2001 From: Linkslumps Date: Tue, 30 Apr 2019 16:42:19 -0300 Subject: [PATCH 02/13] Issue DevChatter#35 Curate the Live Channels Display on Hompage Requested updates to the pull request. Moved the getall method to the channelaggregateservices where it should be. Also changed the properties to Camel Casing --- .../Data/IChannelAggregateService.cs | 1 + .../Data/ICrudRepository.cs | 1 - .../Twitch/ChannelLiveState.cs | 4 +-- .../DapperCrudRepository.cs | 29 ------------------- .../Services/ChannelAggregateService.cs | 28 ++++++++++++++++++ .../TwitchStreamService.cs | 4 +-- .../Controllers/IsLiveController.cs | 15 +++++----- .../Pages/Index.cshtml | 2 +- .../Pages/Index.cshtml.cs | 4 ++- 9 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs b/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs index 7f008a2..4674a60 100644 --- a/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs +++ b/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs @@ -9,6 +9,7 @@ public interface IChannelAggregateService List GetAll(); List GetAll(string userId); Channel GetAggregate(int id); + Task> GetAllAggregate(); Task Create(Channel model, string userId); Task Update(Channel model); Task Delete(int id); diff --git a/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs b/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs index 9229368..1b23715 100644 --- a/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs +++ b/src/DevChatter.DevStreams.Core/Data/ICrudRepository.cs @@ -11,7 +11,6 @@ public interface ICrudRepository Task> GetAll(); Task> GetAll(string filter, object args); Task> GetAll(string filter, string orderBy, object args); - Task> GetAllChannelInfo(); Task Update(T model) where T : DataEntity; Task Delete(int id) where T : DataEntity; Task Delete(T model) where T : DataEntity; diff --git a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs index b459572..0bc69f4 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs @@ -6,8 +6,8 @@ public class ChannelLiveState { public string TwitchId { get; set; } public bool IsLive { get; set; } - public DateTime startedAt { get; set; } - public int viewerCount { get; set; } + public DateTime StartedAt { get; set; } + public int ViewerCount { get; set; } } } diff --git a/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs b/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs index a4a627c..de3e38d 100644 --- a/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs +++ b/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs @@ -86,35 +86,6 @@ public async Task> GetAll(string filter, string orderBy, object args) } } - /// - /// Returns all the channel data for a channel object. - /// - /// All Channel Info in a List Object - public async Task> GetAllChannelInfo() - { - string sql = "SELECT * FROM Channels;"; - string sql2 = "SELECT * FROM TwitchChannels;"; - - string combinedSQL = sql + sql2; - - using (IDbConnection connection = new SqlConnection(_dbSettings.DefaultConnection)) - { - var dbQuery = await connection.QueryMultipleAsync(combinedSQL); - List channels = dbQuery.Read().ToList(); - var twitchChannels = dbQuery.Read(); - - foreach (var channel in channels) - { - if (twitchChannels.Where(x => x?.ChannelId == channel?.Id).Any()) - { - channel.Twitch = twitchChannels.Where(x => x?.ChannelId == channel?.Id).First(); - } - } - - return channels; - } - - } public async Task Update(T model) where T : DataEntity { diff --git a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs index 9e1b2bb..a0eca69 100644 --- a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs +++ b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs @@ -92,6 +92,34 @@ public Channel GetAggregate(int id) } } + /// + /// Returns all the channel data for a channel object. + /// + /// All Channel Info in a List Object + public async Task> GetAllAggregate() + { + string sql = @"SELECT * FROM Channels; + SELECT * FROM TwitchChannels;"; + + using (IDbConnection connection = new SqlConnection(_dbSettings.DefaultConnection)) + { + var dbQuery = await connection.QueryMultipleAsync(sql); + List channels = dbQuery.Read().ToList(); + List twitchChannels = dbQuery.Read().ToList(); + + foreach (var channel in channels) + { + if (twitchChannels.Where(x => x?.ChannelId == channel?.Id).Any()) + { + channel.Twitch = twitchChannels.Where(x => x?.ChannelId == channel?.Id).First(); + } + } + + return channels; + } + + } + public async Task Create(Channel model, string userId) { using (IDbConnection connection = new SqlConnection(_dbSettings.DefaultConnection)) diff --git a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs index 78889eb..3ed1385 100644 --- a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs +++ b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs @@ -58,8 +58,8 @@ public async Task> GetChannelLiveStates(List twit { TwitchId = twitchId, IsLive = liveChannels.Any(x => x.User_id == twitchId), - startedAt = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Started_at.ToUniversalTime()).DefaultIfEmpty().First(), - viewerCount = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Viewer_count).DefaultIfEmpty().First() + StartedAt = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Started_at.ToUniversalTime()).DefaultIfEmpty().First(), + ViewerCount = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Viewer_count).DefaultIfEmpty().First() }) .ToList(); diff --git a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs index 702c57d..228ce94 100644 --- a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs +++ b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs @@ -33,11 +33,12 @@ public IsLiveController(ICrudRepository crudRepository, [HttpGet] public async Task Get() { - List channels = await _crudRepository.GetAll(); - List twitchIds = channels.Select(x => x.TwitchId).ToList(); + List channels = await _channelAggregateService.GetAllAggregate(); + List twitchIds = channels.Select(x => x?.Twitch?.TwitchId).ToList(); + twitchIds.RemoveAll(string.IsNullOrWhiteSpace); var liveTwitchData = (await _twitchService.GetChannelLiveStates(twitchIds)); - var sortedLiveData = liveTwitchData.OrderByDescending(o => o.viewerCount).ToList(); + var sortedLiveData = liveTwitchData.OrderByDescending(o => o.ViewerCount).ToList(); var liveTwitchIds = sortedLiveData .Where(x => x.IsLive) @@ -45,18 +46,18 @@ public async Task Get() .ToList(); var liveChannelSorted = sortedLiveData - .OrderByDescending(x => x.viewerCount) - .Select(x => channels.Where(y => y.TwitchId == x.TwitchId).Where(v => x.IsLive).Select(z => z.TwitchName)) + .OrderByDescending(x => x.ViewerCount) + .Select(x => channels.Where(y => y?.Twitch?.TwitchId == x?.TwitchId).Where(v => x.IsLive).Select(z => z?.Twitch?.TwitchName)) .Where(x => x.Any()) .ToList(); var timeDifference = sortedLiveData .Where(x => liveTwitchIds.Contains(x.TwitchId)) - .Select(x => (DateTime.UtcNow - x.startedAt.ToUniversalTime())); + .Select(x => (DateTime.UtcNow - x?.StartedAt.ToUniversalTime())); var viewerCount = sortedLiveData .Where(x => liveTwitchIds.Contains(x.TwitchId)) - .Select(x => x.viewerCount) + .Select(x => x.ViewerCount) .ToList(); var responseObject = new diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml index 9ef0b5a..7d4ed59 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml @@ -8,7 +8,7 @@

Live Now

@*TODO: Show channels that are live right now.*@
    -
  • +
  • {{liveChannel[0]}} views: {{ liveChannels.viewCount[index] }} Online: {{ liveChannels.timeOnline[index] }} diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs index 45b9f3c..fa86ee3 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs @@ -51,7 +51,9 @@ public async Task OnGetAsync() public async Task OnGetLuckyAsync() { - List channels = await _repo.GetAllChannelInfo(); + List channels = await _repo.GetAll(); + + List twitchIds = channels.Select(x => x?.Twitch?.TwitchId) .Where(x => !string.IsNullOrWhiteSpace(x)) From fc983d1bd63fc023c0da2ce7b8b85e98d75d941b Mon Sep 17 00:00:00 2001 From: Linkslumps Date: Tue, 30 Apr 2019 16:47:49 -0300 Subject: [PATCH 03/13] Issue #35 Curate the Live Channels Display on Homepage Updated a missed camal case. --- .../Twitch/TwitchResult.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs index 5a042fc..0d1e532 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs @@ -52,28 +52,28 @@ public class StreamResult } public class ChannelResultData { - public int _total { get; set; } - public List follows { get; set; } + public int Total { get; set; } + public List Follows { get; set; } } public class ChannelResult { - public DateTime created_at { get; set; } - public bool notifications { get; set; } - public ChannelFollowResult channel { get; set; } + public DateTime CreatedAt { get; set; } + public bool Notifications { get; set; } + public ChannelFollowResult Channel { get; set; } } public class ChannelFollowResult { - public int _id { get; set; } - public DateTime created_at { get; set; } - public string display_name { get; set; } - public int followers { get; set; } - public string game { get; set; } - public string logo { get; set; } - public DateTime updated_at { get; set; } - public string url { get; set; } - public string video_banner { get; set; } - public int views { get; set; } + public int Id { get; set; } + public DateTime CreatedAt { get; set; } + public string DisplayName { get; set; } + public int Followers { get; set; } + public string Game { get; set; } + public string Logo { get; set; } + public DateTime UpdatedAt { get; set; } + public string Url { get; set; } + public string VideoBanner { get; set; } + public int Views { get; set; } } } From 2f45161ebdd7612a33a34c8e710868d28b4b77af Mon Sep 17 00:00:00 2001 From: Linkslumps Date: Tue, 30 Apr 2019 17:07:17 -0300 Subject: [PATCH 04/13] Updated Feeling Lucky Button so that it works Updated feeling lucky button --- src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs index fa86ee3..96e80cb 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs @@ -15,11 +15,13 @@ public class IndexModel : PageModel { private readonly ICrudRepository _repo; private readonly ITwitchStreamService _twitchService; + private readonly IChannelAggregateService _channelAggregateService; - public IndexModel(ICrudRepository repo, ITwitchStreamService twitchService) + public IndexModel(ICrudRepository repo, ITwitchStreamService twitchService, IChannelAggregateService channelAggregateService) { _repo = repo; _twitchService = twitchService; + _channelAggregateService = channelAggregateService; } public List NewlyAddedChannels { get; set; } @@ -51,12 +53,11 @@ public async Task OnGetAsync() public async Task OnGetLuckyAsync() { - List channels = await _repo.GetAll(); - + List channels = await _channelAggregateService.GetAllAggregate(); - - List twitchIds = channels.Select(x => x?.Twitch?.TwitchId) - .Where(x => !string.IsNullOrWhiteSpace(x)) + List twitchIds = channels + .Where(x => !(string.IsNullOrEmpty(x.Twitch?.TwitchId))) + .Select(x => x?.Twitch?.TwitchId) .ToList(); var liveTwitchId = (await _twitchService.GetChannelLiveStates(twitchIds)) From acfe9de930ac6588ca6225cf1128c378538e13e5 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 14:33:18 -0400 Subject: [PATCH 05/13] Rearrange file to make diff simpler --- .../TwitchStreamService.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs index 3ed1385..576551b 100644 --- a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs +++ b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using DevChatter.DevStreams.Core.Model; namespace DevChatter.DevStreams.Infra.Twitch { @@ -20,20 +19,11 @@ public TwitchStreamService(IOptions twitchSettings) _twitchSettings = twitchSettings.Value; } - - - public async Task IsLive(string twitchId) - { - // TODO: Have this just check cache or do a refresh based on getting *all* data. - - var url = $"{_twitchSettings.BaseApiUrl}/streams?user_id={twitchId}"; - var jsonResult = await Get(url); - - var result = JsonConvert.DeserializeObject(jsonResult); - - return new ChannelLiveState{TwitchId = twitchId, IsLive = result.Data.Any()}; - } - + /// + /// Returns the subset of the channels which are currently live on Twitch. + /// + /// Names of the Channels to check for live status. + /// The names of the subset of channels that are currently live. public async Task> GetChannelLiveStates(List twitchIds) { if (!twitchIds.Any()) @@ -67,6 +57,18 @@ public async Task> GetChannelLiveStates(List twit return returnStat; } + public async Task IsLive(string twitchId) + { + // TODO: Have this just check cache or do a refresh based on getting *all* data. + + var url = $"{_twitchSettings.BaseApiUrl}/streams?user_id={twitchId}"; + var jsonResult = await Get(url); + + var result = JsonConvert.DeserializeObject(jsonResult); + + return new ChannelLiveState { TwitchId = twitchId, IsLive = result.Data.Any() }; + } + // TODO: Extract to composed dependency private async Task Get(string url) { From 15f04305ffb2c6132f613e3381607c7fd19a66f3 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 14:37:49 -0400 Subject: [PATCH 06/13] Remove redundant code --- .../TwitchStreamService.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs index 576551b..a4c5598 100644 --- a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs +++ b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs @@ -39,21 +39,16 @@ public async Task> GetChannelLiveStates(List twit var liveChannels = result.Data.ToList(); - var returnStat = new List(); + List returnStat = twitchIds + .Select(twitchId => new ChannelLiveState + { + TwitchId = twitchId, + IsLive = liveChannels.Any(x => x.User_id == twitchId), + StartedAt = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Started_at.ToUniversalTime()).DefaultIfEmpty().First(), + ViewerCount = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Viewer_count).DefaultIfEmpty().First() - if (twitchIds.Any()) - { - returnStat = twitchIds - .Select(twitchId => new ChannelLiveState - { - TwitchId = twitchId, - IsLive = liveChannels.Any(x => x.User_id == twitchId), - StartedAt = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Started_at.ToUniversalTime()).DefaultIfEmpty().First(), - ViewerCount = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Viewer_count).DefaultIfEmpty().First() - - }) - .ToList(); - } + }) + .ToList(); return returnStat; } From 55fee8da449d6b84d46972de5b3c706ce81f40b3 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 14:41:04 -0400 Subject: [PATCH 07/13] non-functional code clean-up --- src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs | 1 - src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs index 0bc69f4..0105f39 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs @@ -8,6 +8,5 @@ public class ChannelLiveState public bool IsLive { get; set; } public DateTime StartedAt { get; set; } public int ViewerCount { get; set; } - } } diff --git a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs index 228ce94..4e7b9dc 100644 --- a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs +++ b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs @@ -2,10 +2,10 @@ using DevChatter.DevStreams.Core.Model; using DevChatter.DevStreams.Core.Twitch; using Microsoft.AspNetCore.Mvc; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using System; namespace DevChatter.DevStreams.Web.Controllers { From 3b71c4d69cbd69ce562f9550bb8a21eec2aaec54 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 14:42:06 -0400 Subject: [PATCH 08/13] Rename GetAllAggregate to GetAllAggregates --- src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs | 2 +- .../Services/ChannelAggregateService.cs | 2 +- src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs | 2 +- src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs b/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs index 4674a60..3d80bc6 100644 --- a/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs +++ b/src/DevChatter.DevStreams.Core/Data/IChannelAggregateService.cs @@ -9,7 +9,7 @@ public interface IChannelAggregateService List GetAll(); List GetAll(string userId); Channel GetAggregate(int id); - Task> GetAllAggregate(); + Task> GetAllAggregates(); Task Create(Channel model, string userId); Task Update(Channel model); Task Delete(int id); diff --git a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs index a0eca69..794a48d 100644 --- a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs +++ b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs @@ -96,7 +96,7 @@ public Channel GetAggregate(int id) /// Returns all the channel data for a channel object. /// /// All Channel Info in a List Object - public async Task> GetAllAggregate() + public async Task> GetAllAggregates() { string sql = @"SELECT * FROM Channels; SELECT * FROM TwitchChannels;"; diff --git a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs index 4e7b9dc..190cfee 100644 --- a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs +++ b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs @@ -33,7 +33,7 @@ public IsLiveController(ICrudRepository crudRepository, [HttpGet] public async Task Get() { - List channels = await _channelAggregateService.GetAllAggregate(); + List channels = await _channelAggregateService.GetAllAggregates(); List twitchIds = channels.Select(x => x?.Twitch?.TwitchId).ToList(); twitchIds.RemoveAll(string.IsNullOrWhiteSpace); var liveTwitchData = (await _twitchService.GetChannelLiveStates(twitchIds)); diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs index 96e80cb..f70bb18 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml.cs @@ -53,7 +53,7 @@ public async Task OnGetAsync() public async Task OnGetLuckyAsync() { - List channels = await _channelAggregateService.GetAllAggregate(); + List channels = await _channelAggregateService.GetAllAggregates(); List twitchIds = channels .Where(x => !(string.IsNullOrEmpty(x.Twitch?.TwitchId))) From 5c6e469d5d4ee357c70c2fa7d49bd0c209dd1c2a Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 14:43:52 -0400 Subject: [PATCH 09/13] Remove unused TwitchAPI Result classes --- .../Twitch/TwitchResult.cs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs index 0d1e532..c3d13cd 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs @@ -50,30 +50,4 @@ public class StreamResult public List Data { get; set; } public Pagination Pagination { get; set; } } - public class ChannelResultData - { - public int Total { get; set; } - public List Follows { get; set; } - } - - public class ChannelResult - { - public DateTime CreatedAt { get; set; } - public bool Notifications { get; set; } - public ChannelFollowResult Channel { get; set; } - } - - public class ChannelFollowResult - { - public int Id { get; set; } - public DateTime CreatedAt { get; set; } - public string DisplayName { get; set; } - public int Followers { get; set; } - public string Game { get; set; } - public string Logo { get; set; } - public DateTime UpdatedAt { get; set; } - public string Url { get; set; } - public string VideoBanner { get; set; } - public int Views { get; set; } - } } From 1b4d01b9b1f5bb0e34f897778ae6114a626430f6 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 14:47:39 -0400 Subject: [PATCH 10/13] Simplify the TwitchChannel selection in GetAllAggregates --- .../DapperCrudRepository.cs | 1 - .../Services/ChannelAggregateService.cs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs b/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs index de3e38d..a310fb2 100644 --- a/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs +++ b/src/DevChatter.DevStreams.Infra.Dapper/DapperCrudRepository.cs @@ -86,7 +86,6 @@ public async Task> GetAll(string filter, string orderBy, object args) } } - public async Task Update(T model) where T : DataEntity { using (IDbConnection connection = new SqlConnection(_dbSettings.DefaultConnection)) diff --git a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs index 794a48d..40d6898 100644 --- a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs +++ b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs @@ -109,10 +109,7 @@ public async Task> GetAllAggregates() foreach (var channel in channels) { - if (twitchChannels.Where(x => x?.ChannelId == channel?.Id).Any()) - { - channel.Twitch = twitchChannels.Where(x => x?.ChannelId == channel?.Id).First(); - } + channel.Twitch = twitchChannels.SingleOrDefault(x => x.ChannelId == channel.Id); } return channels; From 3ee1bc0d5e6937fd440d601b0a5519ef78e9ce19 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 14:53:48 -0400 Subject: [PATCH 11/13] Clean up spacing --- .../Services/ChannelAggregateService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs index 40d6898..1ee1a65 100644 --- a/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs +++ b/src/DevChatter.DevStreams.Infra.Dapper/Services/ChannelAggregateService.cs @@ -100,7 +100,7 @@ public async Task> GetAllAggregates() { string sql = @"SELECT * FROM Channels; SELECT * FROM TwitchChannels;"; - + using (IDbConnection connection = new SqlConnection(_dbSettings.DefaultConnection)) { var dbQuery = await connection.QueryMultipleAsync(sql); @@ -114,7 +114,6 @@ public async Task> GetAllAggregates() return channels; } - } public async Task Create(Channel model, string userId) From 0183474172d51d605d331446a00f64a7ecff9b59 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Sat, 4 May 2019 16:12:13 -0400 Subject: [PATCH 12/13] WIP - Refactoring the objects, linq, and time handling --- .../Twitch/ChannelLiveState.cs | 5 +- .../Twitch/TwitchResult.cs | 3 +- .../Twitch/TwitchResultExtensions.cs | 25 ++++++- .../DevChatter.DevStreams.Infra.Twitch.csproj | 1 + .../TwitchStreamService.cs | 27 +++----- ...hannelLiveStatesFromStreamResultsShould.cs | 66 +++++++++++++++++++ .../Controllers/IsLiveController.cs | 41 +++--------- .../LiveChannels/LiveChannelViewModel.cs | 14 ++++ .../LiveChannelsMappingExtensions.cs | 21 ++++++ .../Pages/Index.cshtml | 13 ++-- 10 files changed, 157 insertions(+), 59 deletions(-) create mode 100644 src/DevChatter.DevStreams.UnitTests/Core/Twitch/TwitchResultExtensions/CreateChannelLiveStatesFromStreamResultsShould.cs create mode 100644 src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelViewModel.cs create mode 100644 src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelsMappingExtensions.cs diff --git a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs index 0105f39..3ad072c 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/ChannelLiveState.cs @@ -1,4 +1,4 @@ -using System; +using NodaTime; namespace DevChatter.DevStreams.Core.Twitch { @@ -6,7 +6,8 @@ public class ChannelLiveState { public string TwitchId { get; set; } public bool IsLive { get; set; } - public DateTime StartedAt { get; set; } + public Instant StartedAt { get; set; } + public Duration TimeOnline => SystemClock.Instance.GetCurrentInstant() - StartedAt; public int ViewerCount { get; set; } } } diff --git a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs index c3d13cd..81174ce 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/TwitchResult.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NodaTime; namespace DevChatter.DevStreams.Core.Twitch { @@ -34,7 +35,7 @@ public class StreamResultData public string Type { get; set; } public string Title { get; set; } public int Viewer_count { get; set; } - public DateTime Started_at { get; set; } + public Instant Started_at { get; set; } public string Language { get; set; } public string Thumbnail_url { get; set; } public List Tag_ids { get; set; } diff --git a/src/DevChatter.DevStreams.Core/Twitch/TwitchResultExtensions.cs b/src/DevChatter.DevStreams.Core/Twitch/TwitchResultExtensions.cs index 5d1ed1c..0e30506 100644 --- a/src/DevChatter.DevStreams.Core/Twitch/TwitchResultExtensions.cs +++ b/src/DevChatter.DevStreams.Core/Twitch/TwitchResultExtensions.cs @@ -1,4 +1,8 @@ -using DevChatter.DevStreams.Core.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using DevChatter.DevStreams.Core.Model; +using NodaTime; namespace DevChatter.DevStreams.Core.Twitch { @@ -15,5 +19,24 @@ public static TwitchChannel ToTwitchChannelModel(this UserResultData src) IsPartner = src.Broadcaster_type == TwitchConstants.PARTNER, }; } + + public static List CreateChannelLiveStatesFromStreamResults( + this StreamResult result, List twitchIds) + { + List returnStat = twitchIds + .Select(twitchId => + { + StreamResultData thisResult = result.Data.FirstOrDefault(x => x.User_id == twitchId); + return new ChannelLiveState + { + TwitchId = twitchId, + IsLive = thisResult != null, + StartedAt = thisResult?.Started_at ?? Instant.MinValue, + ViewerCount = thisResult?.Viewer_count ?? 0, + }; + }) + .ToList(); + return returnStat; + } } } \ No newline at end of file diff --git a/src/DevChatter.DevStreams.Infra.Twitch/DevChatter.DevStreams.Infra.Twitch.csproj b/src/DevChatter.DevStreams.Infra.Twitch/DevChatter.DevStreams.Infra.Twitch.csproj index c7316c1..6a3f000 100644 --- a/src/DevChatter.DevStreams.Infra.Twitch/DevChatter.DevStreams.Infra.Twitch.csproj +++ b/src/DevChatter.DevStreams.Infra.Twitch/DevChatter.DevStreams.Infra.Twitch.csproj @@ -6,6 +6,7 @@ + diff --git a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs index a4c5598..596700b 100644 --- a/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs +++ b/src/DevChatter.DevStreams.Infra.Twitch/TwitchStreamService.cs @@ -2,11 +2,13 @@ using DevChatter.DevStreams.Core.Twitch; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using NodaTime; +using NodaTime.Serialization.JsonNet; + namespace DevChatter.DevStreams.Infra.Twitch { @@ -30,26 +32,17 @@ public async Task> GetChannelLiveStates(List twit { return new List(); // TODO: Replace with Guard Clause } - var channelIdsQueryFormat = String.Join("&user_id=", twitchIds); + var channelIdsQueryFormat = string.Join("&user_id=", twitchIds); var url = $"{_twitchSettings.BaseApiUrl}/streams?user_id={channelIdsQueryFormat}"; - var jsonResult = await Get(url); - - var result = JsonConvert.DeserializeObject(jsonResult); - - var liveChannels = result.Data.ToList(); + string jsonResult = await Get(url); - List returnStat = twitchIds - .Select(twitchId => new ChannelLiveState - { - TwitchId = twitchId, - IsLive = liveChannels.Any(x => x.User_id == twitchId), - StartedAt = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Started_at.ToUniversalTime()).DefaultIfEmpty().First(), - ViewerCount = result.Data.Where(x => x.User_id == twitchId).Select(x => x.Viewer_count).DefaultIfEmpty().First() + var serializerSettings = new JsonSerializerSettings(); + serializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + + var result = JsonConvert.DeserializeObject(jsonResult, serializerSettings); - }) - .ToList(); - return returnStat; + return result.CreateChannelLiveStatesFromStreamResults(twitchIds); } public async Task IsLive(string twitchId) diff --git a/src/DevChatter.DevStreams.UnitTests/Core/Twitch/TwitchResultExtensions/CreateChannelLiveStatesFromStreamResultsShould.cs b/src/DevChatter.DevStreams.UnitTests/Core/Twitch/TwitchResultExtensions/CreateChannelLiveStatesFromStreamResultsShould.cs new file mode 100644 index 0000000..8f98c48 --- /dev/null +++ b/src/DevChatter.DevStreams.UnitTests/Core/Twitch/TwitchResultExtensions/CreateChannelLiveStatesFromStreamResultsShould.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DevChatter.DevStreams.Core.Twitch; +using FluentAssertions; +using NodaTime; +using NodaTime.Text; +using Xunit; + +namespace DevChatter.DevStreams.UnitTests.Core.Twitch.TwitchResultExtensions +{ + public class CreateChannelLiveStatesFromStreamResultsShould + { + [Fact] + public void ReturnEmpty_GivenNoResults() + { + var streamResult = new StreamResult { Data = new List() }; + var twitchIds = new List(); + + var liveStates = streamResult.CreateChannelLiveStatesFromStreamResults(twitchIds); + + liveStates.Should().BeEmpty(); + } + + [Fact] + public void ReturnNonLiveResult_GivenNotLiveChannel() + { + const string twitchId = "123Link"; + var streamResult = new StreamResult {Data = new List()}; + var twitchIds = new List { twitchId }; + + var liveStates = streamResult.CreateChannelLiveStatesFromStreamResults(twitchIds); + + liveStates.Single().IsLive.Should().BeFalse(); + liveStates.Single().TwitchId.Should().Be(twitchId); + liveStates.Single().StartedAt.Should().Be(Instant.MinValue); + liveStates.Single().ViewerCount.Should().Be(0); + } + + [Fact] + public void ReturnLiveResultWithData_GivenLiveChannel() + { + const string twitchId = "DevChatter42"; + var startedAt = InstantPattern.General.Parse("2019-05-04T17:04:19Z").Value; + const int viewerCount = 77; + var streamResult = new StreamResult {Data = new List + { + new StreamResultData + { + Started_at = startedAt, + Viewer_count = viewerCount, + User_id = twitchId, + } + }}; + var twitchIds = new List { twitchId }; + + var liveState = streamResult.CreateChannelLiveStatesFromStreamResults(twitchIds) + .Single(); + + liveState.IsLive.Should().BeTrue(); + liveState.TwitchId.Should().Be(twitchId); + liveState.StartedAt.Should().Be(startedAt); + liveState.ViewerCount.Should().Be(viewerCount); + } + } +} \ No newline at end of file diff --git a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs index 190cfee..e8d35f7 100644 --- a/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs +++ b/src/DevChatter.DevStreams.Web/Controllers/IsLiveController.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using DevChatter.DevStreams.Web.Data.ViewModel.LiveChannels; +using NodaTime; namespace DevChatter.DevStreams.Web.Controllers { @@ -27,47 +29,24 @@ public IsLiveController(ICrudRepository crudRepository, /// - /// Get all live streams info. + /// Get info about all currently live channels. /// - /// TwitchName, isLive = True, started_At, View Count + /// [HttpGet] public async Task Get() { - List channels = await _channelAggregateService.GetAllAggregates(); + List channels = (await _channelAggregateService.GetAllAggregates()) + .Where(c => c.Twitch != null).ToList(); List twitchIds = channels.Select(x => x?.Twitch?.TwitchId).ToList(); twitchIds.RemoveAll(string.IsNullOrWhiteSpace); - var liveTwitchData = (await _twitchService.GetChannelLiveStates(twitchIds)); - - var sortedLiveData = liveTwitchData.OrderByDescending(o => o.ViewerCount).ToList(); - - var liveTwitchIds = sortedLiveData + List channelLiveStates = (await _twitchService.GetChannelLiveStates(twitchIds)); + var liveTwitchData = channelLiveStates .Where(x => x.IsLive) - .Select(x => x.TwitchId) - .ToList(); - - var liveChannelSorted = sortedLiveData .OrderByDescending(x => x.ViewerCount) - .Select(x => channels.Where(y => y?.Twitch?.TwitchId == x?.TwitchId).Where(v => x.IsLive).Select(z => z?.Twitch?.TwitchName)) - .Where(x => x.Any()) + .Select(x => x.ToViewModel(channels.Single(c => c.Twitch.TwitchId == x.TwitchId))) .ToList(); - var timeDifference = sortedLiveData - .Where(x => liveTwitchIds.Contains(x.TwitchId)) - .Select(x => (DateTime.UtcNow - x?.StartedAt.ToUniversalTime())); - - var viewerCount = sortedLiveData - .Where(x => liveTwitchIds.Contains(x.TwitchId)) - .Select(x => x.ViewerCount) - .ToList(); - - var responseObject = new - { - Channel = liveChannelSorted, - viewCount = viewerCount, - timeOnline = timeDifference - }; - - return Ok(responseObject); + return Ok(liveTwitchData); } [HttpGet, Route("{twitchId}")] diff --git a/src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelViewModel.cs b/src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelViewModel.cs new file mode 100644 index 0000000..dae5693 --- /dev/null +++ b/src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelViewModel.cs @@ -0,0 +1,14 @@ +using NodaTime; + +namespace DevChatter.DevStreams.Web.Data.ViewModel.LiveChannels +{ + public class LiveChannelViewModel + { + public string ChannelName { get; set; } + public string Uri { get; set; } + public Instant StartedAt { get; set; } + public Duration TimeOnline => SystemClock.Instance.GetCurrentInstant() - StartedAt; + public int ViewerCount { get; set; } + + } +} \ No newline at end of file diff --git a/src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelsMappingExtensions.cs b/src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelsMappingExtensions.cs new file mode 100644 index 0000000..4474996 --- /dev/null +++ b/src/DevChatter.DevStreams.Web/Data/ViewModel/LiveChannels/LiveChannelsMappingExtensions.cs @@ -0,0 +1,21 @@ +using DevChatter.DevStreams.Core.Model; +using DevChatter.DevStreams.Core.Twitch; + +namespace DevChatter.DevStreams.Web.Data.ViewModel.LiveChannels +{ + public static class LiveChannelsMappingExtensions + { + public static LiveChannelViewModel ToViewModel(this ChannelLiveState src, Channel channel) + { + var viewModel = new LiveChannelViewModel + { + ChannelName = channel.Name, + Uri = channel.Uri, + StartedAt = src.StartedAt, + ViewerCount = src.ViewerCount, + }; + + return viewModel; + } + } +} \ No newline at end of file diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml index 7d4ed59..fb85214 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml @@ -6,14 +6,13 @@ }
    -

    Live Now

    @*TODO: Show channels that are live right now.*@ +

    Live Now

      -
    • - {{liveChannel[0]}} - views: {{ liveChannels.viewCount[index] }} - Online: {{ liveChannels.timeOnline[index] }} - -
    • +
    • + {{ liveChannel.channelName }} + Viewers: {{ liveChannel.viewerCount }} + Online: {{ liveChannel.timeOnline }} +
    From b1ae5a08cdc8b659035e85f8940edbe07be39215 Mon Sep 17 00:00:00 2001 From: Brendan Enrick <2243498+benrick@users.noreply.github.com> Date: Tue, 14 May 2019 15:48:34 -0400 Subject: [PATCH 13/13] Card Styling for Live Channels --- .../Pages/Index.cshtml | 26 +++++++++++---- .../wwwroot/css/site.css | 32 ++++++++++++++++++- .../wwwroot/css/site.min.css | 2 +- .../wwwroot/js/vue/home-page.js | 9 ++++++ 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml index fb85214..e217374 100644 --- a/src/DevChatter.DevStreams.Web/Pages/Index.cshtml +++ b/src/DevChatter.DevStreams.Web/Pages/Index.cshtml @@ -7,13 +7,25 @@

    Live Now

    -
      -
    • - {{ liveChannel.channelName }} - Viewers: {{ liveChannel.viewerCount }} - Online: {{ liveChannel.timeOnline }} -
    • -
    +
    +
    +
    + ProfileImage +
    +
    +

    {{ liveChannel.channelName }}

    +
      +
    • + Viewers: {{ liveChannel.viewerCount }} +
    • +
    • + Online: {{ humanizeTime(liveChannel.timeOnline) }} +
    • +
    + Watch Now +
    +
    +

    DevChatter

    diff --git a/src/DevChatter.DevStreams.Web/wwwroot/css/site.css b/src/DevChatter.DevStreams.Web/wwwroot/css/site.css index 43eec6b..4637b2c 100644 --- a/src/DevChatter.DevStreams.Web/wwwroot/css/site.css +++ b/src/DevChatter.DevStreams.Web/wwwroot/css/site.css @@ -23,4 +23,34 @@ body { .theme-select { margin-top: 6px; -} \ No newline at end of file +} + +/* Channel Card Styles */ + +.channel-card { + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); + transition: 0.3s; + width: 80%; + margin-bottom: 20px; +} + +.channel-card:hover { + box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); +} + +div.channel-card-img { + display: flex; + align-items: center; + justify-content: center; +} +.channel-card-img img { + margin: 12px; + width: 100%; +} + +.channel-card .container { + padding: 2px 16px 16px 16px; + width: 100% +} + +/* End Channel Card Styles */ diff --git a/src/DevChatter.DevStreams.Web/wwwroot/css/site.min.css b/src/DevChatter.DevStreams.Web/wwwroot/css/site.min.css index 3bae478..f40b132 100644 --- a/src/DevChatter.DevStreams.Web/wwwroot/css/site.min.css +++ b/src/DevChatter.DevStreams.Web/wwwroot/css/site.min.css @@ -1 +1 @@ -body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}#qrCode{margin:15px}[v-cloak]{display:none}.theme-select{margin-top:6px} \ No newline at end of file +body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}#qrCode{margin:15px}[v-cloak]{display:none}.theme-select{margin-top:6px}.channel-card{box-shadow:0 4px 8px 0 rgba(0,0,0,.2);transition:.3s;width:80%}.channel-card:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,.2)}div.channel-card-img{display:flex;align-items:center;justify-content:center}.channel-card-img img{margin:12px;width:100%}.channel-card .container{padding:2px 16px 16px 16px;width:100%} \ No newline at end of file diff --git a/src/DevChatter.DevStreams.Web/wwwroot/js/vue/home-page.js b/src/DevChatter.DevStreams.Web/wwwroot/js/vue/home-page.js index eee74c6..b9d6a28 100644 --- a/src/DevChatter.DevStreams.Web/wwwroot/js/vue/home-page.js +++ b/src/DevChatter.DevStreams.Web/wwwroot/js/vue/home-page.js @@ -10,6 +10,15 @@ this.fetchLiveChannels(); }, methods: { + humanizeTime(timeOnline) { + return moment.duration(timeOnline.totalMilliseconds).humanize(); + }, + getImageOrPlaceholder(liveChannel) { + if (liveChannel.imageUrl) { + return liveChannel.imageUrl; + } + return `https://via.placeholder.com/150/404041/FFFFFF/?text=${liveChannel.channelName}`; + }, fetchLiveChannels: function () { axios.get(`/api/IsLive/`) .then(response => {