diff options
Diffstat (limited to 'src/Services/DevHive.Services')
31 files changed, 1793 insertions, 0 deletions
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..1d7d88b --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/PostMappings.cs @@ -0,0 +1,32 @@ +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>(); + 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..9ad5f25 --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using DevHive.Data.Models; +using DevHive.Services.Models.Rating; + +namespace DevHive.Services.Configurations.Mapping +{ + public class RatingMappings : Profile + { + public RatingMappings() + { + CreateMap<CreateRatingServiceModel, Rating>() + .ForMember(dest => dest.User, src => src.Ignore()) + .ForMember(dest => dest.Post, src => src.Ignore()) + .ForMember(dest => dest.Id, src => src.Ignore()); + + CreateMap<Rating, ReadRatingServiceModel>(); + + CreateMap<UpdateRatingServiceModel, Rating>(); + } + } +} 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..e870ab1 --- /dev/null +++ b/src/Services/DevHive.Services/Configurations/Mapping/RoleMapings.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using DevHive.Data.Models; +using DevHive.Services.Models.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>(); + + CreateMap<RoleServiceModel, 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..5a39f73 --- /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.User; + +namespace DevHive.Services.Configurations.Mapping +{ + public class UserMappings : Profile + { + public UserMappings() + { + CreateMap<UserServiceModel, User>(); + CreateMap<RegisterServiceModel, User>() + .ForMember(dest => dest.PasswordHash, src => src.MapFrom(p => p.Password)); + CreateMap<FriendServiceModel, User>() + .ForMember(dest => dest.Friends, src => src.Ignore()); + CreateMap<UpdateUserServiceModel, User>() + .ForMember(dest => dest.Friends, src => src.Ignore()); + 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(dest => dest.ProfilePictureURL, src => src.MapFrom(p => p.ProfilePicture.PictureURL)); + CreateMap<User, FriendServiceModel>(); + + CreateMap<UserServiceModel, UpdateUserServiceModel>(); + CreateMap<UserServiceModel, UpdateFriendServiceModel>(); + } + } +} diff --git a/src/Services/DevHive.Services/DevHive.Services.csproj b/src/Services/DevHive.Services/DevHive.Services.csproj new file mode 100644 index 0000000..2468711 --- /dev/null +++ b/src/Services/DevHive.Services/DevHive.Services.csproj @@ -0,0 +1,27 @@ +<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.4"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0"/> + <PackageReference Include="AutoMapper" Version="10.1.1"/> + <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1"/> + <PackageReference Include="CloudinaryDotNet" Version="1.15.1"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.20.0.28934"/> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\Data\DevHive.Data\DevHive.Data.csproj"/> + <ProjectReference Include="..\DevHive.Services.Models\DevHive.Services.Models.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.Models.csproj"/> + </ItemGroup> + <PropertyGroup> + <EnableNETAnalyzers>true</EnableNETAnalyzers> + <AnalysisLevel>latest</AnalysisLevel> + </PropertyGroup> +</Project>
\ No newline at end of file diff --git a/src/Services/DevHive.Services/Interfaces/ICloudService.cs b/src/Services/DevHive.Services/Interfaces/ICloudService.cs new file mode 100644 index 0000000..040729f --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/ICloudService.cs @@ -0,0 +1,14 @@ +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<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..6d92a5d --- /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 createCommentServiceModel); + + 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/IFriendsService.cs b/src/Services/DevHive.Services/Interfaces/IFriendsService.cs new file mode 100644 index 0000000..52f23f3 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IFriendsService.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace DevHive.Services.Interfaces +{ + public interface IFriendsService + { + Task<bool> AddFriend(Guid userId, string friendUsername); + Task<bool> RemoveFriend(Guid userId, string friendUsername); + } +} 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..5ccecff --- /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/IProfilePictureService.cs b/src/Services/DevHive.Services/Interfaces/IProfilePictureService.cs new file mode 100644 index 0000000..edf2775 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IProfilePictureService.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using DevHive.Services.Models.ProfilePicture; + +namespace DevHive.Services.Interfaces +{ + public interface IProfilePictureService + { + /// <summary> + /// Get a profile picture by it's Guid + /// </summary> + /// <param name="id">Profile picture's Guid</param> + /// <returns>The profile picture's URL in the cloud</returns> + Task<string> GetProfilePictureById(Guid id); + + /// <summary> + /// Uploads the given picture and assigns it's link to the user in the database + /// </summary> + /// <param name="profilePictureServiceModel">Contains User's Guid and the new picture to be updated</param> + /// <returns>The new profile picture's URL in the cloud</returns> + Task<string> UpdateProfilePicture(ProfilePictureServiceModel profilePictureServiceModel); + + /// <summary> + /// Delete a profile picture from the cloud and the database + /// </summary> + /// <param name="id">The profile picture's Guid</param> + /// <returns>True if the picture is deleted, false otherwise</returns> + Task<bool> DeleteProfilePicture(Guid id); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IRatingService.cs b/src/Services/DevHive.Services/Interfaces/IRatingService.cs new file mode 100644 index 0000000..be33300 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IRatingService.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using DevHive.Data.Models; +using DevHive.Services.Models.Rating; + +namespace DevHive.Services.Interfaces +{ + public interface IRatingService + { + Task<Guid> RatePost(CreateRatingServiceModel createRatingServiceModel); + + Task<ReadRatingServiceModel> GetRatingById(Guid ratingId); + Task<ReadRatingServiceModel> GetRatingByPostAndUser(Guid userId, Guid postId); + + + Task<ReadRatingServiceModel> UpdateRating(UpdateRatingServiceModel updateRatingServiceModel); + + Task<bool> DeleteRating(Guid ratingId); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IRoleService.cs b/src/Services/DevHive.Services/Interfaces/IRoleService.cs new file mode 100644 index 0000000..36e5a01 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IRoleService.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using DevHive.Services.Models.Role; + +namespace DevHive.Services.Interfaces +{ + public interface IRoleService + { + Task<Guid> CreateRole(CreateRoleServiceModel createRoleServiceModel); + + Task<RoleServiceModel> GetRoleById(Guid id); + + Task<bool> UpdateRole(UpdateRoleServiceModel updateRoleServiceModel); + + 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..da07507 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IUserService.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using DevHive.Common.Models.Identity; +using DevHive.Services.Models.User; + +namespace DevHive.Services.Interfaces +{ + public interface IUserService + { + /// <summary> + /// Log ins an existing user and gives him/her a JWT Token for further authorization + /// </summary> + /// <param name="loginModel">Login service model, conaining user's username and password</param> + /// <returns>A JWT Token for authorization</returns> + Task<TokenModel> LoginUser(LoginServiceModel loginModel); + + /// <summary> + /// Registers a new user and gives him/her a JWT Token for further authorization + /// </summary> + /// <param name="registerModel">Register service model, containing the new user's data</param> + /// <returns>A JWT Token for authorization</returns> + Task<TokenModel> RegisterUser(RegisterServiceModel registerModel); + + /// <summary> + /// Get a user by his username. Used for querying profiles without provided authentication + /// </summary> + /// <param name="username">User's username, who's to be queried</param> + /// <returns>The queried user or null, if non existant</returns> + Task<UserServiceModel> GetUserByUsername(string username); + + /// <summary> + /// Get a user by his Guid. Used for querying full user's profile + /// Requires authenticated user + /// </summary> + /// <param name="id">User's username, who's to be queried</param> + /// <returns>The queried user or null, if non existant</returns> + Task<UserServiceModel> GetUserById(Guid id); + + /// <summary> + /// Updates a user's data, provided a full model with new details + /// Requires authenticated user + /// </summary> + /// <param name="updateUserServiceModel">Full update user model for updating</param> + /// <returns>Read model of the new user</returns> + Task<UserServiceModel> UpdateUser(UpdateUserServiceModel updateUserServiceModel); + + /// <summary> + /// Deletes a user from the database and removes his data entirely + /// Requires authenticated user + /// </summary> + /// <param name="id">The user's Guid, who's to be deleted</param> + /// <returns>True if successfull, false otherwise</returns> + Task<bool> DeleteUser(Guid id); + + /// <summary> + /// We don't talk about that! + /// </summary> + /// <param name="userId"></param> + /// <returns></returns> + Task<TokenModel> SuperSecretPromotionToAdmin(Guid userId); + } +} diff --git a/src/Services/DevHive.Services/Services/CloudinaryService.cs b/src/Services/DevHive.Services/Services/CloudinaryService.cs new file mode 100644 index 0000000..61d06fc --- /dev/null +++ b/src/Services/DevHive.Services/Services/CloudinaryService.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +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 + { + // Regex for getting the filename without (final) filename extension + // So, from image.png, it will match image, and from doc.my.txt will match doc.my + private static readonly Regex s_imageRegex = new(".*(?=\\.)"); + + 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 fileName = s_imageRegex.Match(formFile.FileName).ToString(); + + using var ms = new MemoryStream(); + formFile.CopyTo(ms); + byte[] formBytes = ms.ToArray(); + + RawUploadParams rawUploadParams = new() + { + File = new FileDescription(fileName, new MemoryStream(formBytes)), + PublicId = fileName, + UseFilename = true + }; + + RawUploadResult rawUploadResult = await this._cloudinary.UploadAsync(rawUploadParams); + fileUrls.Add(rawUploadResult.Url.AbsoluteUri); + } + + return fileUrls; + } + + public async Task<bool> RemoveFilesFromCloud(List<string> fileUrls) + { + // Workaround, this method isn't fully implemented yet + await Task.Run(() => {}); + + 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..1c388f6 --- /dev/null +++ b/src/Services/DevHive.Services/Services/CommentService.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Comment; + +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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Post)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Comment)); + + User user = await this._userRepository.GetByIdAsync(comment.Creator.Id) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Comment)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Comment)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Comment)); + 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(GetClaimTypeValues("ID", jwt.Claims).First()); + + User user = await this._userRepository.GetByIdAsync(jwtUserId) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + return user; + } + + /// <summary> + /// Returns all values from a given claim type + /// </summary> + private static 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..9c622b3 --- /dev/null +++ b/src/Services/DevHive.Services/Services/FeedService.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +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" method for more information on how it works. + /// </summary> + public async Task<ReadPageServiceModel> GetPage(GetPageServiceModel getPageServiceModel) + { + //TODO: Rework the initialization of User + User user = null; + + if (getPageServiceModel.UserId != Guid.Empty) + user = await this._userRepository.GetByIdAsync(getPageServiceModel.UserId); + else if (!string.IsNullOrEmpty(getPageServiceModel.Username)) + user = await this._userRepository.GetByUsernameAsync(getPageServiceModel.Username); + else + throw new InvalidDataException(string.Format(ErrorMessages.InvalidData, ClassesConstants.Data.ToLower())); + + if (user == null) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + List<Post> posts = await this._feedRepository + .GetFriendsPosts(user.Friends.ToList(), getPageServiceModel.FirstRequestIssued, getPageServiceModel.PageNumber, getPageServiceModel.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" method for more information on how it works. + /// </summary> + public async Task<ReadPageServiceModel> GetUserPage(GetPageServiceModel model) + { + //TODO: Rework the initialization of User + User user = null; + + if (!string.IsNullOrEmpty(model.Username)) + user = await this._userRepository.GetByUsernameAsync(model.Username); + else + throw new InvalidDataException(string.Format(ErrorMessages.InvalidData, ClassesConstants.Data.ToLower())); + + if (user == null) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + 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/FriendsService.cs b/src/Services/DevHive.Services/Services/FriendsService.cs new file mode 100644 index 0000000..98f654b --- /dev/null +++ b/src/Services/DevHive.Services/Services/FriendsService.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; + +namespace DevHive.Services.Services +{ + public class FriendsService : IFriendsService + { + private readonly IUserRepository _friendRepository; + + public FriendsService(IUserRepository friendRepository) + { + this._friendRepository = friendRepository; + } + + public async Task<bool> AddFriend(Guid userId, string friendUsername) + { + User user = await this._friendRepository.GetByIdAsync(userId) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, nameof(user))); + + User friend = await this._friendRepository.GetByUsernameAsync(friendUsername) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, nameof(friend))); + + bool addedToUser = user.Friends.Add(friend) && await this._friendRepository.EditAsync(userId, user); + bool addedToFriend = friend.Friends.Add(user) && await this._friendRepository.EditAsync(friend.Id, friend); + return addedToUser && addedToFriend; + } + + public async Task<bool> RemoveFriend(Guid userId, string friendUsername) + { + User user = await this._friendRepository.GetByIdAsync(userId) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, nameof(user))); + + User friend = await this._friendRepository.GetByUsernameAsync(friendUsername) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, nameof(friend))); + + bool addedToUser = user.Friends.Remove(friend) && await this._friendRepository.EditAsync(userId, user); + bool addedToFriend = friend.Friends.Remove(user) && await this._friendRepository.EditAsync(friend.Id, friend); + return addedToUser && addedToFriend; + } + } +} diff --git a/src/Services/DevHive.Services/Services/LanguageService.cs b/src/Services/DevHive.Services/Services/LanguageService.cs new file mode 100644 index 0000000..7ee7d9f --- /dev/null +++ b/src/Services/DevHive.Services/Services/LanguageService.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +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 DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Language)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Language)); + + 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 DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Language)); + + if (newLangNameExists) + throw new DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Language)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Language)); + + 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..8580e82 --- /dev/null +++ b/src/Services/DevHive.Services/Services/PostService.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Data.Models.Relational; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Post; + +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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + Post post = this._postMapper.Map<Post>(createPostServiceModel); + + if (createPostServiceModel.Files.Count != 0) + { + List<string> fileUrls = await _cloudService.UploadFilesToCloud(createPostServiceModel.Files); + post.Attachments = 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Post)); + + // 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + int currentRating = 0; + foreach (Rating rating in post.Ratings) + { + if (rating.IsLike) + currentRating++; + else + currentRating--; + } + + 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(); + readPostServiceModel.CurrentRating = currentRating; + + return readPostServiceModel; + } + #endregion + + #region Update + public async Task<Guid> UpdatePost(UpdatePostServiceModel updatePostServiceModel) + { + if (!await this._postRepository.DoesPostExist(updatePostServiceModel.PostId)) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Post)); + + 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 InvalidOperationException(string.Format(ErrorMessages.CannotDelete, ClassesConstants.Files.ToLower())); + } + + List<string> fileUrls = await _cloudService.UploadFilesToCloud(updatePostServiceModel.Files) ?? + throw new InvalidOperationException(string.Format(ErrorMessages.CannotUpload, ClassesConstants.Files.ToLower())); + post.Attachments = 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Post)); + + 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 InvalidOperationException(string.Format(ErrorMessages.CannotDelete, ClassesConstants.Files.ToLower())); + } + + 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 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Post)); + User user = await 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Comment)); + User user = await 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(GetClaimTypeValues("ID", jwt.Claims).First()); + + User user = await this._userRepository.GetByIdAsync(jwtUserId) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + return user; + } + + /// <summary> + /// Returns all values from a given claim type + /// </summary> + private static 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 static List<PostAttachments> GetPostAttachmentsFromUrls(Post post, List<string> fileUrls) + { + List<PostAttachments> postAttachments = new(); + foreach (string url in fileUrls) + postAttachments.Add(new PostAttachments { Post = post, FileUrl = url }); + return postAttachments; + } + #endregion + } +} diff --git a/src/Services/DevHive.Services/Services/ProfilePictureService.cs b/src/Services/DevHive.Services/Services/ProfilePictureService.cs new file mode 100644 index 0000000..cafa7cb --- /dev/null +++ b/src/Services/DevHive.Services/Services/ProfilePictureService.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.ProfilePicture; +using Microsoft.AspNetCore.Http; + +namespace DevHive.Services.Services +{ + public class ProfilePictureService : IProfilePictureService + { + private readonly IUserRepository _userRepository; + private readonly IProfilePictureRepository _profilePictureRepository; + private readonly ICloudService _cloudinaryService; + + public ProfilePictureService(IUserRepository userRepository, IProfilePictureRepository profilePictureRepository, ICloudService cloudinaryService) + { + this._userRepository = userRepository; + this._profilePictureRepository = profilePictureRepository; + this._cloudinaryService = cloudinaryService; + } + + public async Task<string> GetProfilePictureById(Guid id) + { + return (await this._profilePictureRepository.GetByIdAsync(id)).PictureURL; + } + + public async Task<string> UpdateProfilePicture(ProfilePictureServiceModel profilePictureServiceModel) + { + ValidateProfPic(profilePictureServiceModel.ProfilePictureFormFile); + await ValidateUserExistsAsync(profilePictureServiceModel.UserId); + + User user = await this._userRepository.GetByIdAsync(profilePictureServiceModel.UserId); + if (user.ProfilePicture.Id != Guid.Empty) + { + List<string> file = new() { user.ProfilePicture.PictureURL }; + bool removed = await this._cloudinaryService.RemoveFilesFromCloud(file); + + if (!removed) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotDelete, ClassesConstants.Picture.ToLower())); + } + + return await SaveProfilePictureInDatabase(profilePictureServiceModel); + } + + public async Task<bool> DeleteProfilePicture(Guid id) + { + ProfilePicture profilePic = await this._profilePictureRepository.GetByIdAsync(id) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Picture)); + + bool removedFromDb = await this._profilePictureRepository.DeleteAsync(profilePic); + if (!removedFromDb) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotDelete, ClassesConstants.Picture.ToLower())); + + List<string> file = new() { profilePic.PictureURL }; + bool removedFromCloud = await this._cloudinaryService.RemoveFilesFromCloud(file); + if (!removedFromCloud) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotDelete, ClassesConstants.Picture.ToLower())); + + return true; + } + + private async Task<string> SaveProfilePictureInDatabase(ProfilePictureServiceModel profilePictureServiceModel) + { + List<IFormFile> file = new() { profilePictureServiceModel.ProfilePictureFormFile }; + string picUrl = (await this._cloudinaryService.UploadFilesToCloud(file))[0]; + ProfilePicture profilePic = new() { PictureURL = picUrl }; + + User user = await this._userRepository.GetByIdAsync(profilePictureServiceModel.UserId); + profilePic.UserId = user.Id; + profilePic.User = user; + + bool success = await this._profilePictureRepository.AddAsync(profilePic); + if (!success) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotUpload, ClassesConstants.Files.ToLower())); + + user.ProfilePicture = profilePic; + bool userProfilePicAlter = await this._userRepository.EditAsync(user.Id, user); + + if (!userProfilePicAlter) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotEdit, "user's profile picture")); + + return picUrl; + } + + private static void ValidateProfPic(IFormFile profilePictureFormFile) + { + if (profilePictureFormFile.Length == 0) + throw new ArgumentNullException(nameof(profilePictureFormFile), string.Format(ErrorMessages.InvalidData, ClassesConstants.Data.ToLower())); + } + + private async Task ValidateUserExistsAsync(Guid userId) + { + if (!await this._userRepository.DoesUserExistAsync(userId)) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + } + } +} diff --git a/src/Services/DevHive.Services/Services/RatingService.cs b/src/Services/DevHive.Services/Services/RatingService.cs new file mode 100644 index 0000000..d6b4299 --- /dev/null +++ b/src/Services/DevHive.Services/Services/RatingService.cs @@ -0,0 +1,120 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Rating; + +namespace DevHive.Services.Services +{ + public class RatingService : IRatingService + { + private readonly IPostRepository _postRepository; + private readonly IUserRepository _userRepository; + private readonly IRatingRepository _ratingRepository; + private readonly IMapper _mapper; + + private const string NotRated = "{0} has not rated" + ClassesConstants.Post; + + public RatingService(IPostRepository postRepository, IRatingRepository ratingRepository, IUserRepository userRepository, IMapper mapper) + { + this._postRepository = postRepository; + this._ratingRepository = ratingRepository; + this._userRepository = userRepository; + this._mapper = mapper; + } + + #region Create + public async Task<Guid> RatePost(CreateRatingServiceModel createRatingServiceModel) + { + if (!await this._postRepository.DoesPostExist(createRatingServiceModel.PostId)) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Post)); + + if (await this._ratingRepository.UserRatedPost(createRatingServiceModel.UserId, createRatingServiceModel.PostId)) + throw new ArgumentException("User already rated the post!"); + + Rating rating = this._mapper.Map<Rating>(createRatingServiceModel); + + rating.User = await this._userRepository.GetByIdAsync(createRatingServiceModel.UserId); + rating.Post = await this._postRepository.GetByIdAsync(createRatingServiceModel.PostId); + + bool success = await this._ratingRepository.AddAsync(rating); + + if (success) + { + Rating newRating = await this._ratingRepository.GetRatingByUserAndPostId(rating.User.Id, rating.Post.Id); + + return newRating.Id; + } + else + return Guid.Empty; + } + #endregion + + #region Read + public async Task<ReadRatingServiceModel> GetRatingById(Guid ratingId) + { + Rating rating = await this._ratingRepository.GetByIdAsync(ratingId); + if (rating is null) + return null; + + ReadRatingServiceModel readRatingServiceModel = this._mapper.Map<ReadRatingServiceModel>(rating); + readRatingServiceModel.UserId = rating.User.Id; + + return readRatingServiceModel; + } + + public async Task<ReadRatingServiceModel> GetRatingByPostAndUser(Guid userId, Guid postId) + { + Rating rating = await this._ratingRepository.GetRatingByUserAndPostId(userId, postId); + if (rating is null) + return null; + + ReadRatingServiceModel readRatingServiceModel = this._mapper.Map<ReadRatingServiceModel>(rating); + readRatingServiceModel.UserId = rating.User.Id; + + return readRatingServiceModel; + } + #endregion + + #region Update + public async Task<ReadRatingServiceModel> UpdateRating(UpdateRatingServiceModel updateRatingServiceModel) + { + Rating rating = await this._ratingRepository.GetRatingByUserAndPostId(updateRatingServiceModel.UserId, updateRatingServiceModel.PostId) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Rating)); + + User user = await this._userRepository.GetByIdAsync(updateRatingServiceModel.UserId) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + if (!await this._ratingRepository.UserRatedPost(updateRatingServiceModel.UserId, updateRatingServiceModel.PostId)) + throw new ArgumentException(string.Format(NotRated, ClassesConstants.User)); + + rating.User = user; + rating.IsLike = updateRatingServiceModel.IsLike; + + bool result = await this._ratingRepository.EditAsync(updateRatingServiceModel.Id, rating); + + if (result) + { + ReadRatingServiceModel readRatingServiceModel = this._mapper.Map<ReadRatingServiceModel>(rating); + return readRatingServiceModel; + } + else + return null; + } + #endregion + + #region Delete + public async Task<bool> DeleteRating(Guid ratingId) + { + if (!await this._ratingRepository.DoesRatingExist(ratingId)) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Rating)); + + Rating rating = await this._ratingRepository.GetByIdAsync(ratingId); + return await this._ratingRepository.DeleteAsync(rating); + } + #endregion + } +} diff --git a/src/Services/DevHive.Services/Services/RoleService.cs b/src/Services/DevHive.Services/Services/RoleService.cs new file mode 100644 index 0000000..f61181a --- /dev/null +++ b/src/Services/DevHive.Services/Services/RoleService.cs @@ -0,0 +1,71 @@ +using System; +using System.Data; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Role; + +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 createRoleServiceModel) + { + if (await this._roleRepository.DoesNameExist(createRoleServiceModel.Name)) + throw new DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Role)); + + Role role = this._roleMapper.Map<Role>(createRoleServiceModel); + bool success = await this._roleRepository.AddAsync(role); + + if (success) + { + Role newRole = await this._roleRepository.GetByNameAsync(createRoleServiceModel.Name); + return newRole.Id; + } + else + return Guid.Empty; + + } + + public async Task<RoleServiceModel> GetRoleById(Guid id) + { + Role role = await this._roleRepository.GetByIdAsync(id) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Role)); + + return this._roleMapper.Map<RoleServiceModel>(role); + } + + public async Task<bool> UpdateRole(UpdateRoleServiceModel updateRoleServiceModel) + { + if (!await this._roleRepository.DoesRoleExist(updateRoleServiceModel.Id)) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Role)); + + if (await this._roleRepository.DoesNameExist(updateRoleServiceModel.Name)) + throw new DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Role)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Role)); + + 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..4cf84c5 --- /dev/null +++ b/src/Services/DevHive.Services/Services/TechnologyService.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Data.Interfaces; +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 DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Technology)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Technology)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Technology)); + + if (await this._technologyRepository.DoesTechnologyNameExistAsync(updateTechnologyServiceModel.Name)) + throw new DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Technology)); + + 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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Technology)); + + 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..62576d4 --- /dev/null +++ b/src/Services/DevHive.Services/Services/UserService.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Constants; +using DevHive.Common.Jwt.Interfaces; +using DevHive.Common.Models.Identity; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.User; + +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 IMapper _userMapper; + private readonly IJwtService _jwtService; + + private const string NoYourselfAsFriend = "You cant add yourself as a friend(sry, bro)!"; + private const string Rant = "Can't promote shit in this country..."; + + + public UserService(IUserRepository userRepository, + ILanguageRepository languageRepository, + IRoleRepository roleRepository, + ITechnologyRepository technologyRepository, + IMapper mapper, + IJwtService jwtService) + { + this._userRepository = userRepository; + this._roleRepository = roleRepository; + this._userMapper = mapper; + this._languageRepository = languageRepository; + this._technologyRepository = technologyRepository; + this._jwtService = jwtService; + } + + #region Authentication + public async Task<TokenModel> LoginUser(LoginServiceModel loginModel) + { + if (!await this._userRepository.DoesUsernameExistAsync(loginModel.UserName)) + throw new InvalidDataException(string.Format(ErrorMessages.InvalidData, ClassesConstants.Username)); + + User user = await this._userRepository.GetByUsernameAsync(loginModel.UserName); + + if (!await this._userRepository.VerifyPassword(user, loginModel.Password)) + throw new InvalidDataException(string.Format(ErrorMessages.IncorrectData, ClassesConstants.Password.ToLower())); + + List<string> roleNames = user.Roles.Select(x => x.Name).ToList(); + return new TokenModel(this._jwtService.GenerateJwtToken(user.Id, user.UserName, roleNames)); + } + + public async Task<TokenModel> RegisterUser(RegisterServiceModel registerModel) + { + if (await this._userRepository.DoesUsernameExistAsync(registerModel.UserName)) + throw new DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Username)); + + if (await this._userRepository.DoesEmailExistAsync(registerModel.Email)) + throw new DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Email)); + + + User user = this._userMapper.Map<User>(registerModel); + + bool userResult = await this._userRepository.AddAsync(user); + bool roleResult = await this._userRepository.AddRoleToUser(user, Role.DefaultRole); + + if (!userResult) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotCreate, ClassesConstants.User.ToLower())); + if (!roleResult) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotAdd, ClassesConstants.Role.ToLower())); + + User createdUser = await this._userRepository.GetByUsernameAsync(registerModel.UserName); + + List<string> roleNames = createdUser.Roles.Select(x => x.Name).ToList(); + return new TokenModel(this._jwtService.GenerateJwtToken(createdUser.Id, createdUser.UserName, roleNames)); + } + #endregion + + #region Read + public async Task<UserServiceModel> GetUserById(Guid id) + { + User user = await this._userRepository.GetByIdAsync(id) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + return this._userMapper.Map<UserServiceModel>(user); + } + + public async Task<UserServiceModel> GetUserByUsername(string username) + { + User user = await this._userRepository.GetByUsernameAsync(username) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + return this._userMapper.Map<UserServiceModel>(user); + } + #endregion + + #region Update + public async Task<UserServiceModel> UpdateUser(UpdateUserServiceModel updateUserServiceModel) + { + await ValidateUserOnUpdate(updateUserServiceModel); + + User user = await this._userRepository.GetByIdAsync(updateUserServiceModel.Id); + await PopulateUserModel(user, updateUserServiceModel); + + if (updateUserServiceModel.Friends.Count > 0) + await CreateRelationToFriends(user, updateUserServiceModel.Friends.ToList()); + else + user.Friends.Clear(); + + bool result = await this._userRepository.EditAsync(user.Id, user); + + if (!result) + throw new InvalidOperationException(string.Format(ErrorMessages.CannotEdit, ClassesConstants.User.ToLower())); + + User newUser = await this._userRepository.GetByIdAsync(user.Id); + return this._userMapper.Map<UserServiceModel>(newUser); + } + #endregion + + #region Delete + public async Task<bool> DeleteUser(Guid id) + { + if (!await this._userRepository.DoesUserExistAsync(id)) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + User user = await this._userRepository.GetByIdAsync(id); + return await this._userRepository.DeleteAsync(user); + } + #endregion + + #region Validations + /// <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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User)); + + if (updateUserServiceModel.Friends.Any(x => x.UserName == updateUserServiceModel.UserName)) + throw new InvalidOperationException(NoYourselfAsFriend); + + if (!await this._userRepository.DoesUserHaveThisUsernameAsync(updateUserServiceModel.Id, updateUserServiceModel.UserName) + && await this._userRepository.DoesUsernameExistAsync(updateUserServiceModel.UserName)) + throw new DuplicateNameException(string.Format(ErrorMessages.AlreadyExists, ClassesConstants.Username.ToLower())); + + List<string> usernames = new(); + foreach (var friend in updateUserServiceModel.Friends) + usernames.Add(friend.UserName); + + if (!await this._userRepository.ValidateFriendsCollectionAsync(usernames)) + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.OneOrMoreFriends)); + } + #endregion + + #region Misc + public async Task<TokenModel> SuperSecretPromotionToAdmin(Guid userId) + { + User user = await this._userRepository.GetByIdAsync(userId) ?? + throw new ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.User) + " " + Rant); + + 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._roleRepository.GetByNameAsync(Role.AdminRole); + + user.Roles.Add(admin); + await this._userRepository.EditAsync(user.Id, user); + + User createdUser = await this._userRepository.GetByIdAsync(userId); + List<string> roleNames = createdUser + .Roles + .Select(x => x.Name) + .ToList(); + + return new TokenModel(this._jwtService.GenerateJwtToken(createdUser.Id, createdUser.UserName, roleNames)); + } + + 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._userRepository.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 ArgumentNullException(string.Format(ErrorMessages.DoesNotExist, ClassesConstants.Role)); + + 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 InvalidDataException(string.Format(ErrorMessages.InvalidData, nameof(Language))); + + 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 InvalidDataException(string.Format(ErrorMessages.InvalidData, nameof(Technology))); + + + 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._userRepository.EditAsync(amigo.Id, amigo); + } + } + #endregion + } +} |
