diff options
Diffstat (limited to 'src/Services')
67 files changed, 3907 insertions, 0 deletions
diff --git a/src/Services/DevHive.Services.Models/Comment/CreateCommentServiceModel.cs b/src/Services/DevHive.Services.Models/Comment/CreateCommentServiceModel.cs new file mode 100644 index 0000000..30e919b --- /dev/null +++ b/src/Services/DevHive.Services.Models/Comment/CreateCommentServiceModel.cs @@ -0,0 +1,13 @@ +using System; + +namespace DevHive.Services.Models.Comment +{ + public class CreateCommentServiceModel + { + public Guid PostId { get; set; } + + public Guid CreatorId { get; set; } + + public string Message { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Comment/ReadCommentServiceModel.cs b/src/Services/DevHive.Services.Models/Comment/ReadCommentServiceModel.cs new file mode 100644 index 0000000..3196233 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Comment/ReadCommentServiceModel.cs @@ -0,0 +1,21 @@ +using System; + +namespace DevHive.Services.Models.Comment +{ + public class ReadCommentServiceModel + { + public Guid CommentId { get; set; } + + public string IssuerFirstName { get; set; } + + public string IssuerLastName { get; set; } + + public string IssuerUsername { get; set; } + + public Guid PostId { get; set; } + + public string Message { get; set; } + + public DateTime TimeCreated { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Comment/UpdateCommentServiceModel.cs b/src/Services/DevHive.Services.Models/Comment/UpdateCommentServiceModel.cs new file mode 100644 index 0000000..3b78200 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Comment/UpdateCommentServiceModel.cs @@ -0,0 +1,15 @@ +using System; + +namespace DevHive.Services.Models.Comment +{ + public class UpdateCommentServiceModel + { + public Guid CreatorId { get; set; } + + public Guid CommentId { get; set; } + + public Guid PostId { get; set; } + + public string NewMessage { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/DevHive.Services.Models.csproj b/src/Services/DevHive.Services.Models/DevHive.Services.Models.csproj new file mode 100644 index 0000000..ebf5f9c --- /dev/null +++ b/src/Services/DevHive.Services.Models/DevHive.Services.Models.csproj @@ -0,0 +1,11 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Services/DevHive.Services.Models/Feed/GetPageServiceModel.cs b/src/Services/DevHive.Services.Models/Feed/GetPageServiceModel.cs new file mode 100644 index 0000000..1f02486 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Feed/GetPageServiceModel.cs @@ -0,0 +1,17 @@ +using System; + +namespace DevHive.Services.Models +{ + public class GetPageServiceModel + { + public Guid UserId { get; set; } + + public string Username { get; set; } + + public int PageNumber { get; set; } + + public DateTime FirstRequestIssued { get; set; } + + public int PageSize { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Feed/ReadPageServiceModel.cs b/src/Services/DevHive.Services.Models/Feed/ReadPageServiceModel.cs new file mode 100644 index 0000000..95f6845 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Feed/ReadPageServiceModel.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using DevHive.Services.Models.Post; + +namespace DevHive.Services.Models +{ + public class ReadPageServiceModel + { + public List<ReadPostServiceModel> Posts { get; set; } = new(); + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/Role/CreateRoleServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/Role/CreateRoleServiceModel.cs new file mode 100644 index 0000000..3bed3fd --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/Role/CreateRoleServiceModel.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace DevHive.Services.Models.Identity.Role +{ + public class CreateRoleServiceModel + { + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/Role/RoleServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/Role/RoleServiceModel.cs new file mode 100644 index 0000000..07249fe --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/Role/RoleServiceModel.cs @@ -0,0 +1,7 @@ +namespace DevHive.Services.Models.Identity.Role +{ + public class RoleServiceModel + { + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/Role/UpdateRoleServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/Role/UpdateRoleServiceModel.cs new file mode 100644 index 0000000..e21e6b4 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/Role/UpdateRoleServiceModel.cs @@ -0,0 +1,10 @@ +using System; + +namespace DevHive.Services.Models.Identity.Role +{ + public class UpdateRoleServiceModel + { + public Guid Id { get; set; } + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/BaseUserServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/BaseUserServiceModel.cs new file mode 100644 index 0000000..514f82a --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/BaseUserServiceModel.cs @@ -0,0 +1,10 @@ +namespace DevHive.Services.Models.Identity.User +{ + public class BaseUserServiceModel + { + public string UserName { get; set; } + public string Email { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/FriendServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/FriendServiceModel.cs new file mode 100644 index 0000000..a784f5c --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/FriendServiceModel.cs @@ -0,0 +1,10 @@ +using System; + +namespace DevHive.Services.Models.Identity.User +{ + public class FriendServiceModel + { + public Guid Id { get; set; } + public string UserName { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/LoginServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/LoginServiceModel.cs new file mode 100644 index 0000000..4d53283 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/LoginServiceModel.cs @@ -0,0 +1,8 @@ +namespace DevHive.Services.Models.Identity.User +{ + public class LoginServiceModel + { + public string UserName { get; set; } + public string Password { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/ProfilePictureServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/ProfilePictureServiceModel.cs new file mode 100644 index 0000000..ad81057 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/ProfilePictureServiceModel.cs @@ -0,0 +1,7 @@ +namespace DevHive.Services.Models.Identity.User +{ + public class ProfilePictureServiceModel + { + public string ProfilePictureURL { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/RegisterServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/RegisterServiceModel.cs new file mode 100644 index 0000000..adc4119 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/RegisterServiceModel.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using DevHive.Services.Models.Language; +using DevHive.Services.Models.Technology; + +namespace DevHive.Services.Models.Identity.User +{ + public class RegisterServiceModel : BaseUserServiceModel + { + public string Password { get; set; } + + public HashSet<LanguageServiceModel> Languages { get; set; } + + public HashSet<TechnologyServiceModel> Technologies { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/UpdateFriendServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/UpdateFriendServiceModel.cs new file mode 100644 index 0000000..b0efe10 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/UpdateFriendServiceModel.cs @@ -0,0 +1,10 @@ +using System; + +namespace DevHive.Services.Models.Identity.User +{ + public class UpdateFriendServiceModel + { + public Guid Id { get; set; } + public string UserName { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/UpdateProfilePictureServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/UpdateProfilePictureServiceModel.cs new file mode 100644 index 0000000..8563953 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/UpdateProfilePictureServiceModel.cs @@ -0,0 +1,12 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace DevHive.Services.Models.Identity.User +{ + public class UpdateProfilePictureServiceModel + { + public Guid UserId { get; set; } + + public IFormFile Picture { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/UpdateUserServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/UpdateUserServiceModel.cs new file mode 100644 index 0000000..b4e4400 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/UpdateUserServiceModel.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using DevHive.Services.Models.Identity.Role; +using DevHive.Services.Models.Language; +using DevHive.Services.Models.Technology; + +namespace DevHive.Services.Models.Identity.User +{ + public class UpdateUserServiceModel : BaseUserServiceModel + { + public Guid Id { get; set; } + + public string Password { get; set; } + + public string ProfilePictureURL { get; set; } + + public HashSet<UpdateRoleServiceModel> Roles { get; set; } = new HashSet<UpdateRoleServiceModel>(); + + public HashSet<UpdateFriendServiceModel> Friends { get; set; } = new HashSet<UpdateFriendServiceModel>(); + + public HashSet<UpdateLanguageServiceModel> Languages { get; set; } = new HashSet<UpdateLanguageServiceModel>(); + + public HashSet<UpdateTechnologyServiceModel> Technologies { get; set; } = new HashSet<UpdateTechnologyServiceModel>(); + + } +} diff --git a/src/Services/DevHive.Services.Models/Identity/User/UserServiceModel.cs b/src/Services/DevHive.Services.Models/Identity/User/UserServiceModel.cs new file mode 100644 index 0000000..ac7bba2 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Identity/User/UserServiceModel.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using DevHive.Common.Models.Misc; +using DevHive.Services.Models.Identity.Role; +using DevHive.Services.Models.Language; +using DevHive.Services.Models.Technology; + +namespace DevHive.Services.Models.Identity.User +{ + public class UserServiceModel : BaseUserServiceModel + { + public string ProfilePictureURL { get; set; } + + public HashSet<RoleServiceModel> Roles { get; set; } = new(); + + public HashSet<FriendServiceModel> Friends { get; set; } = new(); + + public HashSet<LanguageServiceModel> Languages { get; set; } = new(); + + public HashSet<TechnologyServiceModel> Technologies { get; set; } = new(); + + public List<IdModel> Posts { get; set; } = new(); + } +} diff --git a/src/Services/DevHive.Services.Models/Language/CreateLanguageServiceModel.cs b/src/Services/DevHive.Services.Models/Language/CreateLanguageServiceModel.cs new file mode 100644 index 0000000..9d66d3e --- /dev/null +++ b/src/Services/DevHive.Services.Models/Language/CreateLanguageServiceModel.cs @@ -0,0 +1,9 @@ +using System; + +namespace DevHive.Services.Models.Language +{ + public class CreateLanguageServiceModel + { + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Language/LanguageServiceModel.cs b/src/Services/DevHive.Services.Models/Language/LanguageServiceModel.cs new file mode 100644 index 0000000..a07aa16 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Language/LanguageServiceModel.cs @@ -0,0 +1,9 @@ +using System; + +namespace DevHive.Services.Models.Language +{ + public class LanguageServiceModel + { + public Guid Id { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Language/ReadLanguageServiceModel.cs b/src/Services/DevHive.Services.Models/Language/ReadLanguageServiceModel.cs new file mode 100644 index 0000000..651dc6d --- /dev/null +++ b/src/Services/DevHive.Services.Models/Language/ReadLanguageServiceModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace DevHive.Services.Models.Language +{ + public class ReadLanguageServiceModel + { + public Guid Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Language/UpdateLanguageServiceModel.cs b/src/Services/DevHive.Services.Models/Language/UpdateLanguageServiceModel.cs new file mode 100644 index 0000000..84b7f27 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Language/UpdateLanguageServiceModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace DevHive.Services.Models.Language +{ + public class UpdateLanguageServiceModel + { + public Guid Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Post/CreatePostServiceModel.cs b/src/Services/DevHive.Services.Models/Post/CreatePostServiceModel.cs new file mode 100644 index 0000000..304eb90 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Post/CreatePostServiceModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; + +namespace DevHive.Services.Models.Post +{ + public class CreatePostServiceModel + { + public Guid CreatorId { get; set; } + + public string Message { get; set; } + + public List<IFormFile> Files { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Post/Rating/RatePostServiceModel.cs b/src/Services/DevHive.Services.Models/Post/Rating/RatePostServiceModel.cs new file mode 100644 index 0000000..d4eb7bd --- /dev/null +++ b/src/Services/DevHive.Services.Models/Post/Rating/RatePostServiceModel.cs @@ -0,0 +1,13 @@ +using System; + +namespace DevHive.Services.Models.Post.Rating +{ + public class RatePostServiceModel + { + public Guid UserId { get; set; } + + public Guid PostId { get; set; } + + public bool Liked { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Post/Rating/ReadPostRatingServiceModel.cs b/src/Services/DevHive.Services.Models/Post/Rating/ReadPostRatingServiceModel.cs new file mode 100644 index 0000000..8c73aaf --- /dev/null +++ b/src/Services/DevHive.Services.Models/Post/Rating/ReadPostRatingServiceModel.cs @@ -0,0 +1,15 @@ +using System; + +namespace DevHive.Services.Models.Post.Rating +{ + public class ReadPostRatingServiceModel + { + public Guid Id { get; set; } + + public Guid PostId { get; set; } + + public int Likes { get; set; } + + public int Dislikes { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Post/ReadPostServiceModel.cs b/src/Services/DevHive.Services.Models/Post/ReadPostServiceModel.cs new file mode 100644 index 0000000..a7aa882 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Post/ReadPostServiceModel.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using DevHive.Common.Models.Misc; + +namespace DevHive.Services.Models.Post +{ + public class ReadPostServiceModel + { + public Guid PostId { get; set; } + + public string CreatorFirstName { get; set; } + + public string CreatorLastName { get; set; } + + public string CreatorUsername { get; set; } + + public string Message { get; set; } + + public DateTime TimeCreated { get; set; } + + public List<IdModel> Comments { get; set; } = new(); + + public List<string> FileUrls { get; set; } + // public List<FileContentResult> Files { get; set; } = new(); + } +} diff --git a/src/Services/DevHive.Services.Models/Post/UpdatePostServiceModel.cs b/src/Services/DevHive.Services.Models/Post/UpdatePostServiceModel.cs new file mode 100644 index 0000000..51b16bc --- /dev/null +++ b/src/Services/DevHive.Services.Models/Post/UpdatePostServiceModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; + +namespace DevHive.Services.Models.Post +{ + public class UpdatePostServiceModel + { + public Guid PostId { get; set; } + + public Guid CreatorId { get; set; } + + public string NewMessage { get; set; } + + public List<IFormFile> Files { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Technology/CreateTechnologyServiceModel.cs b/src/Services/DevHive.Services.Models/Technology/CreateTechnologyServiceModel.cs new file mode 100644 index 0000000..a31d160 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Technology/CreateTechnologyServiceModel.cs @@ -0,0 +1,9 @@ +using System; + +namespace DevHive.Services.Models.Technology +{ + public class CreateTechnologyServiceModel + { + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Technology/ReadTechnologyServiceModel.cs b/src/Services/DevHive.Services.Models/Technology/ReadTechnologyServiceModel.cs new file mode 100644 index 0000000..99f4750 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Technology/ReadTechnologyServiceModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace DevHive.Services.Models.Technology +{ + public class ReadTechnologyServiceModel + { + public Guid Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Technology/TechnologyServiceModel.cs b/src/Services/DevHive.Services.Models/Technology/TechnologyServiceModel.cs new file mode 100644 index 0000000..cb5c881 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Technology/TechnologyServiceModel.cs @@ -0,0 +1,9 @@ +using System; + +namespace DevHive.Services.Models.Technology +{ + public class TechnologyServiceModel + { + public Guid Id { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Technology/UpdateTechnologyServiceModel.cs b/src/Services/DevHive.Services.Models/Technology/UpdateTechnologyServiceModel.cs new file mode 100644 index 0000000..f4c7921 --- /dev/null +++ b/src/Services/DevHive.Services.Models/Technology/UpdateTechnologyServiceModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace DevHive.Services.Models.Technology +{ + public class UpdateTechnologyServiceModel + { + public Guid Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Tests/CommentService.Tests.cs b/src/Services/DevHive.Services.Tests/CommentService.Tests.cs new file mode 100644 index 0000000..ac022ea --- /dev/null +++ b/src/Services/DevHive.Services.Tests/CommentService.Tests.cs @@ -0,0 +1,262 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Models.Comment; +using DevHive.Services.Services; +using Moq; +using NUnit.Framework; + +namespace DevHive.Services.Tests +{ + [TestFixture] + public class CommentServiceTests + { + private const string MESSAGE = "Gosho Trapov"; + private Mock<IUserRepository> UserRepositoryMock { get; set; } + private Mock<IPostRepository> PostRepositoryMock { get; set; } + private Mock<ICommentRepository> CommentRepositoryMock { get; set; } + private Mock<IMapper> MapperMock { get; set; } + private CommentService CommentService { get; set; } + + #region Setup + [SetUp] + public void Setup() + { + this.UserRepositoryMock = new Mock<IUserRepository>(); + this.PostRepositoryMock = new Mock<IPostRepository>(); + this.CommentRepositoryMock = new Mock<ICommentRepository>(); + this.MapperMock = new Mock<IMapper>(); + this.CommentService = new CommentService(this.UserRepositoryMock.Object, this.PostRepositoryMock.Object, this.CommentRepositoryMock.Object, this.MapperMock.Object); + } + #endregion + + #region AddComment + [Test] + public async Task AddComment_ReturnsNonEmptyGuid_WhenEntityIsAddedSuccessfully() + { + Guid id = Guid.NewGuid(); + User creator = new User { Id = Guid.NewGuid() }; + CreateCommentServiceModel createCommentServiceModel = new CreateCommentServiceModel + { + Message = MESSAGE + }; + Comment comment = new Comment + { + Message = MESSAGE, + Id = id, + }; + + this.CommentRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Comment>())).Returns(Task.FromResult(true)); + this.CommentRepositoryMock.Setup(p => p.GetCommentByIssuerAndTimeCreatedAsync(It.IsAny<Guid>(), It.IsAny<DateTime>())).Returns(Task.FromResult(comment)); + this.PostRepositoryMock.Setup(p => p.DoesPostExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(creator)); + this.MapperMock.Setup(p => p.Map<Comment>(It.IsAny<CreateCommentServiceModel>())).Returns(comment); + + Guid result = await this.CommentService.AddComment(createCommentServiceModel); + + Assert.AreEqual(id, result); + } + + [Test] + public async Task AddComment_ReturnsEmptyGuid_WhenEntityIsNotAddedSuccessfully() + { + CreateCommentServiceModel createCommentServiceModel = new CreateCommentServiceModel + { + Message = MESSAGE + }; + Comment comment = new Comment + { + Message = MESSAGE, + }; + + this.CommentRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Comment>())).Returns(Task.FromResult(false)); + this.PostRepositoryMock.Setup(p => p.DoesPostExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.MapperMock.Setup(p => p.Map<Comment>(It.IsAny<CreateCommentServiceModel>())).Returns(comment); + + Guid result = await this.CommentService.AddComment(createCommentServiceModel); + + Assert.IsTrue(result == Guid.Empty); + } + + [Test] + public void AddComment_ThrowsException_WhenPostDoesNotExist() + { + const string EXCEPTION_MESSAGE = "Post does not exist!"; + + CreateCommentServiceModel createCommentServiceModel = new CreateCommentServiceModel + { + Message = MESSAGE + }; + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.CommentService.AddComment(createCommentServiceModel), "AddComment does not throw excpeion when the post does not exist"); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorecct exception message"); + } + #endregion + + #region GetCommentById + [Test] + public async Task GetCommentById_ReturnsTheComment_WhenItExists() + { + Guid creatorId = new Guid(); + User creator = new User { Id = creatorId }; + Comment comment = new Comment + { + Message = MESSAGE, + Creator = creator + }; + ReadCommentServiceModel commentServiceModel = new ReadCommentServiceModel + { + Message = MESSAGE + }; + User user = new User + { + Id = creatorId, + }; + + this.CommentRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(comment)); + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(user)); + this.MapperMock.Setup(p => p.Map<ReadCommentServiceModel>(It.IsAny<Comment>())).Returns(commentServiceModel); + + ReadCommentServiceModel result = await this.CommentService.GetCommentById(new Guid()); + + Assert.AreEqual(MESSAGE, result.Message); + } + + [Test] + public void GetCommentById_ThorwsException_WhenTheUserDoesNotExist() + { + const string EXCEPTION_MESSAGE = "The user does not exist"; + Guid creatorId = new Guid(); + User creator = new User { Id = creatorId }; + Comment comment = new Comment + { + Message = MESSAGE, + Creator = creator + }; + + this.CommentRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(comment)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.CommentService.GetCommentById(new Guid()), "GetCommentById does not throw exception when the user does not exist"); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message); + } + + [Test] + public void GetCommentById_ThrowsException_WhenCommentDoesNotExist() + { + string exceptionMessage = "The comment does not exist"; + Guid creatorId = new Guid(); + User user = new User + { + Id = creatorId, + }; + + this.CommentRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult<Comment>(null)); + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(user)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.CommentService.GetCommentById(new Guid())); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region UpdateComment + [Test] + public async Task UpdateComment_ReturnsTheIdOfTheComment_WhenUpdatedSuccessfully() + { + Guid id = Guid.NewGuid(); + Comment comment = new Comment + { + Id = id, + Message = MESSAGE + }; + UpdateCommentServiceModel updateCommentServiceModel = new UpdateCommentServiceModel + { + CommentId = id, + NewMessage = MESSAGE + }; + + this.CommentRepositoryMock.Setup(p => p.DoesCommentExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.CommentRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<Comment>())).Returns(Task.FromResult(true)); + this.CommentRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(comment)); + this.MapperMock.Setup(p => p.Map<Comment>(It.IsAny<UpdateCommentServiceModel>())).Returns(comment); + + Guid result = await this.CommentService.UpdateComment(updateCommentServiceModel); + + Assert.AreEqual(updateCommentServiceModel.CommentId, result); + } + + [Test] + public async Task UpdateComment_ReturnsEmptyId_WhenTheCommentIsNotUpdatedSuccessfully() + { + Comment comment = new Comment + { + Message = MESSAGE + }; + UpdateCommentServiceModel updateCommentServiceModel = new UpdateCommentServiceModel + { + CommentId = Guid.NewGuid(), + NewMessage = MESSAGE + }; + + this.CommentRepositoryMock.Setup(p => p.DoesCommentExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.CommentRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<Comment>())).Returns(Task.FromResult(false)); + this.MapperMock.Setup(p => p.Map<Comment>(It.IsAny<UpdateCommentServiceModel>())).Returns(comment); + + Guid result = await this.CommentService.UpdateComment(updateCommentServiceModel); + + Assert.AreEqual(Guid.Empty, result); + } + + [Test] + public void UpdateComment_ThrowsArgumentException_WhenCommentDoesNotExist() + { + string exceptionMessage = "Comment does not exist!"; + UpdateCommentServiceModel updateCommentServiceModel = new UpdateCommentServiceModel + { + }; + + this.CommentRepositoryMock.Setup(p => p.DoesCommentExist(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.CommentService.UpdateComment(updateCommentServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region DeleteComment + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task DeleteComment_ShouldReturnIfDeletionIsSuccessfull_WhenCommentExists(bool shouldPass) + { + Guid id = new Guid(); + Comment comment = new Comment(); + + this.CommentRepositoryMock.Setup(p => p.DoesCommentExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.CommentRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(comment)); + this.CommentRepositoryMock.Setup(p => p.DeleteAsync(It.IsAny<Comment>())).Returns(Task.FromResult(shouldPass)); + + bool result = await this.CommentService.DeleteComment(id); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void DeleteComment_ThrowsException_WhenCommentDoesNotExist() + { + string exceptionMessage = "Comment does not exist!"; + Guid id = new Guid(); + + this.CommentRepositoryMock.Setup(p => p.DoesCommentExist(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.CommentService.DeleteComment(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services.Tests/DevHive.Services.Tests.csproj b/src/Services/DevHive.Services.Tests/DevHive.Services.Tests.csproj new file mode 100644 index 0000000..af419df --- /dev/null +++ b/src/Services/DevHive.Services.Tests/DevHive.Services.Tests.csproj @@ -0,0 +1,29 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.1" /> + <PackageReference Include="Moq" Version="4.16.0" /> + <PackageReference Include="NUnit" Version="3.13.0" /> + <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\DevHive.Services\DevHive.Services.csproj" /> + + <ProjectReference Include="..\DevHive.Services.Models\DevHive.Services.Models.csproj" /> + + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.csproj" /> + </ItemGroup> + + <PropertyGroup> + <EnableNETAnalyzers>true</EnableNETAnalyzers> + <AnalysisLevel>latest</AnalysisLevel> + </PropertyGroup> +</Project> diff --git a/src/Services/DevHive.Services.Tests/FeedService.Tests.cs b/src/Services/DevHive.Services.Tests/FeedService.Tests.cs new file mode 100644 index 0000000..e4020c5 --- /dev/null +++ b/src/Services/DevHive.Services.Tests/FeedService.Tests.cs @@ -0,0 +1,155 @@ +//using System; +//using System.Collections.Generic; +//using System.Threading.Tasks; +//using AutoMapper; +//using DevHive.Data.Interfaces.Repositories; +//using DevHive.Data.Models; +//using DevHive.Services.Models; +//using DevHive.Services.Models.Post; +//using DevHive.Services.Services; +//using Moq; +//using NUnit.Framework; + +//namespace DevHive.Services.Tests +//{ +// [TestFixture] +// public class FeedServiceTests +// { +// private Mock<IFeedRepository> FeedRepositoryMock { get; set; } +// private Mock<IUserRepository> UserRepositoryMock { get; set; } +// private Mock<IMapper> MapperMock { get; set; } +// private FeedService FeedService { get; set; } + +// #region SetUps +// [SetUp] +// public void Setup() +// { +// this.FeedRepositoryMock = new Mock<IFeedRepository>(); +// this.UserRepositoryMock = new Mock<IUserRepository>(); +// this.MapperMock = new Mock<IMapper>(); +// this.FeedService = new FeedService(this.FeedRepositoryMock.Object, this.UserRepositoryMock.Object, this.MapperMock.Object); +// } +// #endregion + +// #region GetPage +// [Test] +// public async Task GetPage_ReturnsReadPageServiceModel_WhenSuitablePostsExist() +// { +// GetPageServiceModel getPageServiceModel = new GetPageServiceModel +// { +// UserId = Guid.NewGuid() +// }; + +// User dummyUser = CreateDummyUser(); +// User anotherDummyUser = CreateAnotherDummyUser(); +// HashSet<User> friends = new HashSet<User>(); +// friends.Add(anotherDummyUser); +// dummyUser.Friends = friends; + +// List<Post> posts = new List<Post> +// { +// new Post{ Message = "Message"} +// }; + +// ReadPostServiceModel readPostServiceModel = new ReadPostServiceModel +// { +// PostId = Guid.NewGuid(), +// Message = "Message" +// }; +// List<ReadPostServiceModel> readPostServiceModels = new List<ReadPostServiceModel>(); +// readPostServiceModels.Add(readPostServiceModel); +// ReadPageServiceModel readPageServiceModel = new ReadPageServiceModel +// { +// Posts = readPostServiceModels +// }; + +// this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(dummyUser)); +// this.FeedRepositoryMock.Setup(p => p.GetFriendsPosts(It.IsAny<List<User>>(), It.IsAny<DateTime>(), It.IsAny<int>(), It.IsAny<int>())).Returns(Task.FromResult(posts)); +// this.MapperMock.Setup(p => p.Map<ReadPostServiceModel>(It.IsAny<Post>())).Returns(readPostServiceModel); + +// ReadPageServiceModel result = await this.FeedService.GetPage(getPageServiceModel); + +// Assert.GreaterOrEqual(1, result.Posts.Count, "GetPage does not correctly return the posts"); +// } + +// [Test] +// public void GetPage_ThrowsException_WhenNoSuitablePostsExist() +// { +// const string EXCEPTION_MESSAGE = "No friends of user have posted anything yet!"; +// GetPageServiceModel getPageServiceModel = new GetPageServiceModel +// { +// UserId = Guid.NewGuid() +// }; + +// User dummyUser = CreateDummyUser(); +// User anotherDummyUser = CreateAnotherDummyUser(); +// HashSet<User> friends = new HashSet<User>(); +// friends.Add(anotherDummyUser); +// dummyUser.Friends = friends; + +// ReadPostServiceModel readPostServiceModel = new ReadPostServiceModel +// { +// PostId = Guid.NewGuid(), +// Message = "Message" +// }; +// List<ReadPostServiceModel> readPostServiceModels = new List<ReadPostServiceModel>(); +// readPostServiceModels.Add(readPostServiceModel); +// ReadPageServiceModel readPageServiceModel = new ReadPageServiceModel +// { +// Posts = readPostServiceModels +// }; + +// this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(dummyUser)); +// this.FeedRepositoryMock.Setup(p => p.GetFriendsPosts(It.IsAny<List<User>>(), It.IsAny<DateTime>(), It.IsAny<int>(), It.IsAny<int>())).Returns(Task.FromResult(new List<Post>())); + +// Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.FeedService.GetPage(getPageServiceModel)); + +// Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Wrong exception message"); +// } + +// [Test] +// public void GetPage_ThrowsException_WhenUserHasNoFriendsToGetPostsFrom() +// { +// const string EXCEPTION_MESSAGE = "User has no friends to get feed from!"; +// GetPageServiceModel getPageServiceModel = new GetPageServiceModel +// { +// UserId = Guid.NewGuid() +// }; + +// User dummyUser = CreateDummyUser(); + +// this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(dummyUser)); + +// Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.FeedService.GetPage(getPageServiceModel)); + +// Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Wrong exception message"); +// } +// #endregion + +// #region HelperMethods +// private User CreateDummyUser() +// { +// return new() +// { +// Id = Guid.NewGuid(), +// UserName = "dummyUser", +// FirstName = "Spas", +// LastName = "Spasov", +// Email = "abv@abv.bg", +// }; +// } + +// private User CreateAnotherDummyUser() +// { +// return new() +// { +// Id = Guid.NewGuid(), +// UserName = "anotherDummyUser", +// FirstName = "Alex", +// LastName = "Spiridonov", +// Email = "a_spiridonov@abv.bg", +// }; +// } +// #endregion +// } +//} diff --git a/src/Services/DevHive.Services.Tests/LanguageService.Tests.cs b/src/Services/DevHive.Services.Tests/LanguageService.Tests.cs new file mode 100644 index 0000000..1b59f91 --- /dev/null +++ b/src/Services/DevHive.Services.Tests/LanguageService.Tests.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Models.Language; +using DevHive.Services.Services; +using Moq; +using NUnit.Framework; + +namespace DevHive.Services.Tests +{ + [TestFixture] + public class LanguageServiceTests + { + private Mock<ILanguageRepository> LanguageRepositoryMock { get; set; } + private Mock<IMapper> MapperMock { get; set; } + private LanguageService LanguageService { get; set; } + + #region SetUps + [SetUp] + public void SetUp() + { + this.LanguageRepositoryMock = new Mock<ILanguageRepository>(); + this.MapperMock = new Mock<IMapper>(); + this.LanguageService = new LanguageService(this.LanguageRepositoryMock.Object, this.MapperMock.Object); + } + #endregion + + #region CreateLanguage + [Test] + public async Task CreateLanguage_ReturnsNonEmptyGuid_WhenEntityIsAddedSuccessfully() + { + string technologyName = "Gosho Trapov"; + Guid id = Guid.NewGuid(); + CreateLanguageServiceModel createLanguageServiceModel = new CreateLanguageServiceModel + { + Name = technologyName + }; + Language language = new Language + { + Name = technologyName, + Id = id + }; + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.LanguageRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Language>())).Returns(Task.FromResult(true)); + this.LanguageRepositoryMock.Setup(p => p.GetByNameAsync(It.IsAny<string>())).Returns(Task.FromResult(language)); + this.MapperMock.Setup(p => p.Map<Language>(It.IsAny<CreateLanguageServiceModel>())).Returns(language); + + Guid result = await this.LanguageService.CreateLanguage(createLanguageServiceModel); + + Assert.AreEqual(id, result); + } + + [Test] + public async Task CreateLanguage_ReturnsEmptyGuid_WhenEntityIsNotAddedSuccessfully() + { + string languageName = "Gosho Trapov"; + + CreateLanguageServiceModel createLanguageServiceModel = new CreateLanguageServiceModel + { + Name = languageName + }; + Language language = new Language + { + Name = languageName + }; + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.LanguageRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Language>())).Returns(Task.FromResult(false)); + this.MapperMock.Setup(p => p.Map<Language>(It.IsAny<CreateLanguageServiceModel>())).Returns(language); + + Guid result = await this.LanguageService.CreateLanguage(createLanguageServiceModel); + + Assert.IsTrue(result == Guid.Empty); + + } + + [Test] + public void CreateLanguage_ThrowsArgumentException_WhenEntityAlreadyExists() + { + string exceptionMessage = "Language already exists!"; + string languageName = "Gosho Trapov"; + + CreateLanguageServiceModel createLanguageServiceModel = new CreateLanguageServiceModel + { + Name = languageName + }; + Language language = new Language + { + Name = languageName + }; + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.LanguageService.CreateLanguage(createLanguageServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region GetLanguageById + [Test] + public async Task GetLanguageById_ReturnsTheLanguage_WhenItExists() + { + Guid id = new Guid(); + string name = "Gosho Trapov"; + Language language = new Language + { + Name = name + }; + ReadLanguageServiceModel readLanguageServiceModel = new ReadLanguageServiceModel + { + Name = name + }; + + this.LanguageRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(language)); + this.MapperMock.Setup(p => p.Map<ReadLanguageServiceModel>(It.IsAny<Language>())).Returns(readLanguageServiceModel); + + ReadLanguageServiceModel result = await this.LanguageService.GetLanguageById(id); + + Assert.AreEqual(name, result.Name); + } + + [Test] + public void GetLanguageById_ThrowsException_WhenLanguageDoesNotExist() + { + string exceptionMessage = "The language does not exist"; + Guid id = new Guid(); + this.LanguageRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult<Language>(null)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.LanguageService.GetLanguageById(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region GetLanguages + [Test] + public void GetLanguages_ReturnsAllLanguages_IfAnyExist() + { + ReadLanguageServiceModel firstLanguage = new ReadLanguageServiceModel(); + ReadLanguageServiceModel secondLanguage = new ReadLanguageServiceModel(); + HashSet<ReadLanguageServiceModel> languges = new HashSet<ReadLanguageServiceModel>(); + languges.Add(firstLanguage); + languges.Add(secondLanguage); + + this.LanguageRepositoryMock.Setup(p => p.GetLanguages()).Returns(new HashSet<Language>()); + this.MapperMock.Setup(p => p.Map<HashSet<ReadLanguageServiceModel>>(It.IsAny<HashSet<Language>>())).Returns(languges); + + HashSet<ReadLanguageServiceModel> result = this.LanguageService.GetLanguages(); + + Assert.GreaterOrEqual(2, result.Count, "GetLanguages does not return all languages"); + } + + [Test] + public void GetLanguages_ReturnsEmptyHashSet_IfNoLanguagesExist() + { + this.LanguageRepositoryMock.Setup(p => p.GetLanguages()).Returns(new HashSet<Language>()); + this.MapperMock.Setup(p => p.Map<HashSet<ReadLanguageServiceModel>>(It.IsAny<HashSet<Language>>())).Returns(new HashSet<ReadLanguageServiceModel>()); + + HashSet<ReadLanguageServiceModel> result = this.LanguageService.GetLanguages(); + + Assert.IsEmpty(result, "GetLanguages does not return empty string when no languages exist"); + } + #endregion + + #region UpdateLanguage + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task UpdateLanguage_ReturnsIfUpdateIsSuccessfull_WhenLanguageExistsy(bool shouldPass) + { + string name = "Gosho Trapov"; + Guid id = Guid.NewGuid(); + Language language = new Language + { + Name = name, + Id = id + }; + UpdateLanguageServiceModel updateLanguageServiceModel = new UpdateLanguageServiceModel + { + Name = name, + }; + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.LanguageRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<Language>())).Returns(Task.FromResult(shouldPass)); + this.MapperMock.Setup(p => p.Map<Language>(It.IsAny<UpdateLanguageServiceModel>())).Returns(language); + + bool result = await this.LanguageService.UpdateLanguage(updateLanguageServiceModel); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void UpdateLanguage_ThrowsArgumentException_WhenLanguageDoesNotExist() + { + string exceptionMessage = "Language does not exist!"; + UpdateLanguageServiceModel updateTechnologyServiceModel = new UpdateLanguageServiceModel + { + }; + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.LanguageService.UpdateLanguage(updateTechnologyServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + + [Test] + public void UpdateLanguage_ThrowsException_WhenLanguageNameAlreadyExists() + { + string exceptionMessage = "Language name already exists in our data base!"; + UpdateLanguageServiceModel updateTechnologyServiceModel = new UpdateLanguageServiceModel + { + }; + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.LanguageService.UpdateLanguage(updateTechnologyServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region DeleteLanguage + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task DeleteLanguage_ShouldReturnIfDeletionIsSuccessfull_WhenLanguageExists(bool shouldPass) + { + Guid id = new Guid(); + Language language = new Language(); + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.LanguageRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(language)); + this.LanguageRepositoryMock.Setup(p => p.DeleteAsync(It.IsAny<Language>())).Returns(Task.FromResult(shouldPass)); + + bool result = await this.LanguageService.DeleteLanguage(id); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void DeleteLanguage_ThrowsException_WhenLanguageDoesNotExist() + { + string exceptionMessage = "Language does not exist!"; + Guid id = new Guid(); + + this.LanguageRepositoryMock.Setup(p => p.DoesLanguageExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.LanguageService.DeleteLanguage(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services.Tests/PostService.Tests.cs b/src/Services/DevHive.Services.Tests/PostService.Tests.cs new file mode 100644 index 0000000..900608c --- /dev/null +++ b/src/Services/DevHive.Services.Tests/PostService.Tests.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Post; +using DevHive.Services.Services; +using Microsoft.AspNetCore.Http; +using Moq; +using NUnit.Framework; + +namespace DevHive.Services.Tests +{ + [TestFixture] + public class PostServiceTests + { + private const string MESSAGE = "Gosho Trapov"; + private Mock<ICloudService> CloudServiceMock { get; set; } + private Mock<IPostRepository> PostRepositoryMock { get; set; } + private Mock<ICommentRepository> CommentRepositoryMock { get; set; } + private Mock<IUserRepository> UserRepositoryMock { get; set; } + private Mock<IMapper> MapperMock { get; set; } + private PostService PostService { get; set; } + + #region SetUps + [SetUp] + public void Setup() + { + this.PostRepositoryMock = new Mock<IPostRepository>(); + this.CloudServiceMock = new Mock<ICloudService>(); + this.UserRepositoryMock = new Mock<IUserRepository>(); + this.CommentRepositoryMock = new Mock<ICommentRepository>(); + this.MapperMock = new Mock<IMapper>(); + this.PostService = new PostService(this.CloudServiceMock.Object, this.UserRepositoryMock.Object, this.PostRepositoryMock.Object, this.CommentRepositoryMock.Object, this.MapperMock.Object); + } + #endregion + + #region CreatePost + [Test] + public async Task CreatePost_ReturnsIdOfThePost_WhenItIsSuccessfullyCreated() + { + Guid postId = Guid.NewGuid(); + User creator = new User { Id = Guid.NewGuid() }; + CreatePostServiceModel createPostServiceModel = new CreatePostServiceModel + { + Files = new List<IFormFile>() + }; + Post post = new Post + { + Message = MESSAGE, + Id = postId, + }; + + this.PostRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Post>())).Returns(Task.FromResult(true)); + this.PostRepositoryMock.Setup(p => p.GetPostByCreatorAndTimeCreatedAsync(It.IsAny<Guid>(), It.IsAny<DateTime>())).Returns(Task.FromResult(post)); + this.UserRepositoryMock.Setup(p => p.DoesUserExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(creator)); + this.MapperMock.Setup(p => p.Map<Post>(It.IsAny<CreatePostServiceModel>())).Returns(post); + + Guid result = await this.PostService.CreatePost(createPostServiceModel); + + Assert.AreEqual(postId, result, "CreatePost does not return the correct id"); + } + + [Test] + public async Task CreatePost_ReturnsEmptyGuid_WhenItIsNotSuccessfullyCreated() + { + CreatePostServiceModel createPostServiceModel = new CreatePostServiceModel + { + Files = new List<IFormFile>() + }; + Post post = new Post + { + Message = MESSAGE, + }; + + this.PostRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Post>())).Returns(Task.FromResult(false)); + this.UserRepositoryMock.Setup(p => p.DoesUserExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.MapperMock.Setup(p => p.Map<Post>(It.IsAny<CreatePostServiceModel>())).Returns(post); + + Guid result = await this.PostService.CreatePost(createPostServiceModel); + + Assert.AreEqual(Guid.Empty, result, "CreatePost does not return empty id"); + } + + [Test] + public void CreatePost_ThrowsException_WhenUserDoesNotExist() + { + const string EXCEPTION_MESSAGE = "User does not exist!"; + CreatePostServiceModel createPostServiceModel = new CreatePostServiceModel + { + }; + Post post = new Post + { + Message = MESSAGE, + }; + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.PostService.CreatePost(createPostServiceModel), "CreatePost does not throw excpeion when the user does not exist"); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Excapetion message is not correct"); + } + #endregion + + #region GetPostById + [Test] + public async Task GetPostById_ReturnsThePost_WhenItExists() + { + Guid creatorId = new Guid(); + User creator = new User { Id = creatorId }; + Post post = new Post + { + Message = MESSAGE, + Creator = creator + }; + ReadPostServiceModel readPostServiceModel = new ReadPostServiceModel + { + Message = MESSAGE + }; + User user = new User + { + Id = creatorId, + }; + + this.PostRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(post)); + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(user)); + this.MapperMock.Setup(p => p.Map<ReadPostServiceModel>(It.IsAny<Post>())).Returns(readPostServiceModel); + + ReadPostServiceModel result = await this.PostService.GetPostById(new Guid()); + + Assert.AreEqual(MESSAGE, result.Message); + } + + [Test] + public void GetPostById_ThorwsException_WhenTheUserDoesNotExist() + { + const string EXCEPTION_MESSAGE = "The user does not exist!"; + Guid creatorId = new Guid(); + User creator = new User { Id = creatorId }; + Post post = new Post + { + Message = MESSAGE, + Creator = creator + }; + + this.PostRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(post)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.PostService.GetPostById(new Guid()), "GetPostById does not throw exception when the user does not exist"); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message); + } + + [Test] + public void GetPostById_ThrowsException_WhenCommentDoesNotExist() + { + string exceptionMessage = "The post does not exist!"; + Guid creatorId = new Guid(); + User user = new User + { + Id = creatorId, + }; + + this.PostRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult<Post>(null)); + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(user)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.PostService.GetPostById(new Guid())); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region UpdatePost + [Test] + public async Task UpdatePost_ReturnsTheIdOfThePost_WhenUpdatedSuccessfully() + { + Guid id = Guid.NewGuid(); + Post post = new Post + { + Id = id, + Message = MESSAGE + }; + UpdatePostServiceModel updatePostServiceModel = new UpdatePostServiceModel + { + PostId = id, + NewMessage = MESSAGE, + Files = new List<IFormFile>() + }; + + this.PostRepositoryMock.Setup(p => p.DoesPostExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.PostRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<Post>())).Returns(Task.FromResult(true)); + this.PostRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(post)); + this.MapperMock.Setup(p => p.Map<Post>(It.IsAny<UpdatePostServiceModel>())).Returns(post); + + Guid result = await this.PostService.UpdatePost(updatePostServiceModel); + + Assert.AreEqual(updatePostServiceModel.PostId, result); + } + + [Test] + public async Task UpdatePost_ReturnsEmptyId_WhenThePostIsNotUpdatedSuccessfully() + { + Post post = new Post + { + Message = MESSAGE + }; + UpdatePostServiceModel updatePostServiceModel = new UpdatePostServiceModel + { + PostId = Guid.NewGuid(), + NewMessage = MESSAGE, + Files = new List<IFormFile>() + }; + + this.PostRepositoryMock.Setup(p => p.DoesPostExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.PostRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<Post>())).Returns(Task.FromResult(false)); + this.MapperMock.Setup(p => p.Map<Post>(It.IsAny<UpdatePostServiceModel>())).Returns(post); + + Guid result = await this.PostService.UpdatePost(updatePostServiceModel); + + Assert.AreEqual(Guid.Empty, result); + } + + [Test] + public void UpdatePost_ThrowsArgumentException_WhenCommentDoesNotExist() + { + string exceptionMessage = "Post does not exist!"; + UpdatePostServiceModel updatePostServiceModel = new UpdatePostServiceModel + { + }; + + this.PostRepositoryMock.Setup(p => p.DoesPostExist(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.PostService.UpdatePost(updatePostServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region DeletePost + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Deletepost_ShouldReturnIfDeletionIsSuccessfull_WhenPostExists(bool shouldPass) + { + Guid id = new Guid(); + Post post = new Post(); + + this.PostRepositoryMock.Setup(p => p.DoesPostExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.PostRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(post)); + this.PostRepositoryMock.Setup(p => p.DeleteAsync(It.IsAny<Post>())).Returns(Task.FromResult(shouldPass)); + + bool result = await this.PostService.DeletePost(id); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void DeletePost_ThrowsException_WhenPostDoesNotExist() + { + string exceptionMessage = "Post does not exist!"; + Guid id = new Guid(); + + this.PostRepositoryMock.Setup(p => p.DoesPostExist(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.PostService.DeletePost(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services.Tests/RoleService.Tests.cs b/src/Services/DevHive.Services.Tests/RoleService.Tests.cs new file mode 100644 index 0000000..e500dd1 --- /dev/null +++ b/src/Services/DevHive.Services.Tests/RoleService.Tests.cs @@ -0,0 +1,230 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Models.Identity.Role; +using DevHive.Services.Services; +using Moq; +using NUnit.Framework; + +namespace DevHive.Services.Tests +{ + [TestFixture] + public class RoleServiceTests + { + private Mock<IRoleRepository> RoleRepositoryMock { get; set; } + private Mock<IMapper> MapperMock { get; set; } + private RoleService RoleService { get; set; } + + #region SetUps + [SetUp] + public void Setup() + { + this.RoleRepositoryMock = new Mock<IRoleRepository>(); + this.MapperMock = new Mock<IMapper>(); + this.RoleService = new RoleService(this.RoleRepositoryMock.Object, this.MapperMock.Object); + } + #endregion + + #region CreateRole + [Test] + public async Task CreateRole_ReturnsNonEmptyGuid_WhenEntityIsAddedSuccessfully() + { + string roleName = "Gosho Trapov"; + Guid id = Guid.NewGuid(); + CreateRoleServiceModel createRoleServiceModel = new CreateRoleServiceModel + { + Name = roleName + }; + Role role = new() + { + Name = roleName, + Id = id + }; + + this.RoleRepositoryMock.Setup(p => p.DoesNameExist(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.RoleRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Role>())).Returns(Task.FromResult(true)); + this.RoleRepositoryMock.Setup(p => p.GetByNameAsync(It.IsAny<string>())).Returns(Task.FromResult(role)); + this.MapperMock.Setup(p => p.Map<Role>(It.IsAny<CreateRoleServiceModel>())).Returns(role); + + Guid result = await this.RoleService.CreateRole(createRoleServiceModel); + + Assert.AreEqual(id, result); + } + + [Test] + public async Task CreateRoley_ReturnsEmptyGuid_WhenEntityIsNotAddedSuccessfully() + { + string roleName = "Gosho Trapov"; + + CreateRoleServiceModel createRoleServiceModel = new CreateRoleServiceModel + { + Name = roleName + }; + Role role = new Role + { + Name = roleName + }; + + this.RoleRepositoryMock.Setup(p => p.DoesNameExist(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.RoleRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Role>())).Returns(Task.FromResult(false)); + this.MapperMock.Setup(p => p.Map<Role>(It.IsAny<CreateRoleServiceModel>())).Returns(role); + + Guid result = await this.RoleService.CreateRole(createRoleServiceModel); + + Assert.IsTrue(result == Guid.Empty); + } + + [Test] + public void CreateTechnology_ThrowsArgumentException_WhenEntityAlreadyExists() + { + string exceptionMessage = "Role already exists!"; + string roleName = "Gosho Trapov"; + + CreateRoleServiceModel createRoleServiceModel = new CreateRoleServiceModel + { + Name = roleName + }; + Role role = new Role + { + Name = roleName + }; + + this.RoleRepositoryMock.Setup(p => p.DoesNameExist(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.RoleService.CreateRole(createRoleServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region GetRoleById + [Test] + public async Task GetRoleById_ReturnsTheRole_WhenItExists() + { + Guid id = new Guid(); + string name = "Gosho Trapov"; + Role role = new Role + { + Name = name + }; + RoleServiceModel roleServiceModel = new RoleServiceModel + { + Name = name + }; + + this.RoleRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(role)); + this.MapperMock.Setup(p => p.Map<RoleServiceModel>(It.IsAny<Role>())).Returns(roleServiceModel); + + RoleServiceModel result = await this.RoleService.GetRoleById(id); + + Assert.AreEqual(name, result.Name); + } + + [Test] + public void GetRoleById_ThrowsException_WhenRoleDoesNotExist() + { + string exceptionMessage = "Role does not exist!"; + Guid id = new Guid(); + this.RoleRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult<Role>(null)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.RoleService.GetRoleById(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region UpdateRole + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task UpdateRole_ReturnsIfUpdateIsSuccessfull_WhenRoleExistsy(bool shouldPass) + { + string name = "Gosho Trapov"; + Guid id = Guid.NewGuid(); + Role role = new Role + { + Name = name, + Id = id + }; + UpdateRoleServiceModel updateRoleServiceModel = new UpdateRoleServiceModel + { + Name = name, + }; + + this.RoleRepositoryMock.Setup(p => p.DoesRoleExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.RoleRepositoryMock.Setup(p => p.DoesNameExist(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.RoleRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<Role>())).Returns(Task.FromResult(shouldPass)); + this.MapperMock.Setup(p => p.Map<Role>(It.IsAny<UpdateRoleServiceModel>())).Returns(role); + + bool result = await this.RoleService.UpdateRole(updateRoleServiceModel); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void UpdateRole_ThrowsException_WhenRoleDoesNotExist() + { + string exceptionMessage = "Role does not exist!"; + UpdateRoleServiceModel updateRoleServiceModel = new UpdateRoleServiceModel + { + }; + + this.RoleRepositoryMock.Setup(p => p.DoesRoleExist(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.RoleService.UpdateRole(updateRoleServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + + [Test] + public void UpdateRole_ThrowsException_WhenRoleNameAlreadyExists() + { + string exceptionMessage = "Role name already exists!"; + UpdateRoleServiceModel updateRoleServiceModel = new UpdateRoleServiceModel + { + }; + + this.RoleRepositoryMock.Setup(p => p.DoesRoleExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.RoleRepositoryMock.Setup(p => p.DoesNameExist(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.RoleService.UpdateRole(updateRoleServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region DeleteRole + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task DeleteRole_ShouldReturnIfDeletionIsSuccessfull_WhenRoleExists(bool shouldPass) + { + Guid id = new Guid(); + Role role = new Role(); + + this.RoleRepositoryMock.Setup(p => p.DoesRoleExist(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.RoleRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(role)); + this.RoleRepositoryMock.Setup(p => p.DeleteAsync(It.IsAny<Role>())).Returns(Task.FromResult(shouldPass)); + + bool result = await this.RoleService.DeleteRole(id); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void DeleteRole_ThrowsException_WhenRoleDoesNotExist() + { + string exceptionMessage = "Role does not exist!"; + Guid id = new Guid(); + + this.RoleRepositoryMock.Setup(p => p.DoesRoleExist(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.RoleService.DeleteRole(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services.Tests/TechnologyServices.Tests.cs b/src/Services/DevHive.Services.Tests/TechnologyServices.Tests.cs new file mode 100644 index 0000000..e671adb --- /dev/null +++ b/src/Services/DevHive.Services.Tests/TechnologyServices.Tests.cs @@ -0,0 +1,262 @@ +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Models.Technology; +using DevHive.Services.Services; +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace DevHive.Services.Tests +{ + [TestFixture] + public class TechnologyServicesTests + { + private Mock<ITechnologyRepository> TechnologyRepositoryMock { get; set; } + private Mock<IMapper> MapperMock { get; set; } + private TechnologyService TechnologyService { get; set; } + + #region SetUps + [SetUp] + public void Setup() + { + this.TechnologyRepositoryMock = new Mock<ITechnologyRepository>(); + this.MapperMock = new Mock<IMapper>(); + this.TechnologyService = new TechnologyService(this.TechnologyRepositoryMock.Object, this.MapperMock.Object); + } + #endregion + + #region CreateTechnology + [Test] + public async Task CreateTechnology_ReturnsNonEmptyGuid_WhenEntityIsAddedSuccessfully() + { + string technologyName = "Gosho Trapov"; + Guid id = Guid.NewGuid(); + CreateTechnologyServiceModel createTechnologyServiceModel = new() + { + Name = technologyName + }; + Technology technology = new() + { + Name = technologyName, + Id = id + }; + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.TechnologyRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Technology>())).Returns(Task.FromResult(true)); + this.TechnologyRepositoryMock.Setup(p => p.GetByNameAsync(It.IsAny<string>())).Returns(Task.FromResult(technology)); + this.MapperMock.Setup(p => p.Map<Technology>(It.IsAny<CreateTechnologyServiceModel>())).Returns(technology); + + Guid result = await this.TechnologyService.CreateTechnology(createTechnologyServiceModel); + + Assert.AreEqual(id, result); + } + + [Test] + public async Task CreateTechnology_ReturnsEmptyGuid_WhenEntityIsNotAddedSuccessfully() + { + string technologyName = "Gosho Trapov"; + + CreateTechnologyServiceModel createTechnologyServiceModel = new() + { + Name = technologyName + }; + Technology technology = new() + { + Name = technologyName + }; + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.TechnologyRepositoryMock.Setup(p => p.AddAsync(It.IsAny<Technology>())).Returns(Task.FromResult(false)); + this.MapperMock.Setup(p => p.Map<Technology>(It.IsAny<CreateTechnologyServiceModel>())).Returns(technology); + + Guid result = await this.TechnologyService.CreateTechnology(createTechnologyServiceModel); + + Assert.IsTrue(result == Guid.Empty); + } + + [Test] + public void CreateTechnology_ThrowsArgumentException_WhenEntityAlreadyExists() + { + string exceptionMessage = "Technology already exists!"; + string technologyName = "Gosho Trapov"; + + CreateTechnologyServiceModel createTechnologyServiceModel = new() + { + Name = technologyName + }; + Technology technology = new() + { + Name = technologyName + }; + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.TechnologyService.CreateTechnology(createTechnologyServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region GetTechnologyById + [Test] + public async Task GetTechnologyById_ReturnsTheTechnology_WhenItExists() + { + Guid id = new Guid(); + string name = "Gosho Trapov"; + Technology technology = new() + { + Name = name + }; + ReadTechnologyServiceModel readTechnologyServiceModel = new() + { + Name = name + }; + + this.TechnologyRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(technology)); + this.MapperMock.Setup(p => p.Map<ReadTechnologyServiceModel>(It.IsAny<Technology>())).Returns(readTechnologyServiceModel); + + ReadTechnologyServiceModel result = await this.TechnologyService.GetTechnologyById(id); + + Assert.AreEqual(name, result.Name); + } + + [Test] + public void GetTechnologyById_ThrowsException_WhenTechnologyDoesNotExist() + { + string exceptionMessage = "The technology does not exist"; + Guid id = new Guid(); + this.TechnologyRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult<Technology>(null)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.TechnologyService.GetTechnologyById(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region GetTechnologies + [Test] + public void GetTechnologies_ReturnsAllLanguages_IfAnyExist() + { + ReadTechnologyServiceModel firstTechnology = new ReadTechnologyServiceModel(); + ReadTechnologyServiceModel secondTechnology = new ReadTechnologyServiceModel(); + HashSet<ReadTechnologyServiceModel> technologies = new HashSet<ReadTechnologyServiceModel>(); + technologies.Add(firstTechnology); + technologies.Add(secondTechnology); + + this.TechnologyRepositoryMock.Setup(p => p.GetTechnologies()).Returns(new HashSet<Technology>()); + this.MapperMock.Setup(p => p.Map<HashSet<ReadTechnologyServiceModel>>(It.IsAny<HashSet<Technology>>())).Returns(technologies); + + HashSet<ReadTechnologyServiceModel> result = this.TechnologyService.GetTechnologies(); + + Assert.GreaterOrEqual(2, result.Count, "GetTechnologies does not return all technologies"); + } + + [Test] + public void GetLanguages_ReturnsEmptyHashSet_IfNoLanguagesExist() + { + this.TechnologyRepositoryMock.Setup(p => p.GetTechnologies()).Returns(new HashSet<Technology>()); + this.MapperMock.Setup(p => p.Map<HashSet<ReadTechnologyServiceModel>>(It.IsAny<HashSet<Technology>>())).Returns(new HashSet<ReadTechnologyServiceModel>()); + + HashSet<ReadTechnologyServiceModel> result = this.TechnologyService.GetTechnologies(); + + Assert.IsEmpty(result, "GetTechnologies does not return empty string when no technologies exist"); + } + #endregion + + #region UpdateTechnology + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task UpdateTechnology_ReturnsIfUpdateIsSuccessfull_WhenTechnologyExistsy(bool shouldPass) + { + string name = "Gosho Trapov"; + Guid id = Guid.NewGuid(); + Technology technology = new Technology + { + Name = name, + Id = id + }; + UpdateTechnologyServiceModel updatetechnologyServiceModel = new UpdateTechnologyServiceModel + { + Name = name, + }; + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.TechnologyRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<Technology>())).Returns(Task.FromResult(shouldPass)); + this.MapperMock.Setup(p => p.Map<Technology>(It.IsAny<UpdateTechnologyServiceModel>())).Returns(technology); + + bool result = await this.TechnologyService.UpdateTechnology(updatetechnologyServiceModel); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void UpdateTechnology_ThrowsException_WhenTechnologyDoesNotExist() + { + string exceptionMessage = "Technology does not exist!"; + UpdateTechnologyServiceModel updateTechnologyServiceModel = new UpdateTechnologyServiceModel + { + }; + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.TechnologyService.UpdateTechnology(updateTechnologyServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + + [Test] + public void UpdateTechnology_ThrowsException_WhenTechnologyNameAlreadyExists() + { + string exceptionMessage = "Technology name already exists!"; + UpdateTechnologyServiceModel updateTechnologyServiceModel = new UpdateTechnologyServiceModel + { + }; + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyNameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.TechnologyService.UpdateTechnology(updateTechnologyServiceModel)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region DeleteTechnology + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task DeleteTechnology_ShouldReturnIfDeletionIsSuccessfull_WhenTechnologyExists(bool shouldPass) + { + Guid id = new Guid(); + Technology technology = new Technology(); + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.TechnologyRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(technology)); + this.TechnologyRepositoryMock.Setup(p => p.DeleteAsync(It.IsAny<Technology>())).Returns(Task.FromResult(shouldPass)); + + bool result = await this.TechnologyService.DeleteTechnology(id); + + Assert.AreEqual(shouldPass, result); + } + + [Test] + public void DeleteTechnology_ThrowsException_WhenTechnologyDoesNotExist() + { + string exceptionMessage = "Technology does not exist!"; + Guid id = new Guid(); + + this.TechnologyRepositoryMock.Setup(p => p.DoesTechnologyExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.TechnologyService.DeleteTechnology(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services.Tests/UserService.Tests.cs b/src/Services/DevHive.Services.Tests/UserService.Tests.cs new file mode 100644 index 0000000..21d4862 --- /dev/null +++ b/src/Services/DevHive.Services.Tests/UserService.Tests.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Models.Identity; +using DevHive.Common.Models.Misc; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Identity.User; +using DevHive.Services.Options; +using DevHive.Services.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; +using Moq; +using NUnit.Framework; + +namespace DevHive.Services.Tests +{ + [TestFixture] + public class UserServiceTests + { + private Mock<ICloudService> CloudServiceMock { get; set; } + private Mock<IUserRepository> UserRepositoryMock { get; set; } + private Mock<IRoleRepository> RoleRepositoryMock { get; set; } + private Mock<ILanguageRepository> LanguageRepositoryMock { get; set; } + private Mock<ITechnologyRepository> TechnologyRepositoryMock { get; set; } + private Mock<IMapper> MapperMock { get; set; } + private JWTOptions JWTOptions { get; set; } + private UserService UserService { get; set; } + + #region SetUps + [SetUp] + public void Setup() + { + this.UserRepositoryMock = new Mock<IUserRepository>(); + this.RoleRepositoryMock = new Mock<IRoleRepository>(); + this.CloudServiceMock = new Mock<ICloudService>(); + this.LanguageRepositoryMock = new Mock<ILanguageRepository>(); + this.TechnologyRepositoryMock = new Mock<ITechnologyRepository>(); + this.JWTOptions = new JWTOptions("gXfQlU6qpDleFWyimscjYcT3tgFsQg3yoFjcvSLxG56n1Vu2yptdIUq254wlJWjm"); + this.MapperMock = new Mock<IMapper>(); + // TODO: give actual UserManager and RoleManager to UserService + this.UserService = new UserService(this.UserRepositoryMock.Object, this.LanguageRepositoryMock.Object, this.RoleRepositoryMock.Object, this.TechnologyRepositoryMock.Object, null, null, this.MapperMock.Object, this.JWTOptions, this.CloudServiceMock.Object); + } + #endregion + + #region LoginUser + [Test] + public async Task LoginUser_ReturnsTokenModel_WhenLoggingUserIn() + { + string somePassword = "GoshoTrapovImaGolemChep"; + const string name = "GoshoTrapov"; + string hashedPassword = PasswordModifications.GeneratePasswordHash(somePassword); + LoginServiceModel loginServiceModel = new LoginServiceModel + { + Password = somePassword + }; + User user = new User + { + Id = Guid.NewGuid(), + PasswordHash = hashedPassword, + UserName = name + }; + + this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + this.UserRepositoryMock.Setup(p => p.GetByUsernameAsync(It.IsAny<string>())).Returns(Task.FromResult(user)); + + string JWTSecurityToken = this.WriteJWTSecurityToken(user.Id, user.UserName, user.Roles); + + TokenModel tokenModel = await this.UserService.LoginUser(loginServiceModel); + + Assert.AreEqual(JWTSecurityToken, tokenModel.Token, "LoginUser does not return the correct token"); + } + + [Test] + public void LoginUser_ThrowsException_WhenUserNameDoesNotExist() + { + const string EXCEPTION_MESSAGE = "Invalid username!"; + LoginServiceModel loginServiceModel = new LoginServiceModel + { + }; + + this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.LoginUser(loginServiceModel)); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorect Exception message"); + } + + [Test] + public void LoginUser_ThroiwsException_WhenPasswordIsIncorect() + { + const string EXCEPTION_MESSAGE = "Incorrect password!"; + string somePassword = "GoshoTrapovImaGolemChep"; + LoginServiceModel loginServiceModel = new LoginServiceModel + { + Password = somePassword + }; + User user = new User + { + Id = Guid.NewGuid(), + PasswordHash = "InvalidPasswordHas" + }; + + this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + this.UserRepositoryMock.Setup(p => p.GetByUsernameAsync(It.IsAny<string>())).Returns(Task.FromResult(user)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.LoginUser(loginServiceModel)); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorect Exception message"); + } + #endregion + + #region RegisterUser + // [Test] + // public async Task RegisterUser_ReturnsTokenModel_WhenUserIsSuccessfull() + // { + // string somePassword = "GoshoTrapovImaGolemChep"; + // const string name = "GoshoTrapov"; + // RegisterServiceModel registerServiceModel = new RegisterServiceModel + // { + // Password = somePassword + // }; + // User user = new User + // { + // Id = Guid.NewGuid(), + // UserName = name + // }; + // Role role = new Role { Name = Role.DefaultRole }; + // HashSet<Role> roles = new HashSet<Role> { role }; + // + // this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + // this.UserRepositoryMock.Setup(p => p.DoesEmailExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + // this.RoleRepositoryMock.Setup(p => p.DoesNameExist(It.IsAny<string>())).Returns(Task.FromResult(true)); + // this.RoleRepositoryMock.Setup(p => p.GetByNameAsync(It.IsAny<string>())).Returns(Task.FromResult(role)); + // this.MapperMock.Setup(p => p.Map<User>(It.IsAny<RegisterServiceModel>())).Returns(user); + // this.UserRepositoryMock.Setup(p => p.AddAsync(It.IsAny<User>())).Verifiable(); + // + // string JWTSecurityToken = this.WriteJWTSecurityToken(user.Id, user.UserName, roles); + // + // TokenModel tokenModel = await this.UserService.RegisterUser(registerServiceModel); + // + // Mock.Verify(); + // Assert.AreEqual(JWTSecurityToken, tokenModel.Token, "RegisterUser does not return the correct token"); + // } + + [Test] + public void RegisterUser_ThrowsException_WhenUsernameAlreadyExists() + { + const string EXCEPTION_MESSAGE = "Username already exists!"; + RegisterServiceModel registerServiceModel = new RegisterServiceModel + { + }; + + this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.RegisterUser(registerServiceModel)); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorect Exception message"); + } + + [Test] + public void RegisterUser_ThrowsException_WhenEmailAlreadyExists() + { + const string EXCEPTION_MESSAGE = "Email already exists!"; + + RegisterServiceModel registerServiceModel = new RegisterServiceModel + { + }; + + this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + this.UserRepositoryMock.Setup(p => p.DoesEmailExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.RegisterUser(registerServiceModel)); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorect Exception message"); + } + #endregion + + #region GetUserById + [Test] + public async Task GetUserById_ReturnsTheUser_WhenItExists() + { + Guid id = new Guid(); + string name = "Gosho Trapov"; + User user = new() + { + }; + UserServiceModel userServiceModel = new UserServiceModel + { + UserName = name + }; + + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(user)); + this.MapperMock.Setup(p => p.Map<UserServiceModel>(It.IsAny<User>())).Returns(userServiceModel); + + UserServiceModel result = await this.UserService.GetUserById(id); + + Assert.AreEqual(name, result.UserName); + } + + [Test] + public void GetTechnologyById_ThrowsException_WhenTechnologyDoesNotExist() + { + const string EXCEPTION_MESSEGE = "User does not exist!"; + Guid id = new Guid(); + this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult<User>(null)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.GetUserById(id)); + + Assert.AreEqual(EXCEPTION_MESSEGE, ex.Message, "Incorecct exception message"); + } + #endregion + + #region GetUserByUsername + [Test] + public async Task GetUserByUsername_ReturnsTheCorrectUser_IfItExists() + { + string username = "Gosho Trapov"; + User user = new User(); + UserServiceModel userServiceModel = new UserServiceModel + { + UserName = username + }; + + this.UserRepositoryMock.Setup(p => p.GetByUsernameAsync(It.IsAny<string>())).Returns(Task.FromResult(user)); + this.MapperMock.Setup(p => p.Map<UserServiceModel>(It.IsAny<User>())).Returns(userServiceModel); + + UserServiceModel result = await this.UserService.GetUserByUsername(username); + + Assert.AreEqual(username, result.UserName, "GetUserByUsername does not return the correct user"); + } + + [Test] + public async Task GetUserByUsername_ThrowsException_IfUserDoesNotExist() + { + string username = "Gosho Trapov"; + const string EXCEPTION_MESSEGE = "User does not exist!"; + + this.UserRepositoryMock.Setup(p => p.GetByUsernameAsync(It.IsAny<string>())).Returns(Task.FromResult<User>(null)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.GetUserByUsername(username)); + + Assert.AreEqual(EXCEPTION_MESSEGE, ex.Message, "Incorecct exception message"); + } + #endregion + + #region UpdateUser + // [Test] + // [TestCase(true)] + // [TestCase(false)] + // public async Task UpdateUser_ReturnsIfUpdateIsSuccessfull_WhenUserExistsy(bool shouldPass) + // { + // string name = "Gosho Trapov"; + // Guid id = Guid.NewGuid(); + // User user = new User + // { + // UserName = name, + // Id = id, + // }; + // UpdateUserServiceModel updateUserServiceModel = new UpdateUserServiceModel + // { + // UserName = name, + // }; + // UserServiceModel userServiceModel = new UserServiceModel + // { + // UserName = name, + // }; + // Role role = new Role { }; + // + // this.UserRepositoryMock.Setup(p => p.DoesUserExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + // this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(false)); + // this.UserRepositoryMock.Setup(p => p.DoesUserHaveThisUsername(It.IsAny<Guid>(), It.IsAny<string>())).Returns(true); + // this.UserRepositoryMock.Setup(p => p.EditAsync(It.IsAny<Guid>(), It.IsAny<User>())).Returns(Task.FromResult(shouldPass)); + // this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(user)); + // this.MapperMock.Setup(p => p.Map<User>(It.IsAny<UpdateUserServiceModel>())).Returns(user); + // this.MapperMock.Setup(p => p.Map<UserServiceModel>(It.IsAny<User>())).Returns(userServiceModel); + // + // if (shouldPass) + // { + // UserServiceModel result = await this.UserService.UpdateUser(updateUserServiceModel); + // + // Assert.AreEqual(updateUserServiceModel.UserName, result.UserName); + // } + // else + // { + // const string EXCEPTION_MESSAGE = "Unable to edit user!"; + // + // Exception ex = Assert.ThrowsAsync<InvalidOperationException>(() => this.UserService.UpdateUser(updateUserServiceModel)); + // + // Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorecct exception message"); + // } + // } + + [Test] + public void UpdateUser_ThrowsException_WhenUserDoesNotExist() + { + const string EXCEPTION_MESSAGE = "User does not exist!"; + UpdateUserServiceModel updateUserServiceModel = new UpdateUserServiceModel + { + }; + + this.UserRepositoryMock.Setup(p => p.DoesUserExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.UpdateUser(updateUserServiceModel)); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorecct exception message"); + } + + [Test] + public void UpdateUser_ThrowsException_WhenUserNameAlreadyExists() + { + const string EXCEPTION_MESSAGE = "Username already exists!"; + UpdateUserServiceModel updateUserServiceModel = new UpdateUserServiceModel + { + }; + + this.UserRepositoryMock.Setup(p => p.DoesUserExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + this.UserRepositoryMock.Setup(p => p.DoesUsernameExistAsync(It.IsAny<string>())).Returns(Task.FromResult(true)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.UpdateUser(updateUserServiceModel)); + + Assert.AreEqual(EXCEPTION_MESSAGE, ex.Message, "Incorecct exception message"); + } + #endregion + + #region DeleteUser + //TO DO: compleate once Viko has looked into the return type of UserService.DeleteUser + // [Test] + // [TestCase(true)] + // [TestCase(false)] + // public async Task DeleteUser_ShouldReturnIfDeletionIsSuccessfull_WhenUserExists(bool shouldPass) + // { + // Guid id = new Guid(); + // User user = new User(); + // + // this.UserRepositoryMock.Setup(p => p.DoesUserExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + // this.UserRepositoryMock.Setup(p => p.GetByIdAsync(It.IsAny<Guid>())).Returns(Task.FromResult(user)); + // this.UserRepositoryMock.Setup(p => p.DeleteAsync(It.IsAny<User>())).Returns(Task.FromResult(shouldPass)); + // + // bool result = await this.UserService.DeleteUser(id); + // + // Assert.AreEqual(shouldPass, result); + // } + // + [Test] + public void DeleteUser_ThrowsException_WhenUserDoesNotExist() + { + string exceptionMessage = "User does not exist!"; + Guid id = new Guid(); + + this.UserRepositoryMock.Setup(p => p.DoesUserExistAsync(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + Exception ex = Assert.ThrowsAsync<ArgumentException>(() => this.UserService.DeleteUser(id)); + + Assert.AreEqual(exceptionMessage, ex.Message, "Incorecct exception message"); + } + #endregion + + #region HelperMethods + private string WriteJWTSecurityToken(Guid userId, string username, HashSet<Role> roles) + { + byte[] signingKey = Encoding.ASCII.GetBytes(this.JWTOptions.Secret); + HashSet<Claim> claims = new() + { + new Claim("ID", $"{userId}"), + new Claim("Username", username), + }; + + foreach (var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role.Name)); + } + + SecurityTokenDescriptor tokenDescriptor = new() + { + Subject = new ClaimsIdentity(claims), + Expires = DateTime.Today.AddDays(7), + SigningCredentials = new SigningCredentials( + new SymmetricSecurityKey(signingKey), + SecurityAlgorithms.HmacSha512Signature) + }; + + JwtSecurityTokenHandler tokenHandler = new(); + SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/CommentMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/CommentMappings.cs new file mode 100644 index 0000000..5ca2a9e --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/CommentMappings.cs @@ -0,0 +1,27 @@ +using DevHive.Data.Models; +using AutoMapper; +using DevHive.Services.Models.Comment; +using DevHive.Common.Models.Misc; + +namespace DevHive.Services.Configurations.Mapping +{ + public class CommentMappings : Profile + { + public CommentMappings() + { + CreateMap<CreateCommentServiceModel, Comment>(); + CreateMap<UpdateCommentServiceModel, Comment>() + .ForMember(dest => dest.Id, src => src.MapFrom(p => p.CommentId)) + .ForMember(dest => dest.Message, src => src.MapFrom(p => p.NewMessage)); + + CreateMap<Comment, ReadCommentServiceModel>() + .ForMember(dest => dest.CommentId, src => src.MapFrom(p => p.Id)) + .ForMember(dest => dest.IssuerFirstName, src => src.MapFrom(p => p.Creator.FirstName)) + .ForMember(dest => dest.IssuerLastName, src => src.MapFrom(p => p.Creator.LastName)) + .ForMember(dest => dest.IssuerUsername, src => src.MapFrom(p => p.Creator.UserName)); + + CreateMap<Comment, IdModel>() + .ForMember(dest => dest.Id, src => src.MapFrom(p => p.Id)); + } + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/FeedMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/FeedMappings.cs new file mode 100644 index 0000000..952e480 --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/FeedMappings.cs @@ -0,0 +1,11 @@ +using AutoMapper; + +namespace DevHive.Services.Configurations.Mapping +{ + public class FeedMappings : Profile + { + public FeedMappings() + { + } + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/LanguageMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/LanguageMappings.cs new file mode 100644 index 0000000..9c572df --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/LanguageMappings.cs @@ -0,0 +1,22 @@ +using DevHive.Data.Models; +using AutoMapper; +using DevHive.Services.Models.Language; + +namespace DevHive.Services.Configurations.Mapping +{ + public class LanguageMappings : Profile + { + public LanguageMappings() + { + CreateMap<LanguageServiceModel, Language>(); + CreateMap<ReadLanguageServiceModel, Language>(); + CreateMap<CreateLanguageServiceModel, Language>(); + CreateMap<UpdateLanguageServiceModel, Language>(); + + CreateMap<Language, LanguageServiceModel>(); + CreateMap<Language, ReadLanguageServiceModel>(); + CreateMap<Language, CreateLanguageServiceModel>(); + CreateMap<Language, UpdateLanguageServiceModel>(); + } + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/PostMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/PostMappings.cs new file mode 100644 index 0000000..9362f90 --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/PostMappings.cs @@ -0,0 +1,33 @@ +using DevHive.Data.Models; +using AutoMapper; +using DevHive.Services.Models.Post; +using DevHive.Common.Models.Misc; +using System.Collections.Generic; +using DevHive.Services.Models; + +namespace DevHive.Services.Configurations.Mapping +{ + public class PostMappings : Profile + { + public PostMappings() + { + CreateMap<CreatePostServiceModel, Post>(); + // .ForMember(dest => dest.Files, src => src.Ignore()); + CreateMap<UpdatePostServiceModel, Post>() + .ForMember(dest => dest.Id, src => src.MapFrom(p => p.PostId)) + // .ForMember(dest => dest.Files, src => src.Ignore()) + .ForMember(dest => dest.Message, src => src.MapFrom(p => p.NewMessage)); + + CreateMap<Post, ReadPostServiceModel>() + .ForMember(dest => dest.PostId, src => src.MapFrom(p => p.Id)) + .ForMember(dest => dest.CreatorFirstName, src => src.MapFrom(p => p.Creator.FirstName)) + .ForMember(dest => dest.CreatorLastName, src => src.MapFrom(p => p.Creator.LastName)) + .ForMember(dest => dest.CreatorUsername, src => src.MapFrom(p => p.Creator.UserName)); + + CreateMap<Post, IdModel>() + .ForMember(dest => dest.Id, src => src.MapFrom(x => x.Id)); + + CreateMap<List<Post>, ReadPageServiceModel>(); + } + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs new file mode 100644 index 0000000..1dbb7b4 --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using DevHive.Data.Models; +using DevHive.Services.Models.Post.Rating; + +namespace DevHive.Services.Configurations.Mapping +{ + public class RatingMappings : Profile + { + public RatingMappings() + { + // CreateMap<Rating, ReadPostRatingServiceModel>() + // .ForMember(dest => dest.PostId, src => src.MapFrom(p => p.Post.Id)); + } + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/RoleMapings.cs b/src/Services/DevHive.Services/Configurations/Mapping/RoleMapings.cs new file mode 100644 index 0000000..e61a107 --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/RoleMapings.cs @@ -0,0 +1,19 @@ +using DevHive.Data.Models; +using AutoMapper; +using DevHive.Services.Models.Identity.Role; + +namespace DevHive.Services.Configurations.Mapping +{ + public class RoleMappings : Profile + { + public RoleMappings() + { + CreateMap<CreateRoleServiceModel, Role>(); + CreateMap<RoleServiceModel, Role>(); + CreateMap<UpdateRoleServiceModel, Role>(); + + CreateMap<Role, RoleServiceModel>(); + CreateMap<Role, UpdateRoleServiceModel>(); + } + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/TechnologyMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/TechnologyMappings.cs new file mode 100644 index 0000000..85b57f1 --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/TechnologyMappings.cs @@ -0,0 +1,21 @@ +using DevHive.Data.Models; +using AutoMapper; +using DevHive.Services.Models.Technology; + +namespace DevHive.Services.Configurations.Mapping +{ + public class TechnologyMappings : Profile + { + public TechnologyMappings() + { + CreateMap<CreateTechnologyServiceModel, Technology>(); + CreateMap<UpdateTechnologyServiceModel, Technology>(); + CreateMap<TechnologyServiceModel, Technology>(); + + CreateMap<Technology, CreateTechnologyServiceModel>(); + CreateMap<Technology, TechnologyServiceModel>(); + CreateMap<Technology, ReadTechnologyServiceModel>(); + CreateMap<Technology, UpdateTechnologyServiceModel>(); + } + } +} diff --git a/src/Services/DevHive.Services/Configurations/Mapping/UserMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/UserMappings.cs new file mode 100644 index 0000000..2b0f4ed --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/UserMappings.cs @@ -0,0 +1,31 @@ +using DevHive.Data.Models; +using AutoMapper; +using DevHive.Services.Models.Identity.User; +using DevHive.Common.Models.Misc; +using DevHive.Data.RelationModels; + +namespace DevHive.Services.Configurations.Mapping +{ + public class UserMappings : Profile + { + public UserMappings() + { + CreateMap<UserServiceModel, User>(); + CreateMap<RegisterServiceModel, User>(); + CreateMap<FriendServiceModel, User>() + .ForMember(dest => dest.Friends, src => src.Ignore()); + CreateMap<UpdateUserServiceModel, User>() + .ForMember(dest => dest.Friends, src => src.Ignore()) + .AfterMap((src, dest) => dest.PasswordHash = PasswordModifications.GeneratePasswordHash(src.Password)); + CreateMap<UpdateFriendServiceModel, User>(); + + CreateMap<User, UserServiceModel>() + .ForMember(dest => dest.ProfilePictureURL, src => src.MapFrom(p => p.ProfilePicture.PictureURL)) + .ForMember(dest => dest.Friends, src => src.MapFrom(p => p.Friends)); + CreateMap<User, UpdateUserServiceModel>() + .ForMember(x => x.Password, opt => opt.Ignore()) + .ForMember(dest => dest.ProfilePictureURL, src => src.MapFrom(p => p.ProfilePicture.PictureURL)); + CreateMap<User, FriendServiceModel>(); + } + } +} diff --git a/src/Services/DevHive.Services/DevHive.Services.csproj b/src/Services/DevHive.Services/DevHive.Services.csproj new file mode 100644 index 0000000..7a33166 --- /dev/null +++ b/src/Services/DevHive.Services/DevHive.Services.csproj @@ -0,0 +1,31 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" /> + <ProjectReference Include="..\DevHive.Data\DevHive.Data.csproj" /> + + <PackageReference Include="AutoMapper" Version="10.1.1" /> + <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" /> + + <PackageReference Include="CloudinaryDotNet" Version="1.14.0" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\DevHive.Common\DevHive.Common.csproj" /> + <ProjectReference Include="..\DevHive.Services.Models\DevHive.Services.Models.csproj" /> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.csproj" /> + </ItemGroup> + + <PropertyGroup> + <EnableNETAnalyzers>true</EnableNETAnalyzers> + <AnalysisLevel>latest</AnalysisLevel> + </PropertyGroup> +</Project> diff --git a/src/Services/DevHive.Services/Interfaces/ICloudService.cs b/src/Services/DevHive.Services/Interfaces/ICloudService.cs new file mode 100644 index 0000000..3ae7a24 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/ICloudService.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DevHive.Services.Interfaces +{ + public interface ICloudService + { + Task<List<string>> UploadFilesToCloud(List<IFormFile> formFiles); + + // Task<List<FileContentResult>> GetFilesFromCloud(List<string> fileUrls); + + Task<bool> RemoveFilesFromCloud(List<string> fileUrls); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/ICommentService.cs b/src/Services/DevHive.Services/Interfaces/ICommentService.cs new file mode 100644 index 0000000..e7409a8 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/ICommentService.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using DevHive.Services.Models.Comment; + +namespace DevHive.Services.Interfaces +{ + public interface ICommentService + { + Task<Guid> AddComment(CreateCommentServiceModel createPostServiceModel); + + Task<ReadCommentServiceModel> GetCommentById(Guid id); + + Task<Guid> UpdateComment(UpdateCommentServiceModel updateCommentServiceModel); + + Task<bool> DeleteComment(Guid id); + + Task<bool> ValidateJwtForCreating(Guid userId, string rawTokenData); + Task<bool> ValidateJwtForComment(Guid commentId, string rawTokenData); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IFeedService.cs b/src/Services/DevHive.Services/Interfaces/IFeedService.cs new file mode 100644 index 0000000..b507b3b --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IFeedService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using DevHive.Services.Models; + +namespace DevHive.Services.Interfaces +{ + public interface IFeedService + { + Task<ReadPageServiceModel> GetPage(GetPageServiceModel getPageServiceModel); + Task<ReadPageServiceModel> GetUserPage(GetPageServiceModel model); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/ILanguageService.cs b/src/Services/DevHive.Services/Interfaces/ILanguageService.cs new file mode 100644 index 0000000..fabbec2 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/ILanguageService.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DevHive.Services.Models.Language; + +namespace DevHive.Services.Interfaces +{ + public interface ILanguageService + { + Task<Guid> CreateLanguage(CreateLanguageServiceModel createLanguageServiceModel); + + Task<ReadLanguageServiceModel> GetLanguageById(Guid id); + HashSet<ReadLanguageServiceModel> GetLanguages(); + + Task<bool> UpdateLanguage(UpdateLanguageServiceModel languageServiceModel); + + Task<bool> DeleteLanguage(Guid id); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IPostService.cs b/src/Services/DevHive.Services/Interfaces/IPostService.cs new file mode 100644 index 0000000..d35acfd --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IPostService.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using DevHive.Services.Models.Post; + +namespace DevHive.Services.Interfaces +{ + public interface IPostService + { + Task<Guid> CreatePost(CreatePostServiceModel createPostServiceModel); + + Task<ReadPostServiceModel> GetPostById(Guid id); + + Task<Guid> UpdatePost(UpdatePostServiceModel updatePostServiceModel); + + Task<bool> DeletePost(Guid id); + + Task<bool> ValidateJwtForCreating(Guid userId, string rawTokenData); + Task<bool> ValidateJwtForPost(Guid postId, string rawTokenData); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IRateService.cs b/src/Services/DevHive.Services/Interfaces/IRateService.cs new file mode 100644 index 0000000..359ef55 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IRateService.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using DevHive.Data.Models; +using DevHive.Services.Models.Post.Rating; + +namespace DevHive.Services.Interfaces +{ + public interface IRateService + { + Task<ReadPostRatingServiceModel> RatePost(RatePostServiceModel ratePostServiceModel); + + bool HasUserRatedThisPost(User user, Post post); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IRoleService.cs b/src/Services/DevHive.Services/Interfaces/IRoleService.cs new file mode 100644 index 0000000..d47728c --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IRoleService.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using DevHive.Services.Models.Identity.Role; + +namespace DevHive.Services.Interfaces +{ + public interface IRoleService + { + Task<Guid> CreateRole(CreateRoleServiceModel roleServiceModel); + + Task<RoleServiceModel> GetRoleById(Guid id); + + Task<bool> UpdateRole(UpdateRoleServiceModel roleServiceModel); + + Task<bool> DeleteRole(Guid id); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/ITechnologyService.cs b/src/Services/DevHive.Services/Interfaces/ITechnologyService.cs new file mode 100644 index 0000000..8f9510c --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/ITechnologyService.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DevHive.Services.Models.Technology; + +namespace DevHive.Services.Interfaces +{ + public interface ITechnologyService + { + Task<Guid> CreateTechnology(CreateTechnologyServiceModel technologyServiceModel); + + Task<ReadTechnologyServiceModel> GetTechnologyById(Guid id); + HashSet<ReadTechnologyServiceModel> GetTechnologies(); + + Task<bool> UpdateTechnology(UpdateTechnologyServiceModel updateTechnologyServiceModel); + + Task<bool> DeleteTechnology(Guid id); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IUserService.cs b/src/Services/DevHive.Services/Interfaces/IUserService.cs new file mode 100644 index 0000000..9e2b4e3 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IUserService.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using DevHive.Common.Models.Identity; +using DevHive.Services.Models.Identity.User; + +namespace DevHive.Services.Interfaces +{ + public interface IUserService + { + Task<TokenModel> LoginUser(LoginServiceModel loginModel); + Task<TokenModel> RegisterUser(RegisterServiceModel registerModel); + + Task<UserServiceModel> GetUserByUsername(string username); + Task<UserServiceModel> GetUserById(Guid id); + + Task<UserServiceModel> UpdateUser(UpdateUserServiceModel updateModel); + Task<ProfilePictureServiceModel> UpdateProfilePicture(UpdateProfilePictureServiceModel updateProfilePictureServiceModel); + + Task<bool> DeleteUser(Guid id); + + Task<bool> ValidJWT(Guid id, string rawTokenData); + + Task<TokenModel> SuperSecretPromotionToAdmin(Guid userId); + } +} diff --git a/src/Services/DevHive.Services/Options/JWTOptions.cs b/src/Services/DevHive.Services/Options/JWTOptions.cs new file mode 100644 index 0000000..95458f5 --- /dev/null +++ b/src/Services/DevHive.Services/Options/JWTOptions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Options; + +namespace DevHive.Services.Options +{ + public class JWTOptions + { + public JWTOptions(string secret) + { + this.Secret = secret; + } + + public string Secret { get; init; } + } +} diff --git a/src/Services/DevHive.Services/Services/CloudinaryService.cs b/src/Services/DevHive.Services/Services/CloudinaryService.cs new file mode 100644 index 0000000..57955a2 --- /dev/null +++ b/src/Services/DevHive.Services/Services/CloudinaryService.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using CloudinaryDotNet; +using CloudinaryDotNet.Actions; +using DevHive.Services.Interfaces; +using Microsoft.AspNetCore.Http; + +namespace DevHive.Services.Services +{ + public class CloudinaryService : ICloudService + { + private readonly Cloudinary _cloudinary; + + public CloudinaryService(string cloudName, string apiKey, string apiSecret) + { + this._cloudinary = new Cloudinary(new Account(cloudName, apiKey, apiSecret)); + } + + public async Task<List<string>> UploadFilesToCloud(List<IFormFile> formFiles) + { + List<string> fileUrls = new(); + foreach (var formFile in formFiles) + { + string formFileId = Guid.NewGuid().ToString(); + + using (var ms = new MemoryStream()) + { + formFile.CopyTo(ms); + byte[] formBytes = ms.ToArray(); + + RawUploadParams rawUploadParams = new() + { + File = new FileDescription(formFileId, new MemoryStream(formBytes)), + PublicId = formFileId, + UseFilename = true + }; + + RawUploadResult rawUploadResult = await this._cloudinary.UploadAsync(rawUploadParams); + fileUrls.Add(rawUploadResult.Url.AbsoluteUri); + } + } + + return fileUrls; + } + + public async Task<bool> RemoveFilesFromCloud(List<string> fileUrls) + { + return true; + } + } +} diff --git a/src/Services/DevHive.Services/Services/CommentService.cs b/src/Services/DevHive.Services/Services/CommentService.cs new file mode 100644 index 0000000..e2b54c4 --- /dev/null +++ b/src/Services/DevHive.Services/Services/CommentService.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Models; +using DevHive.Services.Models.Comment; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using DevHive.Services.Interfaces; +using DevHive.Data.Interfaces.Repositories; +using System.Linq; + +namespace DevHive.Services.Services +{ + public class CommentService : ICommentService + { + private readonly IUserRepository _userRepository; + private readonly IPostRepository _postRepository; + private readonly ICommentRepository _commentRepository; + private readonly IMapper _postMapper; + + public CommentService(IUserRepository userRepository, IPostRepository postRepository, ICommentRepository commentRepository, IMapper postMapper) + { + this._userRepository = userRepository; + this._postRepository = postRepository; + this._commentRepository = commentRepository; + this._postMapper = postMapper; + } + + #region Create + public async Task<Guid> AddComment(CreateCommentServiceModel createCommentServiceModel) + { + if (!await this._postRepository.DoesPostExist(createCommentServiceModel.PostId)) + throw new ArgumentException("Post does not exist!"); + + Comment comment = this._postMapper.Map<Comment>(createCommentServiceModel); + comment.TimeCreated = DateTime.Now; + + comment.Creator = await this._userRepository.GetByIdAsync(createCommentServiceModel.CreatorId); + comment.Post = await this._postRepository.GetByIdAsync(createCommentServiceModel.PostId); + + bool success = await this._commentRepository.AddAsync(comment); + if (success) + { + Comment newComment = await this._commentRepository + .GetCommentByIssuerAndTimeCreatedAsync(comment.Creator.Id, comment.TimeCreated); + + return newComment.Id; + } + else + return Guid.Empty; + } + #endregion + + #region Read + public async Task<ReadCommentServiceModel> GetCommentById(Guid id) + { + Comment comment = await this._commentRepository.GetByIdAsync(id) ?? + throw new ArgumentException("The comment does not exist"); + + User user = await this._userRepository.GetByIdAsync(comment.Creator.Id) ?? + throw new ArgumentException("The user does not exist"); + + ReadCommentServiceModel readCommentServiceModel = this._postMapper.Map<ReadCommentServiceModel>(comment); + readCommentServiceModel.IssuerFirstName = user.FirstName; + readCommentServiceModel.IssuerLastName = user.LastName; + readCommentServiceModel.IssuerUsername = user.UserName; + + return readCommentServiceModel; + } + #endregion + + #region Update + public async Task<Guid> UpdateComment(UpdateCommentServiceModel updateCommentServiceModel) + { + if (!await this._commentRepository.DoesCommentExist(updateCommentServiceModel.CommentId)) + throw new ArgumentException("Comment does not exist!"); + + Comment comment = this._postMapper.Map<Comment>(updateCommentServiceModel); + comment.TimeCreated = DateTime.Now; + + comment.Creator = await this._userRepository.GetByIdAsync(updateCommentServiceModel.CreatorId); + comment.Post = await this._postRepository.GetByIdAsync(updateCommentServiceModel.PostId); + + bool result = await this._commentRepository.EditAsync(updateCommentServiceModel.CommentId, comment); + + if (result) + return (await this._commentRepository.GetByIdAsync(updateCommentServiceModel.CommentId)).Id; + else + return Guid.Empty; + } + #endregion + + #region Delete + public async Task<bool> DeleteComment(Guid id) + { + if (!await this._commentRepository.DoesCommentExist(id)) + throw new ArgumentException("Comment does not exist!"); + + Comment comment = await this._commentRepository.GetByIdAsync(id); + return await this._commentRepository.DeleteAsync(comment); + } + #endregion + + #region Validations + /// <summary> + /// Checks whether the user Id in the token and the given user Id match + /// </summary> + public async Task<bool> ValidateJwtForCreating(Guid userId, string rawTokenData) + { + User user = await this.GetUserForValidation(rawTokenData); + + return user.Id == userId; + } + + /// <summary> + /// Checks whether the comment, gotten with the commentId, + /// is made by the user in the token + /// or if the user in the token is an admin + /// </summary> + public async Task<bool> ValidateJwtForComment(Guid commentId, string rawTokenData) + { + Comment comment = await this._commentRepository.GetByIdAsync(commentId) ?? + throw new ArgumentException("Comment does not exist!"); + User user = await this.GetUserForValidation(rawTokenData); + + //If user made the comment + if (comment.Creator.Id == user.Id) + return true; + //If user is admin + else if (user.Roles.Any(x => x.Name == Role.AdminRole)) + return true; + else + return false; + } + + /// <summary> + /// Returns the user, via their Id in the token + /// </summary> + private async Task<User> GetUserForValidation(string rawTokenData) + { + JwtSecurityToken jwt = new JwtSecurityTokenHandler().ReadJwtToken(rawTokenData.Remove(0, 7)); + + Guid jwtUserId = Guid.Parse(this.GetClaimTypeValues("ID", jwt.Claims).First()); + //HashSet<string> jwtRoleNames = this.GetClaimTypeValues("role", jwt.Claims); + + User user = await this._userRepository.GetByIdAsync(jwtUserId) ?? + throw new ArgumentException("User does not exist!"); + + return user; + } + + /// <summary> + /// Returns all values from a given claim type + /// </summary> + private List<string> GetClaimTypeValues(string type, IEnumerable<Claim> claims) + { + List<string> toReturn = new(); + + foreach (var claim in claims) + if (claim.Type == type) + toReturn.Add(claim.Value); + + return toReturn; + } + #endregion + } +} + diff --git a/src/Services/DevHive.Services/Services/FeedService.cs b/src/Services/DevHive.Services/Services/FeedService.cs new file mode 100644 index 0000000..f2f4a3a --- /dev/null +++ b/src/Services/DevHive.Services/Services/FeedService.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models; +using DevHive.Services.Models.Post; + +namespace DevHive.Services.Services +{ + public class FeedService : IFeedService + { + private readonly IMapper _mapper; + private readonly IFeedRepository _feedRepository; + private readonly IUserRepository _userRepository; + + public FeedService(IFeedRepository feedRepository, IUserRepository userRepository, IMapper mapper) + { + this._feedRepository = feedRepository; + this._userRepository = userRepository; + this._mapper = mapper; + } + + /// <summary> + /// This method is used in the feed page. + /// See the FeedRepository "GetFriendsPosts" menthod for more information on how it works. + /// </summary> + public async Task<ReadPageServiceModel> GetPage(GetPageServiceModel model) + { + User user = null; + + if (model.UserId != Guid.Empty) + user = await this._userRepository.GetByIdAsync(model.UserId); + else if (!string.IsNullOrEmpty(model.Username)) + user = await this._userRepository.GetByUsernameAsync(model.Username); + else + throw new ArgumentException("Invalid given data!"); + + if (user == null) + throw new ArgumentException("User doesn't exist!"); + + if (user.Friends.Count == 0) + throw new ArgumentException("User has no friends to get feed from!"); + + List<Post> posts = await this._feedRepository + .GetFriendsPosts(user.Friends.ToList(), model.FirstRequestIssued, model.PageNumber, model.PageSize); + + ReadPageServiceModel readPageServiceModel = new(); + foreach (Post post in posts) + readPageServiceModel.Posts.Add(this._mapper.Map<ReadPostServiceModel>(post)); + + return readPageServiceModel; + } + + /// <summary> + /// This method is used in the profile pages. + /// See the FeedRepository "GetUsersPosts" menthod for more information on how it works. + /// </summary> + public async Task<ReadPageServiceModel> GetUserPage(GetPageServiceModel model) + { + User user = null; + + if (!string.IsNullOrEmpty(model.Username)) + user = await this._userRepository.GetByUsernameAsync(model.Username); + else + throw new ArgumentException("Invalid given data!"); + + if (user == null) + throw new ArgumentException("User doesn't exist!"); + + List<Post> posts = await this._feedRepository + .GetUsersPosts(user, model.FirstRequestIssued, model.PageNumber, model.PageSize); + + ReadPageServiceModel readPageServiceModel = new(); + foreach (Post post in posts) + readPageServiceModel.Posts.Add(this._mapper.Map<ReadPostServiceModel>(post)); + + return readPageServiceModel; + } + } +} diff --git a/src/Services/DevHive.Services/Services/LanguageService.cs b/src/Services/DevHive.Services/Services/LanguageService.cs new file mode 100644 index 0000000..a6364d8 --- /dev/null +++ b/src/Services/DevHive.Services/Services/LanguageService.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Language; + +namespace DevHive.Services.Services +{ + public class LanguageService : ILanguageService + { + private readonly ILanguageRepository _languageRepository; + private readonly IMapper _languageMapper; + + public LanguageService(ILanguageRepository languageRepository, IMapper mapper) + { + this._languageRepository = languageRepository; + this._languageMapper = mapper; + } + + #region Create + public async Task<Guid> CreateLanguage(CreateLanguageServiceModel createLanguageServiceModel) + { + if (await this._languageRepository.DoesLanguageNameExistAsync(createLanguageServiceModel.Name)) + throw new ArgumentException("Language already exists!"); + + Language language = this._languageMapper.Map<Language>(createLanguageServiceModel); + bool success = await this._languageRepository.AddAsync(language); + + if (success) + { + Language newLanguage = await this._languageRepository.GetByNameAsync(createLanguageServiceModel.Name); + return newLanguage.Id; + } + else + return Guid.Empty; + } + #endregion + + #region Read + public async Task<ReadLanguageServiceModel> GetLanguageById(Guid id) + { + Language language = await this._languageRepository.GetByIdAsync(id); + + if (language == null) + throw new ArgumentException("The language does not exist"); + + return this._languageMapper.Map<ReadLanguageServiceModel>(language); + } + + public HashSet<ReadLanguageServiceModel> GetLanguages() + { + HashSet<Language> languages = this._languageRepository.GetLanguages(); + + return this._languageMapper.Map<HashSet<ReadLanguageServiceModel>>(languages); + } + #endregion + + #region Update + public async Task<bool> UpdateLanguage(UpdateLanguageServiceModel languageServiceModel) + { + bool langExists = await this._languageRepository.DoesLanguageExistAsync(languageServiceModel.Id); + bool newLangNameExists = await this._languageRepository.DoesLanguageNameExistAsync(languageServiceModel.Name); + + if (!langExists) + throw new ArgumentException("Language does not exist!"); + + if (newLangNameExists) + throw new ArgumentException("Language name already exists in our data base!"); + + Language lang = this._languageMapper.Map<Language>(languageServiceModel); + return await this._languageRepository.EditAsync(languageServiceModel.Id, lang); + } + #endregion + + #region Delete + public async Task<bool> DeleteLanguage(Guid id) + { + if (!await this._languageRepository.DoesLanguageExistAsync(id)) + throw new ArgumentException("Language does not exist!"); + + Language language = await this._languageRepository.GetByIdAsync(id); + return await this._languageRepository.DeleteAsync(language); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services/Services/PostService.cs b/src/Services/DevHive.Services/Services/PostService.cs new file mode 100644 index 0000000..fe91f23 --- /dev/null +++ b/src/Services/DevHive.Services/Services/PostService.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Models; +using DevHive.Services.Models.Post; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using DevHive.Services.Interfaces; +using DevHive.Data.Interfaces.Repositories; +using System.Linq; +using DevHive.Data.RelationModels; + +namespace DevHive.Services.Services +{ + public class PostService : IPostService + { + private readonly ICloudService _cloudService; + private readonly IUserRepository _userRepository; + private readonly IPostRepository _postRepository; + private readonly ICommentRepository _commentRepository; + private readonly IMapper _postMapper; + + public PostService(ICloudService cloudService, IUserRepository userRepository, IPostRepository postRepository, ICommentRepository commentRepository, IMapper postMapper) + { + this._cloudService = cloudService; + this._userRepository = userRepository; + this._postRepository = postRepository; + this._commentRepository = commentRepository; + this._postMapper = postMapper; + } + + #region Create + public async Task<Guid> CreatePost(CreatePostServiceModel createPostServiceModel) + { + if (!await this._userRepository.DoesUserExistAsync(createPostServiceModel.CreatorId)) + throw new ArgumentException("User does not exist!"); + + Post post = this._postMapper.Map<Post>(createPostServiceModel); + + if (createPostServiceModel.Files.Count != 0) + { + List<string> fileUrls = await _cloudService.UploadFilesToCloud(createPostServiceModel.Files); + post.Attachments = this.GetPostAttachmentsFromUrls(post, fileUrls); + } + + post.Creator = await this._userRepository.GetByIdAsync(createPostServiceModel.CreatorId); + post.TimeCreated = DateTime.Now; + + bool success = await this._postRepository.AddAsync(post); + if (success) + { + Post newPost = await this._postRepository + .GetPostByCreatorAndTimeCreatedAsync(post.Creator.Id, post.TimeCreated); + + await this._postRepository.AddNewPostToCreator(createPostServiceModel.CreatorId, newPost); + + return newPost.Id; + } + else + return Guid.Empty; + } + #endregion + + #region Read + public async Task<ReadPostServiceModel> GetPostById(Guid id) + { + Post post = await this._postRepository.GetByIdAsync(id) ?? + throw new ArgumentException("The post does not exist!"); + + // This can't happen in repo, because of how time is usually compared + post.Comments = post.Comments + .OrderByDescending(x => x.TimeCreated.ToFileTimeUtc()) + .ToList(); + + User user = await this._userRepository.GetByIdAsync(post.Creator.Id) ?? + throw new ArgumentException("The user does not exist!"); + + ReadPostServiceModel readPostServiceModel = this._postMapper.Map<ReadPostServiceModel>(post); + readPostServiceModel.CreatorFirstName = user.FirstName; + readPostServiceModel.CreatorLastName = user.LastName; + readPostServiceModel.CreatorUsername = user.UserName; + readPostServiceModel.FileUrls = post.Attachments.Select(x => x.FileUrl).ToList(); + + return readPostServiceModel; + } + #endregion + + #region Update + public async Task<Guid> UpdatePost(UpdatePostServiceModel updatePostServiceModel) + { + if (!await this._postRepository.DoesPostExist(updatePostServiceModel.PostId)) + throw new ArgumentException("Post does not exist!"); + + Post post = this._postMapper.Map<Post>(updatePostServiceModel); + + if (updatePostServiceModel.Files.Count != 0) + { + if (await this._postRepository.DoesPostHaveFiles(updatePostServiceModel.PostId)) + { + List<string> fileUrlsToRemove = await this._postRepository.GetFileUrls(updatePostServiceModel.PostId); + bool success = await _cloudService.RemoveFilesFromCloud(fileUrlsToRemove); + if (!success) + throw new InvalidCastException("Could not delete files from the post!"); + } + + List<string> fileUrls = await _cloudService.UploadFilesToCloud(updatePostServiceModel.Files) ?? + throw new ArgumentNullException("Unable to upload images to cloud"); + post.Attachments = this.GetPostAttachmentsFromUrls(post, fileUrls); + } + + post.Creator = await this._userRepository.GetByIdAsync(updatePostServiceModel.CreatorId); + post.Comments = await this._commentRepository.GetPostComments(updatePostServiceModel.PostId); + post.TimeCreated = DateTime.Now; + + bool result = await this._postRepository.EditAsync(updatePostServiceModel.PostId, post); + + if (result) + return (await this._postRepository.GetByIdAsync(updatePostServiceModel.PostId)).Id; + else + return Guid.Empty; + } + #endregion + + #region Delete + public async Task<bool> DeletePost(Guid id) + { + if (!await this._postRepository.DoesPostExist(id)) + throw new ArgumentException("Post does not exist!"); + + Post post = await this._postRepository.GetByIdAsync(id); + + if (await this._postRepository.DoesPostHaveFiles(id)) + { + List<string> fileUrls = await this._postRepository.GetFileUrls(id); + bool success = await _cloudService.RemoveFilesFromCloud(fileUrls); + if (!success) + throw new InvalidCastException("Could not delete files from the post. Please try again"); + } + + return await this._postRepository.DeleteAsync(post); + } + #endregion + + #region Validations + /// <summary> + /// Checks whether the user Id in the token and the given user Id match + /// </summary> + public async Task<bool> ValidateJwtForCreating(Guid userId, string rawTokenData) + { + User user = await this.GetUserForValidation(rawTokenData); + + return user.Id == userId; + } + + /// <summary> + /// Checks whether the post, gotten with the postId, + /// is made by the user in the token + /// or if the user in the token is an admin + /// </summary> + public async Task<bool> ValidateJwtForPost(Guid postId, string rawTokenData) + { + Post post = await this._postRepository.GetByIdAsync(postId) ?? + throw new ArgumentException("Post does not exist!"); + User user = await this.GetUserForValidation(rawTokenData); + + //If user made the post + if (post.Creator.Id == user.Id) + return true; + //If user is admin + else if (user.Roles.Any(x => x.Name == Role.AdminRole)) + return true; + else + return false; + } + + /// <summary> + /// Checks whether the comment, gotten with the commentId, + /// is made by the user in the token + /// or if the user in the token is an admin + /// </summary> + public async Task<bool> ValidateJwtForComment(Guid commentId, string rawTokenData) + { + Comment comment = await this._commentRepository.GetByIdAsync(commentId) ?? + throw new ArgumentException("Comment does not exist!"); + User user = await this.GetUserForValidation(rawTokenData); + + //If user made the comment + if (comment.Creator.Id == user.Id) + return true; + //If user is admin + else if (user.Roles.Any(x => x.Name == Role.AdminRole)) + return true; + else + return false; + } + + /// <summary> + /// Returns the user, via their Id in the token + /// </summary> + private async Task<User> GetUserForValidation(string rawTokenData) + { + JwtSecurityToken jwt = new JwtSecurityTokenHandler().ReadJwtToken(rawTokenData.Remove(0, 7)); + + Guid jwtUserId = Guid.Parse(this.GetClaimTypeValues("ID", jwt.Claims).First()); + //HashSet<string> jwtRoleNames = this.GetClaimTypeValues("role", jwt.Claims); + + User user = await this._userRepository.GetByIdAsync(jwtUserId) ?? + throw new ArgumentException("User does not exist!"); + + return user; + } + + /// <summary> + /// Returns all values from a given claim type + /// </summary> + private List<string> GetClaimTypeValues(string type, IEnumerable<Claim> claims) + { + List<string> toReturn = new(); + + foreach (var claim in claims) + if (claim.Type == type) + toReturn.Add(claim.Value); + + return toReturn; + } + #endregion + + #region Misc + private List<PostAttachments> GetPostAttachmentsFromUrls(Post post, List<string> fileUrls) + { + List<PostAttachments> postAttachments = new List<PostAttachments>(); + foreach (string url in fileUrls) + postAttachments.Add(new PostAttachments { Post = post, FileUrl = url }); + return postAttachments; + } + #endregion + } +} diff --git a/src/Services/DevHive.Services/Services/RateService.cs b/src/Services/DevHive.Services/Services/RateService.cs new file mode 100644 index 0000000..204c550 --- /dev/null +++ b/src/Services/DevHive.Services/Services/RateService.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Post.Rating; + +namespace DevHive.Services.Services +{ + public class RateService : IRateService + { + private readonly IPostRepository _postRepository; + private readonly IUserRepository _userRepository; + private readonly IRatingRepository _ratingRepository; + private readonly IMapper _mapper; + + public RateService(IPostRepository postRepository, IRatingRepository ratingRepository, IUserRepository userRepository, IMapper mapper) + { + this._postRepository = postRepository; + this._ratingRepository = ratingRepository; + this._userRepository = userRepository; + this._mapper = mapper; + } + + public async Task<ReadPostRatingServiceModel> RatePost(RatePostServiceModel ratePostServiceModel) + { + throw new NotImplementedException(); + // if (!await this._postRepository.DoesPostExist(ratePostServiceModel.PostId)) + // throw new ArgumentException("Post does not exist!"); + + // if (!await this._userRepository.DoesUserExistAsync(ratePostServiceModel.UserId)) + // throw new ArgumentException("User does not exist!"); + + // Post post = await this._postRepository.GetByIdAsync(ratePostServiceModel.PostId); + // User user = await this._userRepository.GetByIdAsync(ratePostServiceModel.UserId); + + // if (this.HasUserRatedThisPost(user, post)) + // throw new ArgumentException("You can't rate the same post more then one(duh, amigo)"); + + // this.Rate(user, post, ratePostServiceModel.Liked); + + // bool success = await this._ratingRepository.EditAsync(post.Rating.Id, post.Rating); + // if (!success) + // throw new InvalidOperationException("Unable to rate the post!"); + + // Rating newRating = await this._ratingRepository.GetByIdAsync(post.Rating.Id); + // return this._mapper.Map<ReadPostRatingServiceModel>(newRating); + } + + public async Task<ReadPostRatingServiceModel> RemoveUserRateFromPost(Guid userId, Guid postId) + { + throw new NotImplementedException(); + // Post post = await this._postRepository.GetByIdAsync(postId); + // User user = await this._userRepository.GetByIdAsync(userId); + + // if (!this.HasUserRatedThisPost(user, post)) + // throw new ArgumentException("You haven't rated this post, lmao!"); + } + + public bool HasUserRatedThisPost(User user, Post post) + { + throw new NotImplementedException(); + // return post.Rating.UsersThatRated + // .Any(x => x.Id == user.Id); + } + + private void Rate(User user, Post post, bool liked) + { + throw new NotImplementedException(); + // if (liked) + // post.Rating.Rate++; + // else + // post.Rating.Rate--; + + // post.Rating.UsersThatRated.Add(user); + } + } +} diff --git a/src/Services/DevHive.Services/Services/RoleService.cs b/src/Services/DevHive.Services/Services/RoleService.cs new file mode 100644 index 0000000..a8b8e17 --- /dev/null +++ b/src/Services/DevHive.Services/Services/RoleService.cs @@ -0,0 +1,70 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Identity.Role; +using DevHive.Services.Models.Language; + +namespace DevHive.Services.Services +{ + public class RoleService : IRoleService + { + private readonly IRoleRepository _roleRepository; + private readonly IMapper _roleMapper; + + public RoleService(IRoleRepository roleRepository, IMapper mapper) + { + this._roleRepository = roleRepository; + this._roleMapper = mapper; + } + + public async Task<Guid> CreateRole(CreateRoleServiceModel roleServiceModel) + { + if (await this._roleRepository.DoesNameExist(roleServiceModel.Name)) + throw new ArgumentException("Role already exists!"); + + Role role = this._roleMapper.Map<Role>(roleServiceModel); + bool success = await this._roleRepository.AddAsync(role); + + if (success) + { + Role newRole = await this._roleRepository.GetByNameAsync(roleServiceModel.Name); + return newRole.Id; + } + else + return Guid.Empty; + + } + + public async Task<RoleServiceModel> GetRoleById(Guid id) + { + Role role = await this._roleRepository.GetByIdAsync(id) + ?? throw new ArgumentException("Role does not exist!"); + + return this._roleMapper.Map<RoleServiceModel>(role); + } + + public async Task<bool> UpdateRole(UpdateRoleServiceModel updateRoleServiceModel) + { + if (!await this._roleRepository.DoesRoleExist(updateRoleServiceModel.Id)) + throw new ArgumentException("Role does not exist!"); + + if (await this._roleRepository.DoesNameExist(updateRoleServiceModel.Name)) + throw new ArgumentException("Role name already exists!"); + + Role role = this._roleMapper.Map<Role>(updateRoleServiceModel); + return await this._roleRepository.EditAsync(updateRoleServiceModel.Id, role); + } + + public async Task<bool> DeleteRole(Guid id) + { + if (!await this._roleRepository.DoesRoleExist(id)) + throw new ArgumentException("Role does not exist!"); + + Role role = await this._roleRepository.GetByIdAsync(id); + return await this._roleRepository.DeleteAsync(role); + } + } +} diff --git a/src/Services/DevHive.Services/Services/TechnologyService.cs b/src/Services/DevHive.Services/Services/TechnologyService.cs new file mode 100644 index 0000000..6dd6286 --- /dev/null +++ b/src/Services/DevHive.Services/Services/TechnologyService.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces.Repositories; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Technology; + +namespace DevHive.Services.Services +{ + public class TechnologyService : ITechnologyService + { + private readonly ITechnologyRepository _technologyRepository; + private readonly IMapper _technologyMapper; + + public TechnologyService(ITechnologyRepository technologyRepository, IMapper technologyMapper) + { + this._technologyRepository = technologyRepository; + this._technologyMapper = technologyMapper; + } + + #region Create + public async Task<Guid> CreateTechnology(CreateTechnologyServiceModel technologyServiceModel) + { + if (await this._technologyRepository.DoesTechnologyNameExistAsync(technologyServiceModel.Name)) + throw new ArgumentException("Technology already exists!"); + + Technology technology = this._technologyMapper.Map<Technology>(technologyServiceModel); + bool success = await this._technologyRepository.AddAsync(technology); + + if (success) + { + Technology newTechnology = await this._technologyRepository.GetByNameAsync(technologyServiceModel.Name); + return newTechnology.Id; + } + else + return Guid.Empty; + } + #endregion + + #region Read + public async Task<ReadTechnologyServiceModel> GetTechnologyById(Guid id) + { + Technology technology = await this._technologyRepository.GetByIdAsync(id); + + if (technology == null) + throw new ArgumentException("The technology does not exist"); + + return this._technologyMapper.Map<ReadTechnologyServiceModel>(technology); + } + + public HashSet<ReadTechnologyServiceModel> GetTechnologies() + { + HashSet<Technology> technologies = this._technologyRepository.GetTechnologies(); + + return this._technologyMapper.Map<HashSet<ReadTechnologyServiceModel>>(technologies); + } + #endregion + + #region Update + public async Task<bool> UpdateTechnology(UpdateTechnologyServiceModel updateTechnologyServiceModel) + { + if (!await this._technologyRepository.DoesTechnologyExistAsync(updateTechnologyServiceModel.Id)) + throw new ArgumentException("Technology does not exist!"); + + if (await this._technologyRepository.DoesTechnologyNameExistAsync(updateTechnologyServiceModel.Name)) + throw new ArgumentException("Technology name already exists!"); + + Technology technology = this._technologyMapper.Map<Technology>(updateTechnologyServiceModel); + bool result = await this._technologyRepository.EditAsync(updateTechnologyServiceModel.Id, technology); + + return result; + } + #endregion + + #region Delete + public async Task<bool> DeleteTechnology(Guid id) + { + if (!await this._technologyRepository.DoesTechnologyExistAsync(id)) + throw new ArgumentException("Technology does not exist!"); + + Technology technology = await this._technologyRepository.GetByIdAsync(id); + bool result = await this._technologyRepository.DeleteAsync(technology); + + return result; + } + #endregion + } +} diff --git a/src/Services/DevHive.Services/Services/UserService.cs b/src/Services/DevHive.Services/Services/UserService.cs new file mode 100644 index 0000000..22d5052 --- /dev/null +++ b/src/Services/DevHive.Services/Services/UserService.cs @@ -0,0 +1,382 @@ +using AutoMapper; +using DevHive.Services.Options; +using DevHive.Services.Models.Identity.User; +using System.Threading.Tasks; +using DevHive.Data.Models; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using System.Collections.Generic; +using DevHive.Common.Models.Identity; +using DevHive.Services.Interfaces; +using DevHive.Data.Interfaces.Repositories; +using System.Linq; +using DevHive.Common.Models.Misc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; + +namespace DevHive.Services.Services +{ + public class UserService : IUserService + { + private readonly IUserRepository _userRepository; + private readonly IRoleRepository _roleRepository; + private readonly ILanguageRepository _languageRepository; + private readonly ITechnologyRepository _technologyRepository; + private readonly UserManager<User> _userManager; + private readonly RoleManager<Role> _roleManager; + private readonly IMapper _userMapper; + private readonly JWTOptions _jwtOptions; + private readonly ICloudService _cloudService; + + public UserService(IUserRepository userRepository, + ILanguageRepository languageRepository, + IRoleRepository roleRepository, + ITechnologyRepository technologyRepository, + UserManager<User> userManager, + RoleManager<Role> roleManager, + IMapper mapper, + JWTOptions jwtOptions, + ICloudService cloudService) + { + this._userRepository = userRepository; + this._roleRepository = roleRepository; + this._userMapper = mapper; + this._jwtOptions = jwtOptions; + this._languageRepository = languageRepository; + this._technologyRepository = technologyRepository; + this._userManager = userManager; + this._roleManager = roleManager; + this._cloudService = cloudService; + } + + #region Authentication + /// <summary> + /// Adds a new user to the database with the values from the given model. + /// Returns a JSON Web Token (that can be used for authorization) + /// </summary> + public async Task<TokenModel> LoginUser(LoginServiceModel loginModel) + { + if (!await this._userRepository.DoesUsernameExistAsync(loginModel.UserName)) + throw new ArgumentException("Invalid username!"); + + User user = await this._userRepository.GetByUsernameAsync(loginModel.UserName); + + if (user.PasswordHash != PasswordModifications.GeneratePasswordHash(loginModel.Password)) + throw new ArgumentException("Incorrect password!"); + + return new TokenModel(WriteJWTSecurityToken(user.Id, user.UserName, user.Roles)); + } + + /// <summary> + /// Returns a new JSON Web Token (that can be used for authorization) for the given user + /// </summary> + public async Task<TokenModel> RegisterUser(RegisterServiceModel registerModel) + { + if (await this._userRepository.DoesUsernameExistAsync(registerModel.UserName)) + throw new ArgumentException("Username already exists!"); + + if (await this._userRepository.DoesEmailExistAsync(registerModel.Email)) + throw new ArgumentException("Email already exists!"); + + User user = this._userMapper.Map<User>(registerModel); + user.PasswordHash = PasswordModifications.GeneratePasswordHash(registerModel.Password); + user.ProfilePicture = new ProfilePicture() { PictureURL = "/assets/images/feed/profile-pic.png" }; + + // Make sure the default role exists + //TODO: Move when project starts + if (!await this._roleRepository.DoesNameExist(Role.DefaultRole)) + await this._roleRepository.AddAsync(new Role { Name = Role.DefaultRole }); + + user.Roles.Add(await this._roleRepository.GetByNameAsync(Role.DefaultRole)); + + IdentityResult userResult = await this._userManager.CreateAsync(user); + User createdUser = await this._userRepository.GetByUsernameAsync(registerModel.UserName); + + if (!userResult.Succeeded) + throw new ArgumentException("Unable to create a user"); + + return new TokenModel(WriteJWTSecurityToken(createdUser.Id, createdUser.UserName, createdUser.Roles)); + } + #endregion + + #region Read + public async Task<UserServiceModel> GetUserById(Guid id) + { + User user = await this._userRepository.GetByIdAsync(id) ?? + throw new ArgumentException("User does not exist!"); + + return this._userMapper.Map<UserServiceModel>(user); + } + + public async Task<UserServiceModel> GetUserByUsername(string username) + { + User user = await this._userRepository.GetByUsernameAsync(username) ?? + throw new ArgumentException("User does not exist!"); + + return this._userMapper.Map<UserServiceModel>(user); + } + #endregion + + #region Update + public async Task<UserServiceModel> UpdateUser(UpdateUserServiceModel updateUserServiceModel) + { + await this.ValidateUserOnUpdate(updateUserServiceModel); + + User currentUser = await this._userRepository.GetByIdAsync(updateUserServiceModel.Id); + await this.PopulateUserModel(currentUser, updateUserServiceModel); + + if (updateUserServiceModel.Friends.Count() > 0) + await this.CreateRelationToFriends(currentUser, updateUserServiceModel.Friends.ToList()); + else + currentUser.Friends.Clear(); + + IdentityResult result = await this._userManager.UpdateAsync(currentUser); + + if (!result.Succeeded) + throw new InvalidOperationException("Unable to edit user!"); + + User newUser = await this._userRepository.GetByIdAsync(currentUser.Id); + return this._userMapper.Map<UserServiceModel>(newUser); + } + + /// <summary> + /// Uploads the given picture and assigns it's link to the user in the database + /// </summary> + public async Task<ProfilePictureServiceModel> UpdateProfilePicture(UpdateProfilePictureServiceModel updateProfilePictureServiceModel) + { + User user = await this._userRepository.GetByIdAsync(updateProfilePictureServiceModel.UserId); + + if (!string.IsNullOrEmpty(user.ProfilePicture.PictureURL)) + { + bool success = await _cloudService.RemoveFilesFromCloud(new List<string> { user.ProfilePicture.PictureURL }); + if (!success) + throw new InvalidCastException("Could not delete old profile picture!"); + } + + string fileUrl = (await this._cloudService.UploadFilesToCloud(new List<IFormFile> { updateProfilePictureServiceModel.Picture }))[0] ?? + throw new ArgumentNullException("Unable to upload profile picture to cloud"); + + bool successful = await this._userRepository.UpdateProfilePicture(updateProfilePictureServiceModel.UserId, fileUrl); + + if (!successful) + throw new InvalidOperationException("Unable to change profile picture!"); + + return new ProfilePictureServiceModel() { ProfilePictureURL = fileUrl }; + } + #endregion + + #region Delete + public async Task<bool> DeleteUser(Guid id) + { + if (!await this._userRepository.DoesUserExistAsync(id)) + throw new ArgumentException("User does not exist!"); + + User user = await this._userRepository.GetByIdAsync(id); + IdentityResult result = await this._userManager.DeleteAsync(user); + + return result.Succeeded; + } + #endregion + + #region Validations + /// <summary> + /// Checks whether the given user, gotten by the "id" property, + /// is the same user as the one in the token (uness the user in the token has the admin role) + /// and the roles in the token are the same as those in the user, gotten by the id in the token + /// </summary> + public async Task<bool> ValidJWT(Guid id, string rawTokenData) + { + // There is authorization name in the beginning, i.e. "Bearer eyJh..." + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(rawTokenData.Remove(0, 7)); + + Guid jwtUserID = new Guid(this.GetClaimTypeValues("ID", jwt.Claims).First()); + List<string> jwtRoleNames = this.GetClaimTypeValues("role", jwt.Claims); + + User user = await this._userRepository.GetByIdAsync(jwtUserID) + ?? throw new ArgumentException("User does not exist!"); + + /* Check if user is trying to do something to himself, unless he's an admin */ + + /* Check roles */ + if (!jwtRoleNames.Contains(Role.AdminRole)) + if (user.Id != id) + return false; + + // Check if jwt contains all user roles (if it doesn't, jwt is either old or tampered with) + foreach (var role in user.Roles) + { + if (!jwtRoleNames.Contains(role.Name)) + return false; + } + + // Check if jwt contains only roles of user + if (jwtRoleNames.Count != user.Roles.Count) + return false; + + return true; + } + + /// <summary> + /// Returns all values from a given claim type + /// </summary> + private List<string> GetClaimTypeValues(string type, IEnumerable<Claim> claims) + { + List<string> toReturn = new(); + + foreach (var claim in claims) + if (claim.Type == type) + toReturn.Add(claim.Value); + + return toReturn; + } + + /// <summary> + /// Checks whether the user in the model exists + /// and whether the username in the model is already taken. + /// If the check fails (is false), it throws an exception, otherwise nothing happens + /// </summary> + private async Task ValidateUserOnUpdate(UpdateUserServiceModel updateUserServiceModel) + { + if (!await this._userRepository.DoesUserExistAsync(updateUserServiceModel.Id)) + throw new ArgumentException("User does not exist!"); + + if (updateUserServiceModel.Friends.Any(x => x.UserName == updateUserServiceModel.UserName)) + throw new ArgumentException("You cant add yourself as a friend(sry, bro)!"); + + if (!this._userRepository.DoesUserHaveThisUsername(updateUserServiceModel.Id, updateUserServiceModel.UserName) + && await this._userRepository.DoesUsernameExistAsync(updateUserServiceModel.UserName)) + throw new ArgumentException("Username already exists!"); + + List<string> usernames = new(); + foreach (var friend in updateUserServiceModel.Friends) + usernames.Add(friend.UserName); + + if (!await this._userRepository.ValidateFriendsCollectionAsync(usernames)) + throw new ArgumentException("One or more friends do not exist!"); + } + + /// <summary> + /// Return a new JSON Web Token, containing the user id, username and roles. + /// Tokens have an expiration time of 7 days. + /// </summary> + private string WriteJWTSecurityToken(Guid userId, string username, HashSet<Role> roles) + { + byte[] signingKey = Encoding.ASCII.GetBytes(_jwtOptions.Secret); + HashSet<Claim> claims = new() + { + new Claim("ID", $"{userId}"), + new Claim("Username", username) + }; + + foreach (var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role.Name)); + } + + SecurityTokenDescriptor tokenDescriptor = new() + { + Subject = new ClaimsIdentity(claims), + Expires = DateTime.Today.AddDays(7), + SigningCredentials = new SigningCredentials( + new SymmetricSecurityKey(signingKey), + SecurityAlgorithms.HmacSha512Signature) + }; + + JwtSecurityTokenHandler tokenHandler = new(); + SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + #endregion + + #region Misc + public async Task<TokenModel> SuperSecretPromotionToAdmin(Guid userId) + { + User user = await this._userRepository.GetByIdAsync(userId) ?? + throw new ArgumentException("User does not exist! Can't promote shit in this country..."); + + if (!await this._roleRepository.DoesNameExist(Role.AdminRole)) + { + Role adminRole = new() + { + Name = Role.AdminRole + }; + adminRole.Users.Add(user); + + await this._roleRepository.AddAsync(adminRole); + } + + Role admin = await this._roleManager.FindByNameAsync(Role.AdminRole); + + user.Roles.Add(admin); + await this._userManager.UpdateAsync(user); + + User newUser = await this._userRepository.GetByIdAsync(userId); + + return new TokenModel(WriteJWTSecurityToken(newUser.Id, newUser.UserName, newUser.Roles)); + } + + private async Task PopulateUserModel(User user, UpdateUserServiceModel updateUserServiceModel) + { + user.UserName = updateUserServiceModel.UserName; + user.FirstName = updateUserServiceModel.FirstName; + user.LastName = updateUserServiceModel.LastName; + user.Email = updateUserServiceModel.Email; + + //Do NOT allow a user to change his roles, unless he is an Admin + bool isAdmin = await this._userManager.IsInRoleAsync(user, Role.AdminRole); + + if (isAdmin) + { + HashSet<Role> roles = new(); + foreach (var role in updateUserServiceModel.Roles) + { + Role returnedRole = await this._roleRepository.GetByNameAsync(role.Name) ?? + throw new ArgumentException($"Role {role.Name} does not exist!"); + + roles.Add(returnedRole); + } + user.Roles = roles; + } + + HashSet<Language> languages = new(); + int languagesCount = updateUserServiceModel.Languages.Count; + for (int i = 0; i < languagesCount; i++) + { + Language language = await this._languageRepository.GetByNameAsync(updateUserServiceModel.Languages.ElementAt(i).Name) ?? + throw new ArgumentException("Invalid language name!"); + + languages.Add(language); + } + user.Languages = languages; + + /* Fetch Technologies and replace model's*/ + HashSet<Technology> technologies = new(); + int technologiesCount = updateUserServiceModel.Technologies.Count; + for (int i = 0; i < technologiesCount; i++) + { + Technology technology = await this._technologyRepository.GetByNameAsync(updateUserServiceModel.Technologies.ElementAt(i).Name) ?? + throw new ArgumentException("Invalid technology name!"); + + technologies.Add(technology); + } + user.Technologies = technologies; + } + + private async Task CreateRelationToFriends(User user, List<UpdateFriendServiceModel> friends) + { + foreach (var friend in friends) + { + User amigo = await this._userRepository.GetByUsernameAsync(friend.UserName); + + user.Friends.Add(amigo); + amigo.Friends.Add(user); + + await this._userManager.UpdateAsync(amigo); + } + } + #endregion + } +} |
