diff --git a/GameOn.Application/FIFA/Tournaments/Commands/UpdateTournamentPicture/UpdateTournamentPictureCommand.cs b/GameOn.Application/FIFA/Tournaments/Commands/UpdateTournamentPicture/UpdateTournamentPictureCommand.cs
new file mode 100644
index 0000000..6f4ae1c
--- /dev/null
+++ b/GameOn.Application/FIFA/Tournaments/Commands/UpdateTournamentPicture/UpdateTournamentPictureCommand.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) LeadOn's Corp'. All rights reserved.
+//
+
+namespace GameOn.Application.FIFA.Tournaments.Commands.UpdateTournamentPicture
+{
+ using GameOn.Common.DTOs;
+ using GameOn.Domain;
+ using MediatR;
+ using Microsoft.AspNetCore.Http;
+
+ ///
+ /// UpdateTournamentPictureCommand class.
+ ///
+ public class UpdateTournamentPictureCommand : IRequest
+ {
+ ///
+ /// Gets or sets Tournament ID.
+ ///
+ public int TournamentId { get; set; }
+
+ ///
+ /// Gets or sets File.
+ ///
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+ public IFormFile File { get; set; }
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+ }
+}
diff --git a/GameOn.Application/FIFA/Tournaments/Commands/UpdateTournamentPicture/UpdateTournamentPictureCommandHandler.cs b/GameOn.Application/FIFA/Tournaments/Commands/UpdateTournamentPicture/UpdateTournamentPictureCommandHandler.cs
new file mode 100644
index 0000000..b884ccf
--- /dev/null
+++ b/GameOn.Application/FIFA/Tournaments/Commands/UpdateTournamentPicture/UpdateTournamentPictureCommandHandler.cs
@@ -0,0 +1,66 @@
+//
+// Copyright (c) LeadOn's Corp'. All rights reserved.
+//
+
+using GameOn.Application.FIFA.Tournaments.Queries.GetTournamentById;
+
+namespace GameOn.Application.FIFA.Tournaments.Commands.UpdateTournamentPicture
+{
+ using GameOn.Application.Common.Players.Queries.GetPlayerById;
+ using GameOn.Common.Exceptions;
+ using GameOn.Common.Interfaces;
+ using GameOn.Domain;
+ using GameOn.External.NetworkStorage.Interfaces;
+ using MediatR;
+ using Microsoft.EntityFrameworkCore;
+
+ ///
+ /// UpdateTournamentPictureCommandHandler class.
+ ///
+ public class UpdateTournamentPictureCommandHandler : IRequestHandler
+ {
+ private readonly IMediator mediator;
+ private readonly IApplicationDbContext context;
+ private readonly INetworkStorageService nsService;
+ private readonly string bucketName = Environment.GetEnvironmentVariable("S3_BUCKET_NAME") ?? throw new MissingEnvironmentVariableException("S3_BUCKET_NAME");
+ private readonly string tpBasePath = Environment.GetEnvironmentVariable("S3_TP_BASE_PATH") ?? throw new MissingEnvironmentVariableException("S3_TP_BASE_PATH");
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// DbContext, injected.
+ /// NetworkStorageService, injected.
+ /// Mediator, injected.
+ public UpdateTournamentPictureCommandHandler(IApplicationDbContext context, INetworkStorageService nsService, IMediator mediator)
+ {
+ this.context = context;
+ this.nsService = nsService;
+ this.mediator = mediator;
+ }
+
+ ///
+ public async Task Handle(UpdateTournamentPictureCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await this.nsService.UploadFile(this.bucketName, this.tpBasePath + "/" + request.TournamentId + Path.GetExtension(request.File.FileName), request.File);
+
+ // now that file is uploaded, updating user
+ var tournamentInDb = await this.context.Tournaments.FirstOrDefaultAsync(x => x.Id == request.TournamentId, cancellationToken);
+
+ if (tournamentInDb is not null)
+ {
+ tournamentInDb.LogoUrl = request.TournamentId + Path.GetExtension(request.File.FileName);
+ this.context.Tournaments.Update(tournamentInDb);
+ await this.context.SaveChangesAsync(cancellationToken);
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/GameOn.Application/FIFA/Tournaments/Queries/GetTournamentLogo/GetTournamentLogoQuery.cs b/GameOn.Application/FIFA/Tournaments/Queries/GetTournamentLogo/GetTournamentLogoQuery.cs
new file mode 100644
index 0000000..27c305a
--- /dev/null
+++ b/GameOn.Application/FIFA/Tournaments/Queries/GetTournamentLogo/GetTournamentLogoQuery.cs
@@ -0,0 +1,20 @@
+//
+// Copyright (c) LeadOn's Corp'. All rights reserved.
+//
+
+namespace GameOn.Application.FIFA.Tournaments.Queries.GetTournamentLogo
+{
+ using GameOn.Common.DTOs;
+ using MediatR;
+
+ ///
+ /// GetTournamentLogoQuery class.
+ ///
+ public class GetTournamentLogoQuery : IRequest
+ {
+ ///
+ /// Gets or sets Tournament ID.
+ ///
+ public int TournamentId { get; set; }
+ }
+}
diff --git a/GameOn.Application/FIFA/Tournaments/Queries/GetTournamentLogo/GetTournamentLogoQueryHandler.cs b/GameOn.Application/FIFA/Tournaments/Queries/GetTournamentLogo/GetTournamentLogoQueryHandler.cs
new file mode 100644
index 0000000..a00e01f
--- /dev/null
+++ b/GameOn.Application/FIFA/Tournaments/Queries/GetTournamentLogo/GetTournamentLogoQueryHandler.cs
@@ -0,0 +1,63 @@
+//
+// Copyright (c) LeadOn's Corp'. All rights reserved.
+//
+
+using GameOn.Application.FIFA.Tournaments.Queries.GetTournamentById;
+
+namespace GameOn.Application.FIFA.Tournaments.Queries.GetTournamentLogo
+{
+ using GameOn.Application.Common.Players.Queries.GetPlayerById;
+ using GameOn.Common.DTOs;
+ using GameOn.Common.Exceptions;
+ using GameOn.External.NetworkStorage.Interfaces;
+ using MediatR;
+
+ ///
+ /// GetTournamentLogoQueryHandler class.
+ ///
+ public class GetTournamentLogoQueryHandler : IRequestHandler
+ {
+ private readonly IMediator mediator;
+ private readonly INetworkStorageService nsService;
+ private readonly string bucketName = Environment.GetEnvironmentVariable("S3_BUCKET_NAME") ?? throw new MissingEnvironmentVariableException("S3_BUCKET_NAME");
+ private readonly string tpBasePath = Environment.GetEnvironmentVariable("S3_TP_BASE_PATH") ?? throw new MissingEnvironmentVariableException("S3_TP_BASE_PATH");
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// NetworkStorageService, injected.
+ /// Mediator, injected.
+ public GetTournamentLogoQueryHandler(INetworkStorageService nsService, IMediator mediator)
+ {
+ this.nsService = nsService;
+ this.mediator = mediator;
+ }
+
+ ///
+ public async Task Handle(GetTournamentLogoQuery request, CancellationToken cancellationToken)
+ {
+ var tpDto = new TournamentLogoDto();
+
+ // First, getting tournament
+ var tournamentInDb = await this.mediator.Send(new GetTournamentByIdQuery { TournamentId = request.TournamentId }, cancellationToken);
+
+ if (tournamentInDb is null)
+ {
+ return tpDto;
+ }
+
+ if (tournamentInDb.LogoUrl is null || tournamentInDb.LogoUrl == string.Empty)
+ {
+ tpDto.FileName = Environment.GetEnvironmentVariable("DEFAULT_PROFILE_PIC") ?? throw new MissingEnvironmentVariableException("DEFAULT_PROFILE_PIC");
+ }
+ else
+ {
+ tpDto.FileName = tournamentInDb.LogoUrl;
+ }
+
+ // Getting profile picture
+ tpDto.FileStream = await this.nsService.GetFile(this.bucketName, this.tpBasePath + "/" + tpDto.FileName);
+ return tpDto;
+ }
+ }
+}
diff --git a/GameOn.Application/GameOn.Application.csproj b/GameOn.Application/GameOn.Application.csproj
index c4ae6e0..96277df 100644
--- a/GameOn.Application/GameOn.Application.csproj
+++ b/GameOn.Application/GameOn.Application.csproj
@@ -9,13 +9,14 @@
Game On! Application
LeadOn
LeadOn's Corp
- Application layer of the Game On! project.
+ Application layer of the GameOn! app.
LeadOn's Corp
https://www.valentinvirot.fr
gameon-icon.png
README.md
https://github.com/LeadOn/GameOn-API
git
+ 6.0.0
diff --git a/GameOn.Common/DTOs/TournamentLogoDto.cs b/GameOn.Common/DTOs/TournamentLogoDto.cs
new file mode 100644
index 0000000..8f48bcb
--- /dev/null
+++ b/GameOn.Common/DTOs/TournamentLogoDto.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) LeadOn's Corp'. All rights reserved.
+//
+
+namespace GameOn.Common.DTOs
+{
+ ///
+ /// TournamentLogoDto class.
+ ///
+ public class TournamentLogoDto
+ {
+ ///
+ /// Gets or sets File Stream.
+ ///
+ public Stream? FileStream { get; set; }
+
+ ///
+ /// Gets or sets File name.
+ ///
+ public string FileName { get; set; } = string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/GameOn.Common/GameOn.Common.csproj b/GameOn.Common/GameOn.Common.csproj
index 8ef22a6..c8ad082 100644
--- a/GameOn.Common/GameOn.Common.csproj
+++ b/GameOn.Common/GameOn.Common.csproj
@@ -7,7 +7,7 @@
true
GameOn! Common
LeadOn's Corp
- Common utilities used in the GameOn! project.
+ Common utilities used in the GameOn! app.
LeadOn's Corp
https://www.valentinvirot.fr
LeadOn's Corp
@@ -16,6 +16,7 @@
https://github.com/LeadOn/GameOn-API
git
gameon-icon.ico
+ 6.0.0
diff --git a/GameOn.Domain/GameOn.Domain.csproj b/GameOn.Domain/GameOn.Domain.csproj
index 91f4fbc..60c5ad7 100644
--- a/GameOn.Domain/GameOn.Domain.csproj
+++ b/GameOn.Domain/GameOn.Domain.csproj
@@ -9,13 +9,14 @@
GameOn! Domain
LeadOn
LeadOn's Corp
- Domain layer of the GameOn! project.
+ Domain layer of the GameOn! app.
LeadOn's Corp
https://www.valentinvirot.fr
gameon-icon.png
README.md
https://github.com/LeadOn/GameOn-API
git
+ 6.0.0
diff --git a/GameOn.External/GameOn.External.csproj b/GameOn.External/GameOn.External.csproj
index 56bc8b1..f22390b 100644
--- a/GameOn.External/GameOn.External.csproj
+++ b/GameOn.External/GameOn.External.csproj
@@ -9,13 +9,14 @@
GameOn! External
LeadOn
LeadOn's Corp
- External layer of the GameOn! project.
+ External layer of the GameOn! app.
LeadOn's Corp
https://www.valentinvirot.fr
gameon-icon.png
README.md
https://github.com/LeadOn/GameOn-API
git
+ 6.0.0
diff --git a/GameOn.Persistence/GameOn.Persistence.csproj b/GameOn.Persistence/GameOn.Persistence.csproj
index 9aa8847..c4471fc 100644
--- a/GameOn.Persistence/GameOn.Persistence.csproj
+++ b/GameOn.Persistence/GameOn.Persistence.csproj
@@ -9,13 +9,14 @@
GameOn! Persistence
LeadOn
LeadOn's Corp
- Persistance layer of the GameOn! Project
+ Persistance layer of the GameOn! app.
LeadOn's Corp
https://www.valentinvirot.fr
gameon-icon.png
README.md
https://github.com/LeadOn/GameOn-API
git
+ 6.0.0
diff --git a/GameOn.Presentation/Controllers/FIFA/TournamentController.cs b/GameOn.Presentation/Controllers/FIFA/TournamentController.cs
index c2968bb..bbbc4fd 100644
--- a/GameOn.Presentation/Controllers/FIFA/TournamentController.cs
+++ b/GameOn.Presentation/Controllers/FIFA/TournamentController.cs
@@ -13,12 +13,15 @@ namespace GameOn.Presentation.Controllers.FIFA
using GameOn.Application.FIFA.Tournaments.Commands.SavePhase1Score;
using GameOn.Application.FIFA.Tournaments.Commands.SubscribeTournament;
using GameOn.Application.FIFA.Tournaments.Commands.UpdateTournament;
+ using GameOn.Application.FIFA.Tournaments.Commands.UpdateTournamentPicture;
using GameOn.Application.FIFA.Tournaments.Queries.CheckTournamentSubscription;
using GameOn.Application.FIFA.Tournaments.Queries.GetAllTournaments;
using GameOn.Application.FIFA.Tournaments.Queries.GetFeaturedTournaments;
using GameOn.Application.FIFA.Tournaments.Queries.GetTournamentById;
+ using GameOn.Application.FIFA.Tournaments.Queries.GetTournamentLogo;
using GameOn.Common.DTOs;
using GameOn.Domain;
+ using GameOn.External.NetworkStorage.Interfaces;
using GameOn.Presentation.Classes;
using MediatR;
using Microsoft.AspNetCore.Authorization;
@@ -33,14 +36,17 @@ namespace GameOn.Presentation.Controllers.FIFA
public class TournamentController : ControllerBase
{
private readonly ISender mediator;
+ private readonly INetworkStorageService nsService;
///
/// Initializes a new instance of the class.
///
/// MediatR interface, injected.
- public TournamentController(ISender mediator)
+ /// NetworkStorage interface, injected.
+ public TournamentController(ISender mediator, INetworkStorageService nsService)
{
this.mediator = mediator;
+ this.nsService = nsService;
}
///
@@ -331,5 +337,65 @@ public async Task Delete(int id)
return this.NoContent();
}
}
+
+ ///
+ /// Upload tournament picture.
+ ///
+ /// Tournament ID.
+ /// Tournament picture file.
+ /// IActionResult.
+ [HttpPost]
+ [Authorize(Roles = "gameon_admin")]
+ [Route("{tournamentId}/logo")]
+ [SwaggerOperation(Summary = "Updates tournament picture.")]
+ [SwaggerResponse(201, "Tournament's picture created on the server.")]
+ [SwaggerResponse(401, "Unauthorized.")]
+ [SwaggerResponse(406, "Wrong file format.")]
+ [SwaggerResponse(404, "No profile picture found.")]
+ [SwaggerResponse(500, "Unknown error happened.")]
+ public async Task UpdateTournamentPicture(int tournamentId, IFormFile tournamentPicture)
+ {
+ if (!CheckIfPictureFormatIsAuthorized(tournamentPicture.ContentType))
+ {
+ return this.StatusCode(406);
+ }
+
+ var currentPlayer = await this.mediator.Send(new UpdateTournamentPictureCommand { TournamentId = tournamentId, File = tournamentPicture });
+
+ return currentPlayer ? this.Created() : this.Problem();
+ }
+
+ ///
+ /// Gets tournament logo from network attached storage.
+ ///
+ /// Tournament ID.
+ /// File.
+ [HttpGet]
+ [Route("{tournamentId}/logo")]
+ [SwaggerOperation(Summary = "Get tournament logo from server.")]
+ [SwaggerResponse(200, "Tournament logo from server.", typeof(FileStreamResult))]
+ [SwaggerResponse(404, "No tournament logo found.")]
+ [SwaggerResponse(500, "Unknown error happened.")]
+ public async Task GetTournamentLogo(int tournamentId)
+ {
+ var tpDto = await this.mediator.Send(new GetTournamentLogoQuery { TournamentId = tournamentId });
+
+ if (tpDto.FileStream is null)
+ {
+ return this.NotFound();
+ }
+
+ return this.File(tpDto.FileStream, this.nsService.GetContentType(tpDto.FileName));
+ }
+
+ ///
+ /// Checks if file type is valid.
+ ///
+ /// File content type.
+ /// True if valid, false if not.
+ private static bool CheckIfPictureFormatIsAuthorized(string contentType)
+ {
+ return contentType is "image/jpeg" or "image/png" or "image/gif" or "image/webp";
+ }
}
}
diff --git a/GameOn.Presentation/GameOn.Presentation.csproj b/GameOn.Presentation/GameOn.Presentation.csproj
index 19cd582..f0c0258 100644
--- a/GameOn.Presentation/GameOn.Presentation.csproj
+++ b/GameOn.Presentation/GameOn.Presentation.csproj
@@ -9,13 +9,14 @@
GameOn! API
LeadOn
LeadOn's Corp
- Back-end of the GameOn! project.
+ Back-end of the GameOn! app.
LeadOn's Corp
https://www.valentinvirot.fr
gameon-icon.png
README.md
https://github.com/LeadOn/GameOn-API
git
+ 6.0.0
diff --git a/GameOn.Presentation/Properties/launchSettings.json b/GameOn.Presentation/Properties/launchSettings.json
index 0796a9d..2a35ae1 100644
--- a/GameOn.Presentation/Properties/launchSettings.json
+++ b/GameOn.Presentation/Properties/launchSettings.json
@@ -20,6 +20,7 @@
"S3_SECRET_KEY": "",
"S3_BUCKET_NAME": "",
"S3_PP_BASE_PATH": "",
+ "S3_TP_BASE_PATH": "",
"DEFAULT_PROFILE_PIC": ""
},
"dotnetRunMessages": true,
diff --git a/README.md b/README.md
index 60b0c03..38e3b6d 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ Here are all of the environment variables currently used :
S3_SECRET_KEY: S3 Bucket Secret key.
S3_BUCKET_NAME: S3 Bucket Name.
S3_PP_BASE_PATH: Base path to profile pictures in S3 bucket.
+ S3_TP_BASE_PATH: Base path to tournament pictures in S3 bucket.
DEFAULT_PROFILE_PIC: Default profile picture name.