aboutsummaryrefslogtreecommitdiff
path: root/src/Services/DevHive.Services
diff options
context:
space:
mode:
authorKamen Mladenov <kamen.d.mladenov@protonmail.com>2021-04-09 19:51:35 +0300
committerGitHub <noreply@github.com>2021-04-09 19:51:35 +0300
commit233f38915ba0079079233eff55434ef349c05c45 (patch)
tree6c5f69017865bcab87355e910c87339453da1406 /src/Services/DevHive.Services
parentf4a70c6430db923af9fa9958a11c2d6612cb52cc (diff)
parenta992357efcf1bc1ece81b95ecee5e05a0b73bfdc (diff)
downloadDevHive-main.tar
DevHive-main.tar.gz
DevHive-main.zip
Merge pull request #28 from Team-Kaleidoscope/devHEADv0.2mainheroku/main
Second stage: Complete
Diffstat (limited to 'src/Services/DevHive.Services')
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/CommentMappings.cs27
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/FeedMappings.cs11
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/LanguageMappings.cs22
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/PostMappings.cs32
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs21
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/RoleMapings.cs21
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/TechnologyMappings.cs21
-rw-r--r--src/Services/DevHive.Services/Configurations/Mapping/UserMappings.cs31
-rw-r--r--src/Services/DevHive.Services/DevHive.Services.csproj27
-rw-r--r--src/Services/DevHive.Services/Interfaces/ICloudService.cs14
-rw-r--r--src/Services/DevHive.Services/Interfaces/ICommentService.cs20
-rw-r--r--src/Services/DevHive.Services/Interfaces/IFeedService.cs11
-rw-r--r--src/Services/DevHive.Services/Interfaces/IFriendsService.cs11
-rw-r--r--src/Services/DevHive.Services/Interfaces/ILanguageService.cs19
-rw-r--r--src/Services/DevHive.Services/Interfaces/IPostService.cs20
-rw-r--r--src/Services/DevHive.Services/Interfaces/IProfilePictureService.cs30
-rw-r--r--src/Services/DevHive.Services/Interfaces/IRatingService.cs20
-rw-r--r--src/Services/DevHive.Services/Interfaces/IRoleService.cs17
-rw-r--r--src/Services/DevHive.Services/Interfaces/ITechnologyService.cs19
-rw-r--r--src/Services/DevHive.Services/Interfaces/IUserService.cs62
-rw-r--r--src/Services/DevHive.Services/Services/CloudinaryService.cs59
-rw-r--r--src/Services/DevHive.Services/Services/CommentService.cs169
-rw-r--r--src/Services/DevHive.Services/Services/FeedService.cs85
-rw-r--r--src/Services/DevHive.Services/Services/FriendsService.cs45
-rw-r--r--src/Services/DevHive.Services/Services/LanguageService.cs91
-rw-r--r--src/Services/DevHive.Services/Services/PostService.cs249
-rw-r--r--src/Services/DevHive.Services/Services/ProfilePictureService.cs101
-rw-r--r--src/Services/DevHive.Services/Services/RatingService.cs120
-rw-r--r--src/Services/DevHive.Services/Services/RoleService.cs71
-rw-r--r--src/Services/DevHive.Services/Services/TechnologyService.cs92
-rw-r--r--src/Services/DevHive.Services/Services/UserService.cs255
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
+ }
+}