From 681deda3d299e0691d82b8e4cacd9ae4ef679743 Mon Sep 17 00:00:00 2001 From: Darkstack <1835601+darkstack@users.noreply.github.com> Date: Sun, 7 Oct 2018 15:15:11 +0200 Subject: [PATCH] Challonge Provider can create tournament --- .../LaDOSE.Api.DTO/LaDOSE.Api.DTO.csproj | 7 ++ .../LaDOSE.Api/Controllers/EventController.cs | 43 ++++++++++ LaDOSE.Src/LaDOSE.Api/LaDOSE.Api.csproj | 1 + LaDOSE.Src/LaDOSE.Api/Startup.cs | 13 ++- LaDOSE.Src/LaDOSE.Api/appsettings.json | 3 + .../Service/ChallongeService.cs | 30 ++++--- .../LaDOSE.Entity/Context/LaDOSEDbContext.cs | 15 ++++ LaDOSE.Src/LaDOSE.Entity/Event.cs | 5 ++ LaDOSE.Src/LaDOSE.Entity/EventGame.cs | 12 +++ LaDOSE.Src/LaDOSE.Entity/Game.cs | 2 + .../Interface/IChallongeProvider.cs | 12 +++ .../LaDOSE.Service/Interface/IEventService.cs | 2 +- .../LaDOSE.Service/LaDOSE.Business.csproj | 6 ++ .../Provider/ChallongeProvider.cs | 78 ++++++++++++++++++ .../LaDOSE.Service/Service/EventService.cs | 32 ++++++- .../LaDOSE.Service/Service/GameService.cs | 2 +- Library/ChallongeCSharpDriver.dll | Bin 44032 -> 44032 bytes 17 files changed, 245 insertions(+), 18 deletions(-) create mode 100644 LaDOSE.Src/LaDOSE.Api.DTO/LaDOSE.Api.DTO.csproj create mode 100644 LaDOSE.Src/LaDOSE.Api/Controllers/EventController.cs create mode 100644 LaDOSE.Src/LaDOSE.Entity/EventGame.cs create mode 100644 LaDOSE.Src/LaDOSE.Service/Interface/IChallongeProvider.cs create mode 100644 LaDOSE.Src/LaDOSE.Service/Provider/ChallongeProvider.cs diff --git a/LaDOSE.Src/LaDOSE.Api.DTO/LaDOSE.Api.DTO.csproj b/LaDOSE.Src/LaDOSE.Api.DTO/LaDOSE.Api.DTO.csproj new file mode 100644 index 0000000..5766db6 --- /dev/null +++ b/LaDOSE.Src/LaDOSE.Api.DTO/LaDOSE.Api.DTO.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp2.0 + + + diff --git a/LaDOSE.Src/LaDOSE.Api/Controllers/EventController.cs b/LaDOSE.Src/LaDOSE.Api/Controllers/EventController.cs new file mode 100644 index 0000000..2c7dd4a --- /dev/null +++ b/LaDOSE.Src/LaDOSE.Api/Controllers/EventController.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LaDOSE.Business.Interface; +using LaDOSE.Entity; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace LaDOSE.Api.Controllers +{ + [Produces("application/json")] + [Route("api/[controller]")] + public class EventController : Controller + { + private IEventService _eventService; + + public EventController(IEventService eventService) + { + _eventService = eventService; + } + + [HttpPost] + public Event Post([FromBody]Event dto) + { + return _eventService.Create(dto); + } + + [HttpGet("{id}")] + public Event Get(int id) + { + return _eventService.GetById(id); + + } + + [HttpGet("Generate/{dto}")] + public bool GenerateChallonge(int dto) + { + return _eventService.CreateChallonge(dto); + + } + } +} \ No newline at end of file diff --git a/LaDOSE.Src/LaDOSE.Api/LaDOSE.Api.csproj b/LaDOSE.Src/LaDOSE.Api/LaDOSE.Api.csproj index 26c785f..adfc078 100644 --- a/LaDOSE.Src/LaDOSE.Api/LaDOSE.Api.csproj +++ b/LaDOSE.Src/LaDOSE.Api/LaDOSE.Api.csproj @@ -11,6 +11,7 @@ + diff --git a/LaDOSE.Src/LaDOSE.Api/Startup.cs b/LaDOSE.Src/LaDOSE.Api/Startup.cs index d8c889b..51b151f 100644 --- a/LaDOSE.Src/LaDOSE.Api/Startup.cs +++ b/LaDOSE.Src/LaDOSE.Api/Startup.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using LaDOSE.Business.Interface; +using LaDOSE.Business.Provider; using LaDOSE.Business.Service; using LaDOSE.Entity; using LaDOSE.Entity.Context; @@ -40,6 +41,7 @@ namespace LaDOSE.Api var MySqlDatabase = this.Configuration["MySql:Database"]; var MySqlUser = this.Configuration["MySql:User"]; var MySqlPassword = this.Configuration["MySql:Password"]; + services.AddCors(); services.AddMvc().AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); services.AddDbContextPool( // replace "YourDbContext" with the class name of your DbContext @@ -86,11 +88,18 @@ namespace LaDOSE.Api }); // configure DI for application services + AddDIConfig(services); + } + + private void AddDIConfig(IServiceCollection services) + { + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddTransient(p => new ChallongeProvider(this.Configuration["ApiKey:ChallongeApiKey"])); } - - + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) diff --git a/LaDOSE.Src/LaDOSE.Api/appsettings.json b/LaDOSE.Src/LaDOSE.Api/appsettings.json index ca6066c..4a73694 100644 --- a/LaDOSE.Src/LaDOSE.Api/appsettings.json +++ b/LaDOSE.Src/LaDOSE.Api/appsettings.json @@ -14,6 +14,9 @@ "User": "User", "Password": "Password" }, + "ApiKey": { + "ChallongeApiKey": "Challonge ApiKey" + }, "AllowedHosts": "*", "Port": 5000, "JWTTokenSecret": "here goes the custom Secret key for authnetication" diff --git a/LaDOSE.Src/LaDOSE.DiscordBot/Service/ChallongeService.cs b/LaDOSE.Src/LaDOSE.DiscordBot/Service/ChallongeService.cs index a74dd27..22198c9 100644 --- a/LaDOSE.Src/LaDOSE.DiscordBot/Service/ChallongeService.cs +++ b/LaDOSE.Src/LaDOSE.DiscordBot/Service/ChallongeService.cs @@ -35,25 +35,29 @@ namespace LaDOSE.DiscordBot.Service try { - - List tournamentResultList = await new TournamentsQuery() + + List tournamentResultList = await new TournamentsQuery() { state = TournamentState.Ended } - .call(this.ApiCaller); + .call(this.ApiCaller); - var lastDate = tournamentResultList.Max(e => e.completed_at); - var lastRankingDate = new DateTime(lastDate.Year, lastDate.Month, lastDate.Day); - var lastTournament = tournamentResultList.Where(e => e.completed_at > lastRankingDate).ToList(); - string returnValue = "Les derniers tournois : \n"; - foreach (var tournamentResult in lastTournament) - { - returnValue += $"{tournamentResult.name} : \n"; - } + var lastDate = tournamentResultList.Max(e => e.completed_at); + if (lastDate.HasValue) + { + var lastRankingDate = new DateTime(lastDate.Value.Year, lastDate.Value.Month, lastDate.Value.Day); - DernierTournois = returnValue; - return true; + var lastTournament = tournamentResultList.Where(e => e.completed_at > lastRankingDate).ToList(); + string returnValue = "Les derniers tournois : \n"; + foreach (var tournamentResult in lastTournament) + { + returnValue += $"{tournamentResult.name} : \n"; + } + + DernierTournois = returnValue; + } + return true; } catch { diff --git a/LaDOSE.Src/LaDOSE.Entity/Context/LaDOSEDbContext.cs b/LaDOSE.Src/LaDOSE.Entity/Context/LaDOSEDbContext.cs index ed00c13..39b0bed 100644 --- a/LaDOSE.Src/LaDOSE.Entity/Context/LaDOSEDbContext.cs +++ b/LaDOSE.Src/LaDOSE.Entity/Context/LaDOSEDbContext.cs @@ -21,12 +21,16 @@ namespace LaDOSE.Entity.Context base.OnModelCreating(modelBuilder); modelBuilder.Entity() .HasKey(t => new { t.SeasonId, t.GameId }); + modelBuilder.Entity() + .HasKey(t => new { t.EventId, t.GameId }); modelBuilder.Entity() .HasOne(s => s.Season) .WithMany(p => p.Event) .HasForeignKey(fk => fk.SeasonId); + + modelBuilder.Entity() .HasOne(pt => pt.Season) .WithMany(p => p.Games) @@ -37,6 +41,17 @@ namespace LaDOSE.Entity.Context .WithMany(p => p.Seasons) .HasForeignKey(pt => pt.GameId); + + modelBuilder.Entity() + .HasOne(pt => pt.Event) + .WithMany(p => p.Games) + .HasForeignKey(pt => pt.EventId); + + modelBuilder.Entity() + .HasOne(pt => pt.Game) + .WithMany(p => p.Events) + .HasForeignKey(pt => pt.GameId); + } } diff --git a/LaDOSE.Src/LaDOSE.Entity/Event.cs b/LaDOSE.Src/LaDOSE.Entity/Event.cs index 2201f5a..0d55573 100644 --- a/LaDOSE.Src/LaDOSE.Entity/Event.cs +++ b/LaDOSE.Src/LaDOSE.Entity/Event.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace LaDOSE.Entity @@ -13,5 +14,9 @@ namespace LaDOSE.Entity public Season Season { get; set; } public bool Ranking { get; set; } + + public virtual IEnumerable Games { get; set; } + + } } \ No newline at end of file diff --git a/LaDOSE.Src/LaDOSE.Entity/EventGame.cs b/LaDOSE.Src/LaDOSE.Entity/EventGame.cs new file mode 100644 index 0000000..6c1bda9 --- /dev/null +++ b/LaDOSE.Src/LaDOSE.Entity/EventGame.cs @@ -0,0 +1,12 @@ +namespace LaDOSE.Entity +{ + public class EventGame + { + + public int EventId { get; set; } + public Event Event { get; set; } + public int GameId { get; set; } + public Game Game { get; set; } + + } +} \ No newline at end of file diff --git a/LaDOSE.Src/LaDOSE.Entity/Game.cs b/LaDOSE.Src/LaDOSE.Entity/Game.cs index c20751b..8180f14 100644 --- a/LaDOSE.Src/LaDOSE.Entity/Game.cs +++ b/LaDOSE.Src/LaDOSE.Entity/Game.cs @@ -12,5 +12,7 @@ namespace LaDOSE.Entity public string ImgUrl { get; set; } public virtual IEnumerable Seasons { get; set; } + public virtual IEnumerable Events { get; set; } + } } diff --git a/LaDOSE.Src/LaDOSE.Service/Interface/IChallongeProvider.cs b/LaDOSE.Src/LaDOSE.Service/Interface/IChallongeProvider.cs new file mode 100644 index 0000000..f46edc0 --- /dev/null +++ b/LaDOSE.Src/LaDOSE.Service/Interface/IChallongeProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace LaDOSE.Business.Interface +{ + public interface IChallongeProvider + { + Task GetLastTournament(); + string GetLastTournamentMessage(); + Task> CreateTournament(string name, string url); + } +} \ No newline at end of file diff --git a/LaDOSE.Src/LaDOSE.Service/Interface/IEventService.cs b/LaDOSE.Src/LaDOSE.Service/Interface/IEventService.cs index cdc762d..fa841e0 100644 --- a/LaDOSE.Src/LaDOSE.Service/Interface/IEventService.cs +++ b/LaDOSE.Src/LaDOSE.Service/Interface/IEventService.cs @@ -4,6 +4,6 @@ namespace LaDOSE.Business.Interface { public interface IEventService : IBaseService { - + bool CreateChallonge(int dto); } } \ No newline at end of file diff --git a/LaDOSE.Src/LaDOSE.Service/LaDOSE.Business.csproj b/LaDOSE.Src/LaDOSE.Service/LaDOSE.Business.csproj index 10f635d..9b0eda5 100644 --- a/LaDOSE.Src/LaDOSE.Service/LaDOSE.Business.csproj +++ b/LaDOSE.Src/LaDOSE.Service/LaDOSE.Business.csproj @@ -10,4 +10,10 @@ + + + ..\..\Library\ChallongeCSharpDriver.dll + + + diff --git a/LaDOSE.Src/LaDOSE.Service/Provider/ChallongeProvider.cs b/LaDOSE.Src/LaDOSE.Service/Provider/ChallongeProvider.cs new file mode 100644 index 0000000..4d00fe3 --- /dev/null +++ b/LaDOSE.Src/LaDOSE.Service/Provider/ChallongeProvider.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ChallongeCSharpDriver; +using ChallongeCSharpDriver.Caller; +using ChallongeCSharpDriver.Core.Queries; +using ChallongeCSharpDriver.Core.Results; +using LaDOSE.Business.Interface; + +namespace LaDOSE.Business.Provider +{ + public class ChallongeProvider : IChallongeProvider + { + private ChallongeConfig Config; + public string ApiKey { get; set; } + + public ChallongeHTTPClientAPICaller ApiCaller { get; set; } + + public string DernierTournois { get; set; } + + + public ChallongeProvider(string apiKey) + { + this.ApiKey = apiKey; + this.Config = new ChallongeConfig(this.ApiKey); + this.ApiCaller = new ChallongeHTTPClientAPICaller(Config); + DernierTournois = "Aucun tournois."; + } + + public async Task> CreateTournament(string name, string url) + { + var p = await new CreateTournamentQuery(name, TournamentType.Double_Elimination, url).call(ApiCaller); + return new Tuple(p.id, p.url); + + + } + + public async Task GetLastTournament() + { + try + { + + + List tournamentResultList = await new TournamentsQuery() + { + state = TournamentState.Ended + } + .call(this.ApiCaller); + + + var lastDate = tournamentResultList.Max(e => e.completed_at); + if (lastDate.HasValue) + { + var lastRankingDate = new DateTime(lastDate.Value.Year, lastDate.Value.Month, lastDate.Value.Day); + + var lastTournament = tournamentResultList.Where(e => e.completed_at > lastRankingDate).ToList(); + string returnValue = "Les derniers tournois : \n"; + foreach (var tournamentResult in lastTournament) + { + returnValue += $"{tournamentResult.name} : \n"; + } + + DernierTournois = returnValue; + } + return true; + } + catch + { + return false; + } + } + public string GetLastTournamentMessage() + { + return DernierTournois; + } + } +} \ No newline at end of file diff --git a/LaDOSE.Src/LaDOSE.Service/Service/EventService.cs b/LaDOSE.Src/LaDOSE.Service/Service/EventService.cs index fe092b8..440937d 100644 --- a/LaDOSE.Src/LaDOSE.Service/Service/EventService.cs +++ b/LaDOSE.Src/LaDOSE.Service/Service/EventService.cs @@ -1,14 +1,24 @@ using System; +using System.Linq; using LaDOSE.Business.Interface; using LaDOSE.Entity; using LaDOSE.Entity.Context; +using Microsoft.EntityFrameworkCore; namespace LaDOSE.Business.Service { public class EventService : BaseService, IEventService { - public EventService(LaDOSEDbContext context) : base(context) + private IChallongeProvider _challongeProvider; + + public EventService(LaDOSEDbContext context,IChallongeProvider challongeProvider) : base(context) { + this._challongeProvider = challongeProvider; + } + + public override Event GetById(int id) + { + return _context.Event.Include(e=>e.Season).Include(e=>e.Games).ThenInclude(e=>e.Game).FirstOrDefault(e=>e.Id == id); } public override Event Create(Event e) @@ -22,5 +32,25 @@ namespace LaDOSE.Business.Service _context.SaveChanges(); return eventAdded.Entity; } + + public bool CreateChallonge(int dto) + { + var currentEvent = _context.Event.Include(e=>e.Games).ThenInclude(e=>e.Game).FirstOrDefault(e=>e.Id == dto); + if (currentEvent != null) + { + var games = currentEvent.Games.Select(e => e.Game); + var s = currentEvent.Date.ToString("MM/dd/yy"); + foreach (var game in games) + { + var url = $"TestDev{game.Id}{game.Name}"; + var name = $"[{s}]Ranking {currentEvent.Name}{game.Name}"; + _challongeProvider.CreateTournament(name,url); + } + + return true; + } + + return false; + } } } \ No newline at end of file diff --git a/LaDOSE.Src/LaDOSE.Service/Service/GameService.cs b/LaDOSE.Src/LaDOSE.Service/Service/GameService.cs index 1e60406..cbe5ed2 100644 --- a/LaDOSE.Src/LaDOSE.Service/Service/GameService.cs +++ b/LaDOSE.Src/LaDOSE.Service/Service/GameService.cs @@ -17,7 +17,7 @@ namespace LaDOSE.Business.Service public override IEnumerable GetAll() { - return _context.Game.Include(e => e.Seasons).ToList(); + return _context.Game.Include(e => e.Seasons).ThenInclude(e=>e.Season).ToList(); } diff --git a/Library/ChallongeCSharpDriver.dll b/Library/ChallongeCSharpDriver.dll index 0e1f2b597f988450966aede25c77abb481cde07e..d0b5f85d8554fc84a60827ed59b3b94a7ce1ec2c 100644 GIT binary patch delta 3623 zcmZp;!PIbrX+j6fgGZ}gP3)0joHcP}IHSj8Mn-j3g@X(XA(IuEbS6hKsxvN_+{jqZ zSTmWCNtpFT76Sw0IoZz90M5#(kKtxxn9Nbez{XI-`G<>* zA&?iuW)Mu_W@PYS$lzvUFyWfYz{aqJuaKLKfkChqEZ&GD-p9?x@Q(?k1}2NFYZ^Bj z!yYCvu-<;;QN+lwo}CxT2u2<@h7f)Ruo_MtkOh1^j0|Vk1(DQ< zBdJjUt5M}am>id=Z`IVBLRCJcu)@nG2t+_)WD z&mforR+Ne1n<6Bq)PvQu@Gzn|cOsG+4sh(wSlUKW#>wM&qV)zGE(#FBT`He@4fn5cpp^am|;2$1o27V~-svslpWIk67 zes*3t25+b!i;(DKS658|C0==kE{GVzOlC&j$#t$$j2e??y1Mck@v1UZse(+L#xQ}$ zV)9#8>3Ta}4TcX;u^JvbUQGsdHINEW_%SkQftU;o499rwxm+#SBpU(VZ-EhH%)CKO=g|M89w>8n{<6FuQuFWZ5&6L;XGD$ku-!HYnwASDMjca-}cGhK;@;8_xQg*BkSd zF|09!r~>(=jNu5F2l7iIUm1g-5lF6$!# zD9g>im_g15BnSzI#SCg-UNeIj1OH^^AWeQz{-q2~zF-v`Y9ePRTL+o*OYtvfF!O_m zEfrCi+!z$hug<@UfhhnY_Lb+wv(~E6Bi@%+M^Dz?jUin@w;5V=}}4&6$Z4 z8CAW77&$ABr{x^ET8OXDnamI_+ze(f;7n$i zDSQ~rX66FP9^gu5xF!66E15wJmpU1_NaqB}J{KzHXJGipyT@-rwgtQ6VB&%p4L=KxFyDgqbZ?3t;rB(UeR z)#;TB7`z+aF8Fz?DU*M5N4Fd|J13i{U_&e8<`vT(F$r@iFo1c24O6)l7#TtA&68(F zGIDTfKqNN5pQXrQSa#)1D+8l%022cP!&(LghLVE}3?+*}nGAv%85kIZ85kHqjR3_% z3=E2s9T%(cx8(cGe0$Av^VZ2%f1ZOJE`19kvPwfUg{ delta 3611 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