diff options
| author | Victor S <57849063+transtrike@users.noreply.github.com> | 2021-02-05 10:54:49 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-05 10:54:49 -0800 |
| commit | f4a70c6430db923af9fa9958a11c2d6612cb52cc (patch) | |
| tree | ca0ea403ba5500df20bc8854ec50529a25c64245 /src/DevHive.Services | |
| parent | 1ccdefdac025b1b986ad2bd0bc3eda7505d6e7c3 (diff) | |
| parent | 2269b5aa6c8d3dcb407c34fa256200bdc573585a (diff) | |
| download | DevHive-0.1.tar DevHive-0.1.tar.gz DevHive-0.1.zip | |
Merge pull request #18 from Team-Kaleidoscope/devv0.1
First stage: Complete. Awaiting further progress...
Diffstat (limited to 'src/DevHive.Services')
58 files changed, 2029 insertions, 7 deletions
diff --git a/src/DevHive.Services/Configurations/Mapping/CommentMappings.cs b/src/DevHive.Services/Configurations/Mapping/CommentMappings.cs new file mode 100644 index 0000000..5ca2a9e --- /dev/null +++ b/src/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/DevHive.Services/Configurations/Mapping/FeedMappings.cs b/src/DevHive.Services/Configurations/Mapping/FeedMappings.cs new file mode 100644 index 0000000..952e480 --- /dev/null +++ b/src/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/DevHive.Services/Configurations/Mapping/LanguageMappings.cs b/src/DevHive.Services/Configurations/Mapping/LanguageMappings.cs new file mode 100644 index 0000000..9c572df --- /dev/null +++ b/src/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/DevHive.Services/Configurations/Mapping/PostMappings.cs b/src/DevHive.Services/Configurations/Mapping/PostMappings.cs new file mode 100644 index 0000000..9362f90 --- /dev/null +++ b/src/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/DevHive.Services/Configurations/Mapping/RatingMappings.cs b/src/DevHive.Services/Configurations/Mapping/RatingMappings.cs new file mode 100644 index 0000000..1dbb7b4 --- /dev/null +++ b/src/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/DevHive.Services/Configurations/Mapping/RoleMapings.cs b/src/DevHive.Services/Configurations/Mapping/RoleMapings.cs new file mode 100644 index 0000000..e61a107 --- /dev/null +++ b/src/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/DevHive.Services/Configurations/Mapping/TechnologyMappings.cs b/src/DevHive.Services/Configurations/Mapping/TechnologyMappings.cs new file mode 100644 index 0000000..85b57f1 --- /dev/null +++ b/src/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/DevHive.Services/Configurations/Mapping/UserMappings.cs b/src/DevHive.Services/Configurations/Mapping/UserMappings.cs new file mode 100644 index 0000000..2b0f4ed --- /dev/null +++ b/src/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/DevHive.Services/DevHive.Services.csproj b/src/DevHive.Services/DevHive.Services.csproj index 563e6f9..66df209 100644 --- a/src/DevHive.Services/DevHive.Services.csproj +++ b/src/DevHive.Services/DevHive.Services.csproj @@ -1,7 +1,29 @@ -<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
- </PropertyGroup>
-
-</Project>
+<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" /> + </ItemGroup> + + <PropertyGroup> + <EnableNETAnalyzers>true</EnableNETAnalyzers> + <AnalysisLevel>latest</AnalysisLevel> + </PropertyGroup> +</Project> diff --git a/src/DevHive.Services/Interfaces/ICloudService.cs b/src/DevHive.Services/Interfaces/ICloudService.cs new file mode 100644 index 0000000..3ae7a24 --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/ICommentService.cs b/src/DevHive.Services/Interfaces/ICommentService.cs new file mode 100644 index 0000000..e7409a8 --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/IFeedService.cs b/src/DevHive.Services/Interfaces/IFeedService.cs new file mode 100644 index 0000000..b507b3b --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/ILanguageService.cs b/src/DevHive.Services/Interfaces/ILanguageService.cs new file mode 100644 index 0000000..fabbec2 --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/IPostService.cs b/src/DevHive.Services/Interfaces/IPostService.cs new file mode 100644 index 0000000..d35acfd --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/IRateService.cs b/src/DevHive.Services/Interfaces/IRateService.cs new file mode 100644 index 0000000..359ef55 --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/IRoleService.cs b/src/DevHive.Services/Interfaces/IRoleService.cs new file mode 100644 index 0000000..d47728c --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/ITechnologyService.cs b/src/DevHive.Services/Interfaces/ITechnologyService.cs new file mode 100644 index 0000000..8f9510c --- /dev/null +++ b/src/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/DevHive.Services/Interfaces/IUserService.cs b/src/DevHive.Services/Interfaces/IUserService.cs new file mode 100644 index 0000000..9e2b4e3 --- /dev/null +++ b/src/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/DevHive.Services/Models/Comment/CreateCommentServiceModel.cs b/src/DevHive.Services/Models/Comment/CreateCommentServiceModel.cs new file mode 100644 index 0000000..30e919b --- /dev/null +++ b/src/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/DevHive.Services/Models/Comment/ReadCommentServiceModel.cs b/src/DevHive.Services/Models/Comment/ReadCommentServiceModel.cs new file mode 100644 index 0000000..3196233 --- /dev/null +++ b/src/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/DevHive.Services/Models/Comment/UpdateCommentServiceModel.cs b/src/DevHive.Services/Models/Comment/UpdateCommentServiceModel.cs new file mode 100644 index 0000000..3b78200 --- /dev/null +++ b/src/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/DevHive.Services/Models/Feed/GetPageServiceModel.cs b/src/DevHive.Services/Models/Feed/GetPageServiceModel.cs new file mode 100644 index 0000000..1f02486 --- /dev/null +++ b/src/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/DevHive.Services/Models/Feed/ReadPageServiceModel.cs b/src/DevHive.Services/Models/Feed/ReadPageServiceModel.cs new file mode 100644 index 0000000..95f6845 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/Role/CreateRoleServiceModel.cs b/src/DevHive.Services/Models/Identity/Role/CreateRoleServiceModel.cs new file mode 100644 index 0000000..3bed3fd --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/Role/RoleServiceModel.cs b/src/DevHive.Services/Models/Identity/Role/RoleServiceModel.cs new file mode 100644 index 0000000..07249fe --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/Role/UpdateRoleServiceModel.cs b/src/DevHive.Services/Models/Identity/Role/UpdateRoleServiceModel.cs new file mode 100644 index 0000000..e21e6b4 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/BaseUserServiceModel.cs b/src/DevHive.Services/Models/Identity/User/BaseUserServiceModel.cs new file mode 100644 index 0000000..514f82a --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/FriendServiceModel.cs b/src/DevHive.Services/Models/Identity/User/FriendServiceModel.cs new file mode 100644 index 0000000..a784f5c --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/LoginServiceModel.cs b/src/DevHive.Services/Models/Identity/User/LoginServiceModel.cs new file mode 100644 index 0000000..4d53283 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/ProfilePictureServiceModel.cs b/src/DevHive.Services/Models/Identity/User/ProfilePictureServiceModel.cs new file mode 100644 index 0000000..ad81057 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/RegisterServiceModel.cs b/src/DevHive.Services/Models/Identity/User/RegisterServiceModel.cs new file mode 100644 index 0000000..adc4119 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/UpdateFriendServiceModel.cs b/src/DevHive.Services/Models/Identity/User/UpdateFriendServiceModel.cs new file mode 100644 index 0000000..b0efe10 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/UpdateProfilePictureServiceModel.cs b/src/DevHive.Services/Models/Identity/User/UpdateProfilePictureServiceModel.cs new file mode 100644 index 0000000..8563953 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/UpdateUserServiceModel.cs b/src/DevHive.Services/Models/Identity/User/UpdateUserServiceModel.cs new file mode 100644 index 0000000..b4e4400 --- /dev/null +++ b/src/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/DevHive.Services/Models/Identity/User/UserServiceModel.cs b/src/DevHive.Services/Models/Identity/User/UserServiceModel.cs new file mode 100644 index 0000000..ac7bba2 --- /dev/null +++ b/src/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/DevHive.Services/Models/Language/CreateLanguageServiceModel.cs b/src/DevHive.Services/Models/Language/CreateLanguageServiceModel.cs new file mode 100644 index 0000000..9d66d3e --- /dev/null +++ b/src/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/DevHive.Services/Models/Language/LanguageServiceModel.cs b/src/DevHive.Services/Models/Language/LanguageServiceModel.cs new file mode 100644 index 0000000..a07aa16 --- /dev/null +++ b/src/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/DevHive.Services/Models/Language/ReadLanguageServiceModel.cs b/src/DevHive.Services/Models/Language/ReadLanguageServiceModel.cs new file mode 100644 index 0000000..651dc6d --- /dev/null +++ b/src/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/DevHive.Services/Models/Language/UpdateLanguageServiceModel.cs b/src/DevHive.Services/Models/Language/UpdateLanguageServiceModel.cs new file mode 100644 index 0000000..84b7f27 --- /dev/null +++ b/src/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/DevHive.Services/Models/Post/CreatePostServiceModel.cs b/src/DevHive.Services/Models/Post/CreatePostServiceModel.cs new file mode 100644 index 0000000..304eb90 --- /dev/null +++ b/src/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/DevHive.Services/Models/Post/Rating/RatePostServiceModel.cs b/src/DevHive.Services/Models/Post/Rating/RatePostServiceModel.cs new file mode 100644 index 0000000..d4eb7bd --- /dev/null +++ b/src/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/DevHive.Services/Models/Post/Rating/ReadPostRatingServiceModel.cs b/src/DevHive.Services/Models/Post/Rating/ReadPostRatingServiceModel.cs new file mode 100644 index 0000000..8c73aaf --- /dev/null +++ b/src/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/DevHive.Services/Models/Post/ReadPostServiceModel.cs b/src/DevHive.Services/Models/Post/ReadPostServiceModel.cs new file mode 100644 index 0000000..a7aa882 --- /dev/null +++ b/src/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/DevHive.Services/Models/Post/UpdatePostServiceModel.cs b/src/DevHive.Services/Models/Post/UpdatePostServiceModel.cs new file mode 100644 index 0000000..51b16bc --- /dev/null +++ b/src/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/DevHive.Services/Models/Technology/CreateTechnologyServiceModel.cs b/src/DevHive.Services/Models/Technology/CreateTechnologyServiceModel.cs new file mode 100644 index 0000000..a31d160 --- /dev/null +++ b/src/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/DevHive.Services/Models/Technology/ReadTechnologyServiceModel.cs b/src/DevHive.Services/Models/Technology/ReadTechnologyServiceModel.cs new file mode 100644 index 0000000..99f4750 --- /dev/null +++ b/src/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/DevHive.Services/Models/Technology/TechnologyServiceModel.cs b/src/DevHive.Services/Models/Technology/TechnologyServiceModel.cs new file mode 100644 index 0000000..cb5c881 --- /dev/null +++ b/src/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/DevHive.Services/Models/Technology/UpdateTechnologyServiceModel.cs b/src/DevHive.Services/Models/Technology/UpdateTechnologyServiceModel.cs new file mode 100644 index 0000000..f4c7921 --- /dev/null +++ b/src/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/DevHive.Services/Options/JWTOptions.cs b/src/DevHive.Services/Options/JWTOptions.cs new file mode 100644 index 0000000..95458f5 --- /dev/null +++ b/src/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/DevHive.Services/Services/CloudinaryService.cs b/src/DevHive.Services/Services/CloudinaryService.cs new file mode 100644 index 0000000..57955a2 --- /dev/null +++ b/src/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/DevHive.Services/Services/CommentService.cs b/src/DevHive.Services/Services/CommentService.cs new file mode 100644 index 0000000..e2b54c4 --- /dev/null +++ b/src/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/DevHive.Services/Services/FeedService.cs b/src/DevHive.Services/Services/FeedService.cs new file mode 100644 index 0000000..f2f4a3a --- /dev/null +++ b/src/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/DevHive.Services/Services/LanguageService.cs b/src/DevHive.Services/Services/LanguageService.cs new file mode 100644 index 0000000..a6364d8 --- /dev/null +++ b/src/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/DevHive.Services/Services/PostService.cs b/src/DevHive.Services/Services/PostService.cs new file mode 100644 index 0000000..fe91f23 --- /dev/null +++ b/src/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/DevHive.Services/Services/RateService.cs b/src/DevHive.Services/Services/RateService.cs new file mode 100644 index 0000000..204c550 --- /dev/null +++ b/src/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/DevHive.Services/Services/RoleService.cs b/src/DevHive.Services/Services/RoleService.cs new file mode 100644 index 0000000..a8b8e17 --- /dev/null +++ b/src/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/DevHive.Services/Services/TechnologyService.cs b/src/DevHive.Services/Services/TechnologyService.cs new file mode 100644 index 0000000..6dd6286 --- /dev/null +++ b/src/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/DevHive.Services/Services/UserService.cs b/src/DevHive.Services/Services/UserService.cs new file mode 100644 index 0000000..22d5052 --- /dev/null +++ b/src/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 + } +} |
