diff options
| author | Syndamia <kamen.d.mladenov@protonmail.com> | 2021-03-13 15:07:51 +0200 |
|---|---|---|
| committer | Syndamia <kamen.d.mladenov@protonmail.com> | 2021-03-13 15:07:51 +0200 |
| commit | 75d57f305f2ed1ecf8b82bd62d4bb8f17c06303b (patch) | |
| tree | b721fe307d22a7a89f8ee13b3e557df4a2e2bbab | |
| parent | 503a23c04355624b133161c9356b139f2e4500f6 (diff) | |
| parent | 4add0831649f6e534d3883aa0e0e7f380d8042c7 (diff) | |
| download | DevHive-75d57f305f2ed1ecf8b82bd62d4bb8f17c06303b.tar DevHive-75d57f305f2ed1ecf8b82bd62d4bb8f17c06303b.tar.gz DevHive-75d57f305f2ed1ecf8b82bd62d4bb8f17c06303b.zip | |
Merge branch 'dev' of github.com:Team-Kaleidoscope/DevHive into unit_test_refactoring
60 files changed, 1824 insertions, 421 deletions
diff --git a/src/.editorconfig b/src/.editorconfig index 757e49d..9f0e74b 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -44,11 +44,12 @@ dotnet_diagnostic.IDE0055.severity = warning # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false + # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = true:refactoring dotnet_style_qualification_for_property = true:refactoring -dotnet_style_qualification_for_method = true:refactoring -dotnet_style_qualification_for_event = true:refactoring +dotnet_style_qualification_for_method = false:refactoring +dotnet_style_qualification_for_event = false:refactoring # Use language keywords instead of framework type names for type references dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion @@ -143,6 +144,7 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case # error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}' dotnet_diagnostic.RS2008.severity = none +dotnet_diagnostic.CS1591.severity = none # IDE0073: File header dotnet_diagnostic.IDE0073.severity = warning @@ -228,3 +230,6 @@ csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true + +[/Data/DevHive.Data/Migrations/**] +dotnet_diagnostic.IDE0055.severity = none diff --git a/src/Common/DevHive.Common.Models/DevHive.Common.csproj b/src/Common/DevHive.Common.Models/DevHive.Common.Models.csproj index f6d662c..a952c59 100644 --- a/src/Common/DevHive.Common.Models/DevHive.Common.csproj +++ b/src/Common/DevHive.Common.Models/DevHive.Common.Models.csproj @@ -4,7 +4,7 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> <PropertyGroup> <EnableNETAnalyzers>true</EnableNETAnalyzers> diff --git a/src/Common/DevHive.Common/DevHive.Common.csproj b/src/Common/DevHive.Common/DevHive.Common.csproj new file mode 100644 index 0000000..cd60d85 --- /dev/null +++ b/src/Common/DevHive.Common/DevHive.Common.csproj @@ -0,0 +1,11 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <ItemGroup> + <ProjectReference Include="..\DevHive.Common.Models\DevHive.Common.Models.csproj"/> + </ItemGroup> + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0"/> + </ItemGroup> +</Project> diff --git a/src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs b/src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs new file mode 100644 index 0000000..352a7d5 --- /dev/null +++ b/src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace DevHive.Common.Jwt.Interfaces +{ + public interface IJwtService + { + /// <summary> + /// The generation of a JWT, when a new user registers or log ins + /// Tokens have an expiration time of 7 days. + /// </summary> + /// <param name="userId">User's Guid</param> + /// <param name="username">Users's username</param> + /// <param name="roleNames">List of user's roles</param> + /// <returns>Return a new JWT, containing the user id, username and roles.</returns> + string GenerateJwtToken(Guid userId, string username, List<string> roleNames); + + /// <summary> + /// Checks whether the given user, gotten by the "id" property, + /// is the same user as the one in the token (unless 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> + /// <param name="userId">Guid of the user being validated</param> + /// <param name="rawToken">The raw token coming from the request</param> + /// <returns>Bool result of is the user authenticated to do an action</returns> + bool ValidateToken(Guid userId, string rawToken); + } +} diff --git a/src/Common/DevHive.Common/Jwt/JwtService.cs b/src/Common/DevHive.Common/Jwt/JwtService.cs new file mode 100644 index 0000000..9f316da --- /dev/null +++ b/src/Common/DevHive.Common/Jwt/JwtService.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using DevHive.Common.Jwt.Interfaces; +using Microsoft.IdentityModel.Tokens; + +namespace DevHive.Common.Jwt +{ + public class JwtService : IJwtService + { + private readonly string _validationIssuer; + private readonly string _audience; + private readonly byte[] _signingKey; + + public JwtService(byte[] signingKey, string validationIssuer, string audience) + { + this._signingKey = signingKey; + this._validationIssuer = validationIssuer; + this._audience = audience; + } + + public string GenerateJwtToken(Guid userId, string username, List<string> roleNames) + { + var securityKey = new SymmetricSecurityKey(this._signingKey); + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + + HashSet<Claim> claims = new() + { + new Claim("ID", $"{userId}"), + new Claim("Username", username) + }; + + foreach (var roleName in roleNames) + claims.Add(new Claim(ClaimTypes.Role, roleName)); + + SecurityTokenDescriptor securityTokenDescriptor = new() + { + Issuer = this._validationIssuer, + Audience = this._audience, + Subject = new ClaimsIdentity(claims), + Expires = DateTime.Today.AddDays(7), + SigningCredentials = credentials, + }; + + JwtSecurityTokenHandler tokenHandler = new(); + SecurityToken token = tokenHandler.CreateToken(securityTokenDescriptor); + + return tokenHandler.WriteToken(token); + } + + public bool ValidateToken(Guid userId, string rawToken) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var validationParameters = GetValidationParameters(); + string actualToken = rawToken.Remove(0, 7); + + IPrincipal principal = tokenHandler.ValidateToken(actualToken, validationParameters, out SecurityToken validatedToken); + JwtSecurityToken jwtToken = tokenHandler.ReadJwtToken(actualToken); + + if (!principal.Identity.IsAuthenticated) + return false; + else if (principal.IsInRole("Admin")) + return true; + else if (jwtToken.Claims.FirstOrDefault(x => x.Type == "ID").Value != userId.ToString()) + return false; + else + return true; + } + + private TokenValidationParameters GetValidationParameters() + { + return new TokenValidationParameters() + { + ValidateLifetime = true, + ValidateAudience = true, + ValidateIssuer = true, + ValidIssuer = this._validationIssuer, + ValidAudience = this._audience, + IssuerSigningKey = new SymmetricSecurityKey(this._signingKey) + }; + } + } +} diff --git a/src/Data/DevHive.Data.Models/DevHive.Data.Models.csproj b/src/Data/DevHive.Data.Models/DevHive.Data.Models.csproj index e9dc644..d249c77 100644 --- a/src/Data/DevHive.Data.Models/DevHive.Data.Models.csproj +++ b/src/Data/DevHive.Data.Models/DevHive.Data.Models.csproj @@ -5,6 +5,6 @@ <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0"/> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.3"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/Data/DevHive.Data.Models/Interfaces/IRating.cs b/src/Data/DevHive.Data.Models/Interfaces/IRating.cs index 1b081b0..f04f823 100644 --- a/src/Data/DevHive.Data.Models/Interfaces/IRating.cs +++ b/src/Data/DevHive.Data.Models/Interfaces/IRating.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using DevHive.Data.Models; @@ -5,10 +6,10 @@ namespace DevHive.Data.Models.Interfaces { public interface IRating : IModel { - // Post Post { get; set; } + bool IsLike { get; set; } - int Rate { get; set; } + Post Post { get; set; } - // HashSet<User> UsersThatRated { get; set; } + User User { get; set; } } } diff --git a/src/Data/DevHive.Data.Models/Post.cs b/src/Data/DevHive.Data.Models/Post.cs index 15b6b77..c95a8f1 100644 --- a/src/Data/DevHive.Data.Models/Post.cs +++ b/src/Data/DevHive.Data.Models/Post.cs @@ -19,7 +19,7 @@ namespace DevHive.Data.Models public List<Comment> Comments { get; set; } = new(); - public Rating Rating { get; set; } = new(); + public List<Rating> Ratings { get; set; } public List<PostAttachments> Attachments { get; set; } = new(); } diff --git a/src/Data/DevHive.Data.Models/Rating.cs b/src/Data/DevHive.Data.Models/Rating.cs index 13fdbce..8743a3e 100644 --- a/src/Data/DevHive.Data.Models/Rating.cs +++ b/src/Data/DevHive.Data.Models/Rating.cs @@ -6,12 +6,13 @@ namespace DevHive.Data.Models { public class Rating : IRating { + //if adding rating to comments change Post for intreface IRatable! public Guid Id { get; set; } - public Guid PostId { get; set; } + public User User { get; set; } public Post Post { get; set; } - public int Rate { get; set; } + public bool IsLike { get; set; } } } diff --git a/src/Data/DevHive.Data.Tests/DevHive.Data.Tests.csproj b/src/Data/DevHive.Data.Tests/DevHive.Data.Tests.csproj index 2af369f..25b2b39 100644 --- a/src/Data/DevHive.Data.Tests/DevHive.Data.Tests.csproj +++ b/src/Data/DevHive.Data.Tests/DevHive.Data.Tests.csproj @@ -5,11 +5,11 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.3"/> - <PackageReference Include="Moq" Version="4.16.0"/> + <PackageReference Include="Moq" Version="4.16.1"/> <PackageReference Include="NUnit" Version="3.13.1"/> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0"/> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\DevHive.Data\DevHive.Data.csproj"/> diff --git a/src/Data/DevHive.Data/DevHive.Data.csproj b/src/Data/DevHive.Data/DevHive.Data.csproj index 46928c6..fcdb7ae 100644 --- a/src/Data/DevHive.Data/DevHive.Data.csproj +++ b/src/Data/DevHive.Data/DevHive.Data.csproj @@ -12,7 +12,7 @@ </PackageReference> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0"/> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\DevHive.Data.Models\DevHive.Data.Models.csproj"/> diff --git a/src/Data/DevHive.Data/DevHiveContext.cs b/src/Data/DevHive.Data/DevHiveContext.cs index ece3439..b0bbdc6 100644 --- a/src/Data/DevHive.Data/DevHiveContext.cs +++ b/src/Data/DevHive.Data/DevHiveContext.cs @@ -88,11 +88,10 @@ namespace DevHive.Data builder.Entity<Rating>() .HasOne(x => x.Post) - .WithOne(x => x.Rating) - .HasForeignKey<Rating>(x => x.PostId); + .WithMany(x => x.Ratings); builder.Entity<Post>() - .HasOne(x => x.Rating) + .HasMany(x => x.Ratings) .WithOne(x => x.Post); /* User Rated Posts */ diff --git a/src/Data/DevHive.Data/Interfaces/IRatingRepository.cs b/src/Data/DevHive.Data/Interfaces/IRatingRepository.cs index b5aff88..db37d00 100644 --- a/src/Data/DevHive.Data/Interfaces/IRatingRepository.cs +++ b/src/Data/DevHive.Data/Interfaces/IRatingRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using DevHive.Data.Models; using DevHive.Data.Repositories.Interfaces; @@ -7,7 +8,10 @@ namespace DevHive.Data.Interfaces { public interface IRatingRepository : IRepository<Rating> { - Task<Rating> GetRatingByPostId(Guid postId); + Task<List<Rating>> GetRatingsByPostId(Guid postId); Task<bool> UserRatedPost(Guid userId, Guid postId); + Task<Rating> GetRatingByUserAndPostId(Guid userId, Guid postId); + + Task<bool> DoesRatingExist(Guid id); } } diff --git a/src/Data/DevHive.Data/Migrations/20210225193352_rating_migration.Designer.cs b/src/Data/DevHive.Data/Migrations/20210225193352_rating_migration.Designer.cs new file mode 100644 index 0000000..c0b67a6 --- /dev/null +++ b/src/Data/DevHive.Data/Migrations/20210225193352_rating_migration.Designer.cs @@ -0,0 +1,678 @@ +// <auto-generated /> +using System; +using DevHive.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace DevHive.Data.Migrations +{ + [DbContext(typeof(DevHiveContext))] + [Migration("20210225193352_rating_migration")] + partial class rating_migration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("DevHive.Data.Models.Comment", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid?>("CreatorId") + .HasColumnType("uuid"); + + b.Property<string>("Message") + .HasColumnType("text"); + + b.Property<Guid?>("PostId") + .HasColumnType("uuid"); + + b.Property<DateTime>("TimeCreated") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("PostId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Language", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Languages"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Post", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid?>("CreatorId") + .HasColumnType("uuid"); + + b.Property<int>("CurrentRating") + .HasColumnType("integer"); + + b.Property<string>("Message") + .HasColumnType("text"); + + b.Property<DateTime>("TimeCreated") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("DevHive.Data.Models.ProfilePicture", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("PictureURL") + .HasColumnType("text"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ProfilePicture"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Rating", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<bool>("IsLike") + .HasColumnType("boolean"); + + b.Property<Guid?>("PostId") + .HasColumnType("uuid"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("Rating"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Relational.PostAttachments", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("FileUrl") + .HasColumnType("text"); + + b.Property<Guid?>("PostId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.ToTable("PostAttachments"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Relational.RatedPost", b => + { + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<Guid>("PostId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "PostId"); + + b.HasIndex("PostId"); + + b.ToTable("RatedPosts"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Relational.UserRate", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<bool>("Liked") + .HasColumnType("boolean"); + + b.Property<Guid?>("PostId") + .HasColumnType("uuid"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("UserRates"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Role", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property<string>("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Technology", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Technologies"); + }); + + modelBuilder.Entity("DevHive.Data.Models.User", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("integer"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property<string>("FirstName") + .HasColumnType("text"); + + b.Property<string>("LastName") + .HasColumnType("text"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("PasswordHash") + .HasColumnType("text"); + + b.Property<string>("PhoneNumber") + .HasColumnType("text"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property<string>("SecurityStamp") + .HasColumnType("text"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.Property<string>("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("LanguageUser", b => + { + b.Property<Guid>("LanguagesId") + .HasColumnType("uuid"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid"); + + b.HasKey("LanguagesId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("LanguageUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("ClaimType") + .HasColumnType("text"); + + b.Property<string>("ClaimValue") + .HasColumnType("text"); + + b.Property<Guid>("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("ClaimType") + .HasColumnType("text"); + + b.Property<string>("ClaimValue") + .HasColumnType("text"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("text"); + + b.Property<string>("ProviderKey") + .HasColumnType("text"); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("text"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b => + { + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<Guid>("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b => + { + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<string>("LoginProvider") + .HasColumnType("text"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<string>("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("RoleUser", b => + { + b.Property<Guid>("RolesId") + .HasColumnType("uuid"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid"); + + b.HasKey("RolesId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("RoleUser"); + }); + + modelBuilder.Entity("TechnologyUser", b => + { + b.Property<Guid>("TechnologiesId") + .HasColumnType("uuid"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid"); + + b.HasKey("TechnologiesId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("TechnologyUser"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Comment", b => + { + b.HasOne("DevHive.Data.Models.User", "Creator") + .WithMany("Comments") + .HasForeignKey("CreatorId"); + + b.HasOne("DevHive.Data.Models.Post", "Post") + .WithMany("Comments") + .HasForeignKey("PostId"); + + b.Navigation("Creator"); + + b.Navigation("Post"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Post", b => + { + b.HasOne("DevHive.Data.Models.User", "Creator") + .WithMany("Posts") + .HasForeignKey("CreatorId"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("DevHive.Data.Models.ProfilePicture", b => + { + b.HasOne("DevHive.Data.Models.User", "User") + .WithOne("ProfilePicture") + .HasForeignKey("DevHive.Data.Models.ProfilePicture", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Rating", b => + { + b.HasOne("DevHive.Data.Models.Post", "Post") + .WithMany("Ratings") + .HasForeignKey("PostId"); + + b.HasOne("DevHive.Data.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Relational.PostAttachments", b => + { + b.HasOne("DevHive.Data.Models.Post", "Post") + .WithMany("Attachments") + .HasForeignKey("PostId"); + + b.Navigation("Post"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Relational.RatedPost", b => + { + b.HasOne("DevHive.Data.Models.Post", "Post") + .WithMany() + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevHive.Data.Models.User", "User") + .WithMany("RatedPosts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("DevHive.Data.Models.Relational.UserRate", b => + { + b.HasOne("DevHive.Data.Models.Post", "Post") + .WithMany() + .HasForeignKey("PostId"); + + b.HasOne("DevHive.Data.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("DevHive.Data.Models.User", b => + { + b.HasOne("DevHive.Data.Models.User", null) + .WithMany("Friends") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("LanguageUser", b => + { + b.HasOne("DevHive.Data.Models.Language", null) + .WithMany() + .HasForeignKey("LanguagesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevHive.Data.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b => + { + b.HasOne("DevHive.Data.Models.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b => + { + b.HasOne("DevHive.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b => + { + b.HasOne("DevHive.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b => + { + b.HasOne("DevHive.Data.Models.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevHive.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b => + { + b.HasOne("DevHive.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RoleUser", b => + { + b.HasOne("DevHive.Data.Models.Role", null) + .WithMany() + .HasForeignKey("RolesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevHive.Data.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TechnologyUser", b => + { + b.HasOne("DevHive.Data.Models.Technology", null) + .WithMany() + .HasForeignKey("TechnologiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DevHive.Data.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DevHive.Data.Models.Post", b => + { + b.Navigation("Attachments"); + + b.Navigation("Comments"); + + b.Navigation("Ratings"); + }); + + modelBuilder.Entity("DevHive.Data.Models.User", b => + { + b.Navigation("Comments"); + + b.Navigation("Friends"); + + b.Navigation("Posts"); + + b.Navigation("ProfilePicture"); + + b.Navigation("RatedPosts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Data/DevHive.Data/Migrations/20210225193352_rating_migration.cs b/src/Data/DevHive.Data/Migrations/20210225193352_rating_migration.cs new file mode 100644 index 0000000..da81cdc --- /dev/null +++ b/src/Data/DevHive.Data/Migrations/20210225193352_rating_migration.cs @@ -0,0 +1,139 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace DevHive.Data.Migrations +{ + public partial class rating_migration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rating_Posts_PostId", + table: "Rating"); + + migrationBuilder.DropIndex( + name: "IX_Rating_PostId", + table: "Rating"); + + migrationBuilder.DropColumn( + name: "Rate", + table: "Rating"); + + migrationBuilder.AlterColumn<Guid>( + name: "PostId", + table: "Rating", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn<bool>( + name: "IsLike", + table: "Rating", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn<Guid>( + name: "UserId", + table: "Rating", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn<int>( + name: "CurrentRating", + table: "Posts", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateIndex( + name: "IX_Rating_PostId", + table: "Rating", + column: "PostId"); + + migrationBuilder.CreateIndex( + name: "IX_Rating_UserId", + table: "Rating", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_Rating_AspNetUsers_UserId", + table: "Rating", + column: "UserId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Rating_Posts_PostId", + table: "Rating", + column: "PostId", + principalTable: "Posts", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rating_AspNetUsers_UserId", + table: "Rating"); + + migrationBuilder.DropForeignKey( + name: "FK_Rating_Posts_PostId", + table: "Rating"); + + migrationBuilder.DropIndex( + name: "IX_Rating_PostId", + table: "Rating"); + + migrationBuilder.DropIndex( + name: "IX_Rating_UserId", + table: "Rating"); + + migrationBuilder.DropColumn( + name: "IsLike", + table: "Rating"); + + migrationBuilder.DropColumn( + name: "UserId", + table: "Rating"); + + migrationBuilder.DropColumn( + name: "CurrentRating", + table: "Posts"); + + migrationBuilder.AlterColumn<Guid>( + name: "PostId", + table: "Rating", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddColumn<int>( + name: "Rate", + table: "Rating", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateIndex( + name: "IX_Rating_PostId", + table: "Rating", + column: "PostId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Rating_Posts_PostId", + table: "Rating", + column: "PostId", + principalTable: "Posts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Data/DevHive.Data/Migrations/DevHiveContextModelSnapshot.cs b/src/Data/DevHive.Data/Migrations/DevHiveContextModelSnapshot.cs index 8db3abf..a722cb2 100644 --- a/src/Data/DevHive.Data/Migrations/DevHiveContextModelSnapshot.cs +++ b/src/Data/DevHive.Data/Migrations/DevHiveContextModelSnapshot.cs @@ -69,6 +69,9 @@ namespace DevHive.Data.Migrations b.Property<Guid?>("CreatorId") .HasColumnType("uuid"); + b.Property<int>("CurrentRating") + .HasColumnType("integer"); + b.Property<string>("Message") .HasColumnType("text"); @@ -108,16 +111,20 @@ namespace DevHive.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property<Guid>("PostId") + b.Property<bool>("IsLike") + .HasColumnType("boolean"); + + b.Property<Guid?>("PostId") .HasColumnType("uuid"); - b.Property<int>("Rate") - .HasColumnType("integer"); + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); b.HasKey("Id"); - b.HasIndex("PostId") - .IsUnique(); + b.HasIndex("PostId"); + + b.HasIndex("UserId"); b.ToTable("Rating"); }); @@ -484,12 +491,16 @@ namespace DevHive.Data.Migrations modelBuilder.Entity("DevHive.Data.Models.Rating", b => { b.HasOne("DevHive.Data.Models.Post", "Post") - .WithOne("Rating") - .HasForeignKey("DevHive.Data.Models.Rating", "PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .WithMany("Ratings") + .HasForeignKey("PostId"); + + b.HasOne("DevHive.Data.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); b.Navigation("Post"); + + b.Navigation("User"); }); modelBuilder.Entity("DevHive.Data.Models.Relational.PostAttachments", b => @@ -644,7 +655,7 @@ namespace DevHive.Data.Migrations b.Navigation("Comments"); - b.Navigation("Rating"); + b.Navigation("Ratings"); }); modelBuilder.Entity("DevHive.Data.Models.User", b => diff --git a/src/Data/DevHive.Data/Repositories/PostRepository.cs b/src/Data/DevHive.Data/Repositories/PostRepository.cs index b6c5e37..0a88cf2 100644 --- a/src/Data/DevHive.Data/Repositories/PostRepository.cs +++ b/src/Data/DevHive.Data/Repositories/PostRepository.cs @@ -36,7 +36,7 @@ namespace DevHive.Data.Repositories .Include(x => x.Comments) .Include(x => x.Creator) .Include(x => x.Attachments) - // .Include(x => x.Rating) + .Include(x => x.Ratings) .FirstOrDefaultAsync(x => x.Id == id); } diff --git a/src/Data/DevHive.Data/Repositories/RatingRepository.cs b/src/Data/DevHive.Data/Repositories/RatingRepository.cs index b361693..9bb2368 100644 --- a/src/Data/DevHive.Data/Repositories/RatingRepository.cs +++ b/src/Data/DevHive.Data/Repositories/RatingRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DevHive.Data.Interfaces; @@ -19,17 +20,36 @@ namespace DevHive.Data.Repositories this._postRepository = postRepository; } - public async Task<Rating> GetRatingByPostId(Guid postId) + public override async Task<Rating> GetByIdAsync(Guid id) { return await this._context.Rating - .FirstOrDefaultAsync(x => x.Post.Id == postId); + .Include(x => x.User) + .Include(x => x.Post) + .FirstOrDefaultAsync(x => x.Id == id); + } + public async Task<List<Rating>> GetRatingsByPostId(Guid postId) + { + return await this._context.Rating + .Where(x => x.Post.Id == postId).ToListAsync(); } - public async Task<bool> UserRatedPost(Guid userId, Guid postId) { - return await this._context.UserRate + return await this._context.Rating .Where(x => x.Post.Id == postId) .AnyAsync(x => x.User.Id == userId); } + public async Task<Rating> GetRatingByUserAndPostId(Guid userId, Guid postId) + { + return await this._context.Rating + .FirstOrDefaultAsync(x => x.Post.Id == postId && x.User.Id == userId); + } + + public async Task<bool> DoesRatingExist(Guid id) + { + return await this._context.Rating + .AsNoTracking() + .AnyAsync(r => r.Id == id); + } } } + diff --git a/src/DevHive.code-workspace b/src/DevHive.code-workspace index 8511609..72c2301 100644 --- a/src/DevHive.code-workspace +++ b/src/DevHive.code-workspace @@ -15,7 +15,7 @@ { "name": "Common", "path": "./Common" - }, + } ], "settings": { "files.exclude": { @@ -23,14 +23,14 @@ "**/bin": true, "**/obj": true, - ".gitignore" : true, + ".gitignore": true }, "code-runner.fileDirectoryAsCwd": true, "dotnet-test-explorer.runInParallel": true, "dotnet-test-explorer.testProjectPath": "**/*.Tests.csproj", "omnisharp.enableEditorConfigSupport": true, "omnisharp.enableRoslynAnalyzers": true, - "prettier.useEditorConfig": true, + "prettier.useEditorConfig": true }, "launch": { "configurations": [ @@ -45,8 +45,8 @@ "stopAtEntry": false, "env": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - }, + } + } // { // "name": "Launch Data Tests", // "type": "coreclr", diff --git a/src/DevHive.sln b/src/DevHive.sln index 05bdcda..a202180 100644 --- a/src/DevHive.sln +++ b/src/DevHive.sln @@ -11,10 +11,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Data.Models", "Data EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Data.Tests", "Data\DevHive.Data.Tests\DevHive.Data.Tests.csproj", "{F056B3F1-B72D-4935-87EA-F7BFEA96AFB0}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{F2864A9D-70F1-452F-AAAC-AAFD8102ABAD}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Common", "Common\DevHive.Common.Models\DevHive.Common.csproj", "{5C3DFE9B-9690-475E-A0AE-D62315D38337}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{7CA79114-C359-4871-BFA7-0EA898B50AE4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Services", "Services\DevHive.Services\DevHive.Services.csproj", "{B5F22590-E3CE-4595-BE48-AA7F1797A6B8}"
@@ -31,6 +27,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Web.Models", "Web\D EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Web.Tests", "Web\DevHive.Web.Tests\DevHive.Web.Tests.csproj", "{608273FF-01ED-48B3-B912-66CCDBF5572E}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{49B4EAF5-8F45-493F-A25A-7F37DAAE6B1E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Common", "Common\DevHive.Common\DevHive.Common.csproj", "{AAEC0516-A943-449E-A1E8-E0628BFFAA2E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevHive.Common.Models", "Common\DevHive.Common.Models\DevHive.Common.Models.csproj", "{3D63C965-A734-45D6-B75D-AFDCAB511293}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -80,18 +82,6 @@ Global {F056B3F1-B72D-4935-87EA-F7BFEA96AFB0}.Release|x64.Build.0 = Release|Any CPU
{F056B3F1-B72D-4935-87EA-F7BFEA96AFB0}.Release|x86.ActiveCfg = Release|Any CPU
{F056B3F1-B72D-4935-87EA-F7BFEA96AFB0}.Release|x86.Build.0 = Release|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Debug|x64.ActiveCfg = Debug|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Debug|x64.Build.0 = Debug|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Debug|x86.ActiveCfg = Debug|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Debug|x86.Build.0 = Debug|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Release|Any CPU.Build.0 = Release|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Release|x64.ActiveCfg = Release|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Release|x64.Build.0 = Release|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Release|x86.ActiveCfg = Release|Any CPU
- {5C3DFE9B-9690-475E-A0AE-D62315D38337}.Release|x86.Build.0 = Release|Any CPU
{B5F22590-E3CE-4595-BE48-AA7F1797A6B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5F22590-E3CE-4595-BE48-AA7F1797A6B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5F22590-E3CE-4595-BE48-AA7F1797A6B8}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -164,17 +154,42 @@ Global {608273FF-01ED-48B3-B912-66CCDBF5572E}.Release|x64.Build.0 = Release|Any CPU
{608273FF-01ED-48B3-B912-66CCDBF5572E}.Release|x86.ActiveCfg = Release|Any CPU
{608273FF-01ED-48B3-B912-66CCDBF5572E}.Release|x86.Build.0 = Release|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Debug|x64.Build.0 = Debug|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Debug|x86.Build.0 = Debug|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Release|x64.ActiveCfg = Release|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Release|x64.Build.0 = Release|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Release|x86.ActiveCfg = Release|Any CPU
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E}.Release|x86.Build.0 = Release|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Debug|x64.Build.0 = Debug|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Debug|x86.Build.0 = Debug|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Release|x64.ActiveCfg = Release|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Release|x64.Build.0 = Release|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Release|x86.ActiveCfg = Release|Any CPU
+ {3D63C965-A734-45D6-B75D-AFDCAB511293}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{70D0903D-C65F-4600-B6F8-F7BD00500A51} = {0C2AC7A9-AC68-4668-B88E-9370C596F498}
{56F85916-3955-4558-8809-376D20902B94} = {0C2AC7A9-AC68-4668-B88E-9370C596F498}
{F056B3F1-B72D-4935-87EA-F7BFEA96AFB0} = {0C2AC7A9-AC68-4668-B88E-9370C596F498}
- {5C3DFE9B-9690-475E-A0AE-D62315D38337} = {F2864A9D-70F1-452F-AAAC-AAFD8102ABAD}
{B5F22590-E3CE-4595-BE48-AA7F1797A6B8} = {7CA79114-C359-4871-BFA7-0EA898B50AE4}
{2FFF985B-A26F-443D-A159-62ED2FD5A2BC} = {7CA79114-C359-4871-BFA7-0EA898B50AE4}
{6E58003B-E5E8-4AA4-8F70-A9442BBFC110} = {7CA79114-C359-4871-BFA7-0EA898B50AE4}
{A6D35BD9-A2A4-4937-89A8-DCB0D610B04A} = {768A592D-58EA-4CD3-A053-2E8F2DC7708A}
{D8C898F7-A0DE-4939-8708-3D4A5C383EFC} = {768A592D-58EA-4CD3-A053-2E8F2DC7708A}
{608273FF-01ED-48B3-B912-66CCDBF5572E} = {768A592D-58EA-4CD3-A053-2E8F2DC7708A}
+ {AAEC0516-A943-449E-A1E8-E0628BFFAA2E} = {49B4EAF5-8F45-493F-A25A-7F37DAAE6B1E}
+ {3D63C965-A734-45D6-B75D-AFDCAB511293} = {49B4EAF5-8F45-493F-A25A-7F37DAAE6B1E}
EndGlobalSection
EndGlobal
diff --git a/src/Dockerfile b/src/Dockerfile index f99804c..0491463 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -1,16 +1,11 @@ -FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base -FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build -EXPOSE 80 +FROM mcr.microsoft.com/dotnet/sdk:5.0 AS sdk -COPY . /app -WORKDIR /app +COPY . ./Build +WORKDIR /Build +RUN [ "dotnet", "publish", "-f", "net5.0", "-c", "Release", "Web/DevHive.Web/DevHive.Web.csproj", "-o", "/Out"] -RUN dotnet restore -RUN dotnet build -c Release -o /app/build +FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime +COPY --from=sdk /Out /App -FROM build AS publish -RUN dotnet publish "DevHive.sln" -c Release -o /app/publish - -FROM base AS final -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "DevHive.Web.dll"] +WORKDIR /App +ENTRYPOINT [ "dotnet", "DevHive.Web.dll" ] diff --git a/src/Services/DevHive.Services.Models/DevHive.Services.Models.csproj b/src/Services/DevHive.Services.Models/DevHive.Services.Models.csproj index 914efe0..a55972a 100644 --- a/src/Services/DevHive.Services.Models/DevHive.Services.Models.csproj +++ b/src/Services/DevHive.Services.Models/DevHive.Services.Models.csproj @@ -4,9 +4,10 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.Models.csproj"/> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/Services/DevHive.Services.Models/Post/Rating/RatePostServiceModel.cs b/src/Services/DevHive.Services.Models/Post/Rating/CreateRatingServiceModel.cs index d4eb7bd..aaeab73 100644 --- a/src/Services/DevHive.Services.Models/Post/Rating/RatePostServiceModel.cs +++ b/src/Services/DevHive.Services.Models/Post/Rating/CreateRatingServiceModel.cs @@ -2,12 +2,12 @@ using System; namespace DevHive.Services.Models.Post.Rating { - public class RatePostServiceModel + public class CreateRatingServiceModel { public Guid UserId { get; set; } public Guid PostId { get; set; } - public bool Liked { get; set; } + public bool IsLike { get; set; } } } diff --git a/src/Services/DevHive.Services.Models/Post/Rating/ReadPostRatingServiceModel.cs b/src/Services/DevHive.Services.Models/Post/Rating/ReadRatingServiceModel.cs index 8c73aaf..86b4957 100644 --- a/src/Services/DevHive.Services.Models/Post/Rating/ReadPostRatingServiceModel.cs +++ b/src/Services/DevHive.Services.Models/Post/Rating/ReadRatingServiceModel.cs @@ -2,14 +2,14 @@ using System; namespace DevHive.Services.Models.Post.Rating { - public class ReadPostRatingServiceModel + public class ReadRatingServiceModel { public Guid Id { get; set; } public Guid PostId { get; set; } - public int Likes { get; set; } + public Guid UserId { get; set; } - public int Dislikes { get; set; } + public bool IsLike { get; set; } } } diff --git a/src/Services/DevHive.Services.Models/Post/Rating/UpdateRatingServiceModel.cs b/src/Services/DevHive.Services.Models/Post/Rating/UpdateRatingServiceModel.cs new file mode 100644 index 0000000..1ea8d8f --- /dev/null +++ b/src/Services/DevHive.Services.Models/Post/Rating/UpdateRatingServiceModel.cs @@ -0,0 +1,15 @@ +using System; + +namespace DevHive.Services.Models.Post.Rating +{ + public class UpdateRatingServiceModel + { + public Guid Id { get; set; } + + public Guid UserId { get; set; } + + public Guid PostId { get; set; } + + public bool IsLike { get; set; } + } +} diff --git a/src/Services/DevHive.Services.Models/Post/ReadPostServiceModel.cs b/src/Services/DevHive.Services.Models/Post/ReadPostServiceModel.cs index a7aa882..33d6520 100644 --- a/src/Services/DevHive.Services.Models/Post/ReadPostServiceModel.cs +++ b/src/Services/DevHive.Services.Models/Post/ReadPostServiceModel.cs @@ -21,6 +21,7 @@ namespace DevHive.Services.Models.Post public List<IdModel> Comments { get; set; } = new(); public List<string> FileUrls { get; set; } - // public List<FileContentResult> Files { get; set; } = new(); + + public int CurrentRating { get; set; } } } diff --git a/src/Services/DevHive.Services.Tests/DevHive.Services.Tests.csproj b/src/Services/DevHive.Services.Tests/DevHive.Services.Tests.csproj index bdfb2bb..d85eea2 100644 --- a/src/Services/DevHive.Services.Tests/DevHive.Services.Tests.csproj +++ b/src/Services/DevHive.Services.Tests/DevHive.Services.Tests.csproj @@ -5,14 +5,16 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.3"/> - <PackageReference Include="Moq" Version="4.16.0"/> + <PackageReference Include="Moq" Version="4.16.1"/> <PackageReference Include="NUnit" Version="3.13.1"/> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0"/> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\DevHive.Services\DevHive.Services.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.Models.csproj"/> </ItemGroup> <PropertyGroup> <EnableNETAnalyzers>true</EnableNETAnalyzers> diff --git a/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs index fefa6d8..4534511 100644 --- a/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs +++ b/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs @@ -8,6 +8,14 @@ namespace DevHive.Services.Configurations.Mapping { 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/DevHive.Services.csproj b/src/Services/DevHive.Services/DevHive.Services.csproj index 650a304..f51c1b6 100644 --- a/src/Services/DevHive.Services/DevHive.Services.csproj +++ b/src/Services/DevHive.Services/DevHive.Services.csproj @@ -12,11 +12,13 @@ <PackageReference Include="AutoMapper" Version="10.1.1"/> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1"/> <PackageReference Include="CloudinaryDotNet" Version="1.14.0"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </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> diff --git a/src/Services/DevHive.Services/Interfaces/IRateService.cs b/src/Services/DevHive.Services/Interfaces/IRateService.cs deleted file mode 100644 index 359ef55..0000000 --- a/src/Services/DevHive.Services/Interfaces/IRateService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Threading.Tasks; -using DevHive.Data.Models; -using DevHive.Services.Models.Post.Rating; - -namespace DevHive.Services.Interfaces -{ - public interface IRateService - { - Task<ReadPostRatingServiceModel> RatePost(RatePostServiceModel ratePostServiceModel); - - bool HasUserRatedThisPost(User user, Post post); - } -} diff --git a/src/Services/DevHive.Services/Interfaces/IRatingService.cs b/src/Services/DevHive.Services/Interfaces/IRatingService.cs new file mode 100644 index 0000000..beea821 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IRatingService.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; +using DevHive.Data.Models; +using DevHive.Services.Models.Post.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); + + Task<bool> HasUserRatedThisPost(Guid userId, Guid postId); + } +} diff --git a/src/Services/DevHive.Services/Interfaces/IUserService.cs b/src/Services/DevHive.Services/Interfaces/IUserService.cs index 4a9ffc8..a55f9dd 100644 --- a/src/Services/DevHive.Services/Interfaces/IUserService.cs +++ b/src/Services/DevHive.Services/Interfaces/IUserService.cs @@ -7,19 +7,64 @@ 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> + /// Uploads the given picture and assigns it's link to the user in the database + /// Requires authenticated user + /// </summary> + /// <param name="updateProfilePictureServiceModel">Contains User's Guid and the new picture to be updated</param> + /// <returns>The new picture's URL</returns> Task<ProfilePictureServiceModel> UpdateProfilePicture(UpdateProfilePictureServiceModel updateProfilePictureServiceModel); + /// <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); - Task<bool> ValidJWT(Guid id, string rawTokenData); - + /// <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/Options/JwtOptions.cs b/src/Services/DevHive.Services/Options/JwtOptions.cs deleted file mode 100644 index d973f45..0000000 --- a/src/Services/DevHive.Services/Options/JwtOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.Options; - -namespace DevHive.Services.Options -{ - public class JwtOptions - { - public JwtOptions(string secret) - { - this.Secret = secret; - } - - public string Secret { get; init; } - } -} diff --git a/src/Services/DevHive.Services/Services/RateService.cs b/src/Services/DevHive.Services/Services/RateService.cs deleted file mode 100644 index caf4b80..0000000 --- a/src/Services/DevHive.Services/Services/RateService.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using DevHive.Data.Interfaces; -using DevHive.Data.Models; -using DevHive.Services.Interfaces; -using DevHive.Services.Models.Post.Rating; - -namespace DevHive.Services.Services -{ - public class RateService : IRateService - { - private readonly IPostRepository _postRepository; - private readonly IUserRepository _userRepository; - private readonly IRatingRepository _ratingRepository; - private readonly IMapper _mapper; - - public RateService(IPostRepository postRepository, IRatingRepository ratingRepository, IUserRepository userRepository, IMapper mapper) - { - this._postRepository = postRepository; - this._ratingRepository = ratingRepository; - this._userRepository = userRepository; - this._mapper = mapper; - } - - public async Task<ReadPostRatingServiceModel> RatePost(RatePostServiceModel ratePostServiceModel) - { - throw new NotImplementedException(); - // if (!await this._postRepository.DoesPostExist(ratePostServiceModel.PostId)) - // throw new ArgumentException("Post does not exist!"); - - // if (!await this._userRepository.DoesUserExistAsync(ratePostServiceModel.UserId)) - // throw new ArgumentException("User does not exist!"); - - // Post post = await this._postRepository.GetByIdAsync(ratePostServiceModel.PostId); - // User user = await this._userRepository.GetByIdAsync(ratePostServiceModel.UserId); - - // if (this.HasUserRatedThisPost(user, post)) - // throw new ArgumentException("You can't rate the same post more then one(duh, amigo)"); - - // this.Rate(user, post, ratePostServiceModel.Liked); - - // bool success = await this._ratingRepository.EditAsync(post.Rating.Id, post.Rating); - // if (!success) - // throw new InvalidOperationException("Unable to rate the post!"); - - // Rating newRating = await this._ratingRepository.GetByIdAsync(post.Rating.Id); - // return this._mapper.Map<ReadPostRatingServiceModel>(newRating); - } - - public async Task<ReadPostRatingServiceModel> RemoveUserRateFromPost(Guid userId, Guid postId) - { - throw new NotImplementedException(); - // Post post = await this._postRepository.GetByIdAsync(postId); - // User user = await this._userRepository.GetByIdAsync(userId); - - // if (!this.HasUserRatedThisPost(user, post)) - // throw new ArgumentException("You haven't rated this post, lmao!"); - } - - public bool HasUserRatedThisPost(User user, Post post) - { - throw new NotImplementedException(); - // return post.Rating.UsersThatRated - // .Any(x => x.Id == user.Id); - } - - private void Rate(User user, Post post, bool liked) - { - throw new NotImplementedException(); - // if (liked) - // post.Rating.Rate++; - // else - // post.Rating.Rate--; - - // post.Rating.UsersThatRated.Add(user); - } - } -} diff --git a/src/Services/DevHive.Services/Services/RatingService.cs b/src/Services/DevHive.Services/Services/RatingService.cs new file mode 100644 index 0000000..6ddba1c --- /dev/null +++ b/src/Services/DevHive.Services/Services/RatingService.cs @@ -0,0 +1,125 @@ +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.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Post.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; + + 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 ArgumentException("Post does not exist!"); + + 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) ?? + throw new ArgumentException("The rating does not exist"); + + 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) ?? + throw new ArgumentException("The rating does not exist"); + + 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.GetByIdAsync(updateRatingServiceModel.Id) ?? + throw new ArgumentException("Rating does not exist!"); + + User user = await this._userRepository.GetByIdAsync(updateRatingServiceModel.UserId) ?? + throw new ArgumentException("User does not exist!"); + + if (!await this._ratingRepository.UserRatedPost(updateRatingServiceModel.UserId, updateRatingServiceModel.PostId)) + throw new ArgumentException("User has not rated the post!"); + + 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 ArgumentException("Rating does not exist!"); + + Rating rating = await this._ratingRepository.GetByIdAsync(ratingId); + return await this._ratingRepository.DeleteAsync(rating); + } + #endregion + + public async Task<bool> HasUserRatedThisPost(Guid userId, Guid postId) + { + return await this._ratingRepository + .UserRatedPost(userId, postId); + } + } +} diff --git a/src/Services/DevHive.Services/Services/UserService.cs b/src/Services/DevHive.Services/Services/UserService.cs index dfd45cc..4f74b06 100644 --- a/src/Services/DevHive.Services/Services/UserService.cs +++ b/src/Services/DevHive.Services/Services/UserService.cs @@ -1,21 +1,15 @@ using AutoMapper; -using DevHive.Services.Options; using DevHive.Services.Models.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; using System.Linq; -using DevHive.Common.Models.Misc; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; +using DevHive.Common.Jwt.Interfaces; namespace DevHive.Services.Services { @@ -26,31 +20,27 @@ namespace DevHive.Services.Services private readonly ILanguageRepository _languageRepository; private readonly ITechnologyRepository _technologyRepository; private readonly IMapper _userMapper; - private readonly JwtOptions _jwtOptions; private readonly ICloudService _cloudService; + private readonly IJwtService _jwtService; public UserService(IUserRepository userRepository, ILanguageRepository languageRepository, IRoleRepository roleRepository, ITechnologyRepository technologyRepository, IMapper mapper, - JwtOptions jwtOptions, - ICloudService cloudService) + ICloudService cloudService, + IJwtService jwtService) { this._userRepository = userRepository; this._roleRepository = roleRepository; this._userMapper = mapper; - this._jwtOptions = jwtOptions; this._languageRepository = languageRepository; this._technologyRepository = technologyRepository; this._cloudService = cloudService; + this._jwtService = jwtService; } #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)) @@ -61,12 +51,10 @@ namespace DevHive.Services.Services if (!await this._userRepository.VerifyPassword(user, loginModel.Password)) throw new ArgumentException("Incorrect password!"); - return new TokenModel(WriteJWTSecurityToken(user.Id, user.UserName, user.Roles)); + List<string> roleNames = user.Roles.Select(x => x.Name).ToList(); + return new TokenModel(this._jwtService.GenerateJwtToken(user.Id, user.UserName, roleNames)); } - /// <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)) @@ -86,7 +74,9 @@ namespace DevHive.Services.Services throw new ArgumentException("Unable to add role to user"); User createdUser = await this._userRepository.GetByUsernameAsync(registerModel.UserName); - return new TokenModel(WriteJWTSecurityToken(createdUser.Id, createdUser.UserName, createdUser.Roles)); + + List<string> roleNames = createdUser.Roles.Select(x => x.Name).ToList(); + return new TokenModel(this._jwtService.GenerateJwtToken(createdUser.Id, createdUser.UserName, roleNames)); } #endregion @@ -130,9 +120,6 @@ namespace DevHive.Services.Services 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); @@ -169,57 +156,7 @@ namespace DevHive.Services.Services #region Validations /// <summary> - /// Checks whether the given user, gotten by the "id" property, - /// is the same user as the one in the token (unless 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(UserService.GetClaimTypeValues("ID", jwt.Claims).First()); - List<string> jwtRoleNames = UserService.GetClaimTypeValues("role", jwt.Claims); - - User user = await this._userRepository.GetByIdAsync(jwtUserID) - ?? throw new ArgumentException("User does not exist!"); - - /* Check if he is an admin */ - if (user.Roles.Any(x => x.Name == Role.AdminRole)) - return true; - - if (!jwtRoleNames.Contains(Role.AdminRole) && 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 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; - } - - /// <summary> - /// Checks whether the user in the model exists - /// and whether the username in the model is already taken. + /// 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) @@ -241,38 +178,6 @@ namespace DevHive.Services.Services 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 @@ -294,9 +199,13 @@ namespace DevHive.Services.Services user.Roles.Add(admin); await this._userRepository.EditAsync(user.Id, user); - User newUser = await this._userRepository.GetByIdAsync(userId); + User createdUser = await this._userRepository.GetByIdAsync(userId); + List<string> roleNames = createdUser + .Roles + .Select(x => x.Name) + .ToList(); - return new TokenModel(WriteJWTSecurityToken(newUser.Id, newUser.UserName, newUser.Roles)); + return new TokenModel(this._jwtService.GenerateJwtToken(createdUser.Id, createdUser.UserName, roleNames)); } private async Task PopulateUserModel(User user, UpdateUserServiceModel updateUserServiceModel) diff --git a/src/Web/DevHive.Web.Models/DevHive.Web.Models.csproj b/src/Web/DevHive.Web.Models/DevHive.Web.Models.csproj index 64d0bd0..9d62eee 100644 --- a/src/Web/DevHive.Web.Models/DevHive.Web.Models.csproj +++ b/src/Web/DevHive.Web.Models/DevHive.Web.Models.csproj @@ -3,10 +3,11 @@ <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> - <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.Models.csproj"/> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/Web/DevHive.Web.Models/Post/ReadPostWebModel.cs b/src/Web/DevHive.Web.Models/Post/ReadPostWebModel.cs index 3ae93aa..d6ea1f4 100644 --- a/src/Web/DevHive.Web.Models/Post/ReadPostWebModel.cs +++ b/src/Web/DevHive.Web.Models/Post/ReadPostWebModel.cs @@ -21,5 +21,7 @@ namespace DevHive.Web.Models.Post public List<IdModel> Comments { get; set; } public List<string> FileUrls { get; set; } + + public int CurrentRating { get; set; } } } diff --git a/src/Web/DevHive.Web.Models/Rating/RatePostWebModel.cs b/src/Web/DevHive.Web.Models/Rating/CreateRatingWebModel.cs index cbba4ab..abbb702 100644 --- a/src/Web/DevHive.Web.Models/Rating/RatePostWebModel.cs +++ b/src/Web/DevHive.Web.Models/Rating/CreateRatingWebModel.cs @@ -2,10 +2,10 @@ using System; namespace DevHive.Web.Models.Rating { - public class RatePostWebModel + public class CreateRatingWebModel { public Guid PostId { get; set; } - public bool Liked { get; set; } + public bool IsLike { get; set; } } } diff --git a/src/Web/DevHive.Web.Models/Rating/ReadPostRatingWebModel.cs b/src/Web/DevHive.Web.Models/Rating/ReadRatingWebModel.cs index 8afd57e..40f4c6f 100644 --- a/src/Web/DevHive.Web.Models/Rating/ReadPostRatingWebModel.cs +++ b/src/Web/DevHive.Web.Models/Rating/ReadRatingWebModel.cs @@ -2,14 +2,14 @@ using System; namespace DevHive.Web.Models.Rating { - public class ReadPostRatingWebModel + public class ReadRatingWebModel { public Guid Id { get; set; } public Guid PostId { get; set; } - public int Likes { get; set; } + public Guid UserId { get; set; } - public int Dislikes { get; set; } + public bool IsLike { get; set; } } } diff --git a/src/Web/DevHive.Web.Models/Rating/UpdateRatingWebModel.cs b/src/Web/DevHive.Web.Models/Rating/UpdateRatingWebModel.cs new file mode 100644 index 0000000..425c3e1 --- /dev/null +++ b/src/Web/DevHive.Web.Models/Rating/UpdateRatingWebModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DevHive.Web.Models.Rating +{ + public class UpdateRatingWebModel + { + public Guid Id { get; set; } + + public Guid PostId { get; set; } + + public bool IsLike { get; set; } + } +} diff --git a/src/Web/DevHive.Web.Tests/DevHive.Web.Tests.csproj b/src/Web/DevHive.Web.Tests/DevHive.Web.Tests.csproj index 465698c..5099119 100644 --- a/src/Web/DevHive.Web.Tests/DevHive.Web.Tests.csproj +++ b/src/Web/DevHive.Web.Tests/DevHive.Web.Tests.csproj @@ -4,14 +4,16 @@ <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> - <PackageReference Include="Moq" Version="4.16.0"/> + <PackageReference Include="Moq" Version="4.16.1"/> <PackageReference Include="NUnit" Version="3.13.1"/> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0"/> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\DevHive.Web\DevHive.Web.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.Models.csproj"/> </ItemGroup> <PropertyGroup> <EnableNETAnalyzers>true</EnableNETAnalyzers> diff --git a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureAutoMapper.cs b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureAutoMapper.cs index 0b8194e..cd5679f 100644 --- a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureAutoMapper.cs +++ b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureAutoMapper.cs @@ -1,4 +1,7 @@ using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; using AutoMapper; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureDependencyInjection.cs b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureDependencyInjection.cs index c547951..a0d0979 100644 --- a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureDependencyInjection.cs +++ b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureDependencyInjection.cs @@ -1,3 +1,6 @@ +using System.Text; +using DevHive.Common.Jwt; +using DevHive.Common.Jwt.Interfaces; using DevHive.Data.Interfaces; using DevHive.Data.Repositories; using DevHive.Services.Interfaces; @@ -27,12 +30,20 @@ namespace DevHive.Web.Configurations.Extensions services.AddTransient<IPostService, PostService>(); services.AddTransient<ICommentService, CommentService>(); services.AddTransient<IFeedService, FeedService>(); + services.AddTransient<IRatingService, RatingService>(); + services.AddTransient<ICloudService, CloudinaryService>(options => new CloudinaryService( cloudName: configuration.GetSection("Cloud").GetSection("cloudName").Value, apiKey: configuration.GetSection("Cloud").GetSection("apiKey").Value, apiSecret: configuration.GetSection("Cloud").GetSection("apiSecret").Value)); - services.AddTransient<IRateService, RateService>(); + + services.AddSingleton<IJwtService, JwtService>(options => + new JwtService( + signingKey: Encoding.ASCII.GetBytes(configuration.GetSection("Jwt").GetSection("signingKey").Value), + validationIssuer: configuration.GetSection("Jwt").GetSection("validationIssuer").Value, + audience: configuration.GetSection("Jwt").GetSection("audience").Value)); + services.AddTransient<IRatingService, RatingService>(); } } } diff --git a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureJwt.cs b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureJwt.cs index 8d387bd..18127bc 100644 --- a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureJwt.cs +++ b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureJwt.cs @@ -1,6 +1,5 @@ using System.Text; using System.Threading.Tasks; -using DevHive.Services.Options; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -12,15 +11,10 @@ namespace DevHive.Web.Configurations.Extensions { public static void JWTConfiguration(this IServiceCollection services, IConfiguration configuration) { - services.AddSingleton(new JwtOptions(configuration - .GetSection("AppSettings") - .GetSection("Secret") - .Value)); - // Get key from appsettings.json - var key = Encoding.ASCII.GetBytes(configuration - .GetSection("AppSettings") - .GetSection("Secret") + var signingKey = Encoding.ASCII.GetBytes(configuration + .GetSection("Jwt") + .GetSection("signingKey") .Value); // Setup Jwt Authentication @@ -42,7 +36,7 @@ namespace DevHive.Web.Configurations.Extensions x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters { - IssuerSigningKey = new SymmetricSecurityKey(key), + IssuerSigningKey = new SymmetricSecurityKey(signingKey), ValidateIssuer = false, ValidateAudience = false }; diff --git a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureSwagger.cs b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureSwagger.cs index a0641ab..9387561 100644 --- a/src/Web/DevHive.Web/Configurations/Extensions/ConfigureSwagger.cs +++ b/src/Web/DevHive.Web/Configurations/Extensions/ConfigureSwagger.cs @@ -1,23 +1,65 @@ +using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using NSwag; +using NSwag.Generation.Processors.Security; namespace DevHive.Web.Configurations.Extensions { public static class SwaggerExtensions { +#pragma warning disable S1075 + private const string LicenseName = "GPL-3.0 License"; + private const string LicenseUri = "https://github.com/Team-Kaleidoscope/DevHive/blob/main/LICENSE"; + private const string TermsOfServiceUri = "https://example.com/terms"; +#pragma warning restore S1075 + public static void SwaggerConfiguration(this IServiceCollection services) { - services.AddSwaggerGen(c => + services.AddOpenApiDocument(c => { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" }); + c.GenerateXmlObjects = true; + c.UseControllerSummaryAsTagDescription = true; + + c.AllowNullableBodyParameters = false; + c.Description = "DevHive Social Media's API Endpoints"; + + c.PostProcess = doc => + { + doc.Info.Version = "v0.1"; + doc.Info.Title = "API"; + doc.Info.Description = "DevHive Social Media's first official API release"; + doc.Info.TermsOfService = TermsOfServiceUri; + doc.Info.License = new NSwag.OpenApiLicense + { + Name = LicenseName, + Url = LicenseUri + }; + }; + + c.AddSecurity("Bearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme + { + Type = OpenApiSecuritySchemeType.ApiKey, + Name = "Authorization", + In = OpenApiSecurityApiKeyLocation.Header, + Description = "Type into the textbox: Bearer {your JWT token}." + }); + c.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("Bearer")); }); } public static void UseSwaggerConfiguration(this IApplicationBuilder app) { - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1")); + app.UseOpenApi(c => + { + c.DocumentName = "v0.1"; + }); + app.UseSwaggerUi3(c => + { + c.DocumentTitle = "DevHive API"; + c.EnableTryItOut = false; + c.DocExpansion = "list"; + }); } } -}
\ No newline at end of file +} diff --git a/src/Web/DevHive.Web/Configurations/Mapping/RatingMappings.cs b/src/Web/DevHive.Web/Configurations/Mapping/RatingMappings.cs index a29e06c..23c3eeb 100644 --- a/src/Web/DevHive.Web/Configurations/Mapping/RatingMappings.cs +++ b/src/Web/DevHive.Web/Configurations/Mapping/RatingMappings.cs @@ -8,9 +8,11 @@ namespace DevHive.Web.Configurations.Mapping { public RatingMappings() { - CreateMap<RatePostWebModel, RatePostServiceModel>(); + CreateMap<CreateRatingWebModel, CreateRatingServiceModel>(); - CreateMap<ReadPostRatingServiceModel, ReadPostRatingWebModel>(); + CreateMap<ReadRatingServiceModel, ReadRatingWebModel>(); + + CreateMap<UpdateRatingWebModel, UpdateRatingServiceModel>(); } } } diff --git a/src/Web/DevHive.Web/Controllers/CommentController.cs b/src/Web/DevHive.Web/Controllers/CommentController.cs index c38e300..8fa3577 100644 --- a/src/Web/DevHive.Web/Controllers/CommentController.cs +++ b/src/Web/DevHive.Web/Controllers/CommentController.cs @@ -6,9 +6,13 @@ using DevHive.Web.Models.Comment; using DevHive.Services.Models.Comment; using Microsoft.AspNetCore.Authorization; using DevHive.Services.Interfaces; +using DevHive.Common.Jwt.Interfaces; namespace DevHive.Web.Controllers { + /// <summary> + /// All endpoints for interacting with the comments layer + /// </summary> [ApiController] [Route("/api/[controller]")] [Authorize(Roles = "User,Admin")] @@ -16,16 +20,28 @@ namespace DevHive.Web.Controllers { private readonly ICommentService _commentService; private readonly IMapper _commentMapper; + private readonly IJwtService _jwtService; - public CommentController(ICommentService commentService, IMapper commentMapper) + public CommentController(ICommentService commentService, IMapper commentMapper, IJwtService jwtService) { this._commentService = commentService; this._commentMapper = commentMapper; + this._jwtService = jwtService; } + /// <summary> + /// Create a comment and attach it to a post + /// </summary> + /// <param name="userId">The useer's Id</param> + /// <param name="createCommentWebModel">The new comment's parametars</param> + /// <param name="authorization">JWT Bearer token</param> + /// <returns>The comment's Id</returns> [HttpPost] public async Task<IActionResult> AddComment(Guid userId, [FromBody] CreateCommentWebModel createCommentWebModel, [FromHeader] string authorization) { + if (!this._jwtService.ValidateToken(userId, authorization)) + return new UnauthorizedResult(); + if (!await this._commentService.ValidateJwtForCreating(userId, authorization)) return new UnauthorizedResult(); @@ -40,20 +56,32 @@ namespace DevHive.Web.Controllers new OkObjectResult(new { Id = id }); } + /// <summary> + /// Query comment's data by it's Id + /// </summary> + /// <param name="commentId">The comment's Id</param> + /// <returns>Full data model of the comment</returns> [HttpGet] [AllowAnonymous] - public async Task<IActionResult> GetCommentById(Guid id) + public async Task<IActionResult> GetCommentById(Guid commentId) { - ReadCommentServiceModel readCommentServiceModel = await this._commentService.GetCommentById(id); + ReadCommentServiceModel readCommentServiceModel = await this._commentService.GetCommentById(commentId); ReadCommentWebModel readCommentWebModel = this._commentMapper.Map<ReadCommentWebModel>(readCommentServiceModel); return new OkObjectResult(readCommentWebModel); } + /// <summary> + /// Update comment's parametars. Comment creator only! + /// </summary> + /// <param name="userId">The comment creator's Id</param> + /// <param name="updateCommentWebModel">New comment's parametars</param> + /// <param name="authorization">JWT Bearer token</param> + /// <returns>Ok result</returns> [HttpPut] public async Task<IActionResult> UpdateComment(Guid userId, [FromBody] UpdateCommentWebModel updateCommentWebModel, [FromHeader] string authorization) { - if (!await this._commentService.ValidateJwtForComment(updateCommentWebModel.CommentId, authorization)) + if (!this._jwtService.ValidateToken(userId, authorization)) return new UnauthorizedResult(); UpdateCommentServiceModel updateCommentServiceModel = @@ -67,17 +95,22 @@ namespace DevHive.Web.Controllers new OkObjectResult(new { Id = id }); } + /// <summary> + /// Delete a comment. Comment creator only! + /// </summary> + /// <param name="commentId">Comment's Id</param> + /// <param name="authorization">JWT Bearer token</param> + /// <returns>Ok result</returns> [HttpDelete] - public async Task<IActionResult> DeleteComment(Guid id, [FromHeader] string authorization) + public async Task<IActionResult> DeleteComment(Guid commentId, [FromHeader] string authorization) { - if (!await this._commentService.ValidateJwtForComment(id, authorization)) + if (!await this._commentService.ValidateJwtForComment(commentId, authorization)) return new UnauthorizedResult(); - return await this._commentService.DeleteComment(id) ? + return await this._commentService.DeleteComment(commentId) ? new OkResult() : new BadRequestObjectResult("Could not delete Comment"); } - } } diff --git a/src/Web/DevHive.Web/Controllers/FeedController.cs b/src/Web/DevHive.Web/Controllers/FeedController.cs index abca3e4..37532a9 100644 --- a/src/Web/DevHive.Web/Controllers/FeedController.cs +++ b/src/Web/DevHive.Web/Controllers/FeedController.cs @@ -10,6 +10,9 @@ using Microsoft.AspNetCore.Mvc; namespace DevHive.Web.Controllers { + /// <summary> + /// All endpoints for interacting with the feed layer + /// </summary> [ApiController] [Route("/api/[controller]")] [Authorize(Roles = "User,Admin")] @@ -24,6 +27,12 @@ namespace DevHive.Web.Controllers this._mapper = mapper; } + /// <summary> + /// Query posts for user's feed + /// </summary> + /// <param name="userId">The user's Id, whose feed is begin queried</param> + /// <param name="getPageWebModel">Page parametars</param> + /// <returns>A page of the feed</returns> [HttpPost] [Route("GetPosts")] public async Task<IActionResult> GetPosts(Guid userId, [FromBody] GetPageWebModel getPageWebModel) @@ -37,6 +46,12 @@ namespace DevHive.Web.Controllers return new OkObjectResult(readPageWebModel); } + /// <summary> + /// Query a user profile's posts + /// </summary> + /// <param name="username">The user's username, whose posts are being queried</param> + /// <param name="getPageWebModel">Page parametars</param> + /// <returns>A page of the user's posts</returns> [HttpPost] [Route("GetUserPosts")] [AllowAnonymous] diff --git a/src/Web/DevHive.Web/Controllers/LanguageController.cs b/src/Web/DevHive.Web/Controllers/LanguageController.cs index 5b0d5de..665fb66 100644 --- a/src/Web/DevHive.Web/Controllers/LanguageController.cs +++ b/src/Web/DevHive.Web/Controllers/LanguageController.cs @@ -10,6 +10,9 @@ using Microsoft.AspNetCore.Mvc; namespace DevHive.Web.Controllers { + /// <summary> + /// All endpoints for interacting with the language layer + /// </summary> [ApiController] [Route("/api/[controller]")] public class LanguageController @@ -23,6 +26,11 @@ namespace DevHive.Web.Controllers this._languageMapper = mapper; } + /// <summary> + /// Create a new language, so users can have a choice. Admin only! + /// </summary> + /// <param name="createLanguageWebModel">The new language's parametars</param> + /// <returns>The new language's Id</returns> [HttpPost] [Authorize(Roles = "Admin")] public async Task<IActionResult> Create([FromBody] CreateLanguageWebModel createLanguageWebModel) @@ -36,6 +44,11 @@ namespace DevHive.Web.Controllers new OkObjectResult(new { Id = id }); } + /// <summary> + /// Query full language data by Id + /// </summary> + /// <param name="id">The language's Id</param> + /// <returns>Full language data</returns> [HttpGet] [AllowAnonymous] public async Task<IActionResult> GetById(Guid id) @@ -46,6 +59,10 @@ namespace DevHive.Web.Controllers return new OkObjectResult(languageWebModel); } + /// <summary> + /// Query all languages in the database + /// </summary> + /// <returns>All languages in the database</returns> [HttpGet] [Route("GetLanguages")] [Authorize(Roles = "User,Admin")] @@ -57,6 +74,12 @@ namespace DevHive.Web.Controllers return new OkObjectResult(languageWebModels); } + /// <summary> + /// Alter language's properties. Admin only! + /// </summary> + /// <param name="id">The language's Id</param> + /// <param name="updateModel">The langauge's new parametars</param> + /// <returns>Ok result</returns> [HttpPut] [Authorize(Roles = "Admin")] public async Task<IActionResult> Update(Guid id, [FromBody] UpdateLanguageWebModel updateModel) @@ -72,11 +95,16 @@ namespace DevHive.Web.Controllers return new OkResult(); } + /// <summary> + /// Delete a language. Admin only! + /// </summary> + /// <param name="langaugeId">The language's Id</param> + /// <returns>Ok result</returns> [HttpDelete] [Authorize(Roles = "Admin")] - public async Task<IActionResult> Delete(Guid id) + public async Task<IActionResult> Delete(Guid langaugeId) { - bool result = await this._languageService.DeleteLanguage(id); + bool result = await this._languageService.DeleteLanguage(langaugeId); if (!result) return new BadRequestObjectResult("Could not delete Language"); diff --git a/src/Web/DevHive.Web/Controllers/PostController.cs b/src/Web/DevHive.Web/Controllers/PostController.cs index d3fdbf6..44b291d 100644 --- a/src/Web/DevHive.Web/Controllers/PostController.cs +++ b/src/Web/DevHive.Web/Controllers/PostController.cs @@ -6,9 +6,13 @@ using DevHive.Web.Models.Post; using DevHive.Services.Models.Post; using Microsoft.AspNetCore.Authorization; using DevHive.Services.Interfaces; +using DevHive.Common.Jwt.Interfaces; namespace DevHive.Web.Controllers { + /// <summary> + /// All endpoints for interacting with the post layer + /// </summary> [ApiController] [Route("/api/[controller]")] [Authorize(Roles = "User,Admin")] @@ -16,18 +20,27 @@ namespace DevHive.Web.Controllers { private readonly IPostService _postService; private readonly IMapper _postMapper; + private readonly IJwtService _jwtService; - public PostController(IPostService postService, IMapper postMapper) + public PostController(IPostService postService, IMapper postMapper, IJwtService jwtService) { this._postService = postService; this._postMapper = postMapper; + this._jwtService = jwtService; } #region Create + /// <summary> + /// Create a new post + /// </summary> + /// <param name="userId">The user's Id</param> + /// <param name="createPostWebModel">The new post's data</param> + /// <param name="authorization">JWT Bearer token</param> + /// <returns>New post's Id</returns> [HttpPost] public async Task<IActionResult> Create(Guid userId, [FromForm] CreatePostWebModel createPostWebModel, [FromHeader] string authorization) { - if (!await this._postService.ValidateJwtForCreating(userId, authorization)) + if (!this._jwtService.ValidateToken(userId, authorization)) return new UnauthorizedResult(); CreatePostServiceModel createPostServiceModel = @@ -43,6 +56,11 @@ namespace DevHive.Web.Controllers #endregion #region Read + /// <summary> + /// Query full post's data by it's Id + /// </summary> + /// <param name="id">The post's Id</param> + /// <returns>Full data model of the post</returns> [HttpGet] [AllowAnonymous] public async Task<IActionResult> GetById(Guid id) @@ -55,9 +73,19 @@ namespace DevHive.Web.Controllers #endregion #region Update + /// <summary> + /// Update post's data. Creator only! + /// </summary> + /// <param name="userId">The post creator's Id</param> + /// <param name="updatePostWebModel">The new params of the post</param> + /// <param name="authorization">JWT Bearer token</param> + /// <returns>The post's Id</returns> [HttpPut] public async Task<IActionResult> Update(Guid userId, [FromForm] UpdatePostWebModel updatePostWebModel, [FromHeader] string authorization) { + if (!this._jwtService.ValidateToken(userId, authorization)) + return new UnauthorizedResult(); + if (!await this._postService.ValidateJwtForPost(updatePostWebModel.PostId, authorization)) return new UnauthorizedResult(); @@ -74,13 +102,19 @@ namespace DevHive.Web.Controllers #endregion #region Delete + /// <summary> + /// Delete a post. Creator only! + /// </summary> + /// <param name="postId">Post's Id</param> + /// <param name="authorization">JWT Bearer token</param> + /// <returns>Ok result</returns> [HttpDelete] - public async Task<IActionResult> Delete(Guid id, [FromHeader] string authorization) + public async Task<IActionResult> Delete(Guid postId, [FromHeader] string authorization) { - if (!await this._postService.ValidateJwtForPost(id, authorization)) + if (!await this._postService.ValidateJwtForPost(postId, authorization)) return new UnauthorizedResult(); - return await this._postService.DeletePost(id) ? + return await this._postService.DeletePost(postId) ? new OkResult() : new BadRequestObjectResult("Could not delete Post"); } diff --git a/src/Web/DevHive.Web/Controllers/ProfilePictureController.cs b/src/Web/DevHive.Web/Controllers/ProfilePictureController.cs new file mode 100644 index 0000000..2eec99e --- /dev/null +++ b/src/Web/DevHive.Web/Controllers/ProfilePictureController.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using DevHive.Web.Models.User; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace DevHive.Web.Controllers +{ + /// <summary> + /// All endpoints for interacting with the profile picture layer + /// </summary> + [ApiController] + [Route("api/[controller]")] + public class ProfilePictureController + { + // private readonly ProfilePictureService _profilePictureService; + + // public ProfilePictureController(ProfilePictureService profilePictureService) + // { + // this._profilePictureService = profilePictureService; + // } + + /// <summary> + /// Alter the profile picture of a user + /// </summary> + /// <param name="userId">The user's Id</param> + /// <param name="updateProfilePictureWebModel">The new profile picture</param> + /// <param name="authorization">JWT Bearer Token</param> + /// <returns>???</returns> + [HttpPut] + [Route("ProfilePicture")] + [Authorize(Roles = "User,Admin")] + public async Task<IActionResult> UpdateProfilePicture(Guid userId, [FromForm] UpdateProfilePictureWebModel updateProfilePictureWebModel, [FromHeader] string authorization) + { + throw new NotImplementedException(); + // if (!await this._userService.ValidJWT(userId, authorization)) + // return new UnauthorizedResult(); + + // UpdateProfilePictureServiceModel updateProfilePictureServiceModel = this._userMapper.Map<UpdateProfilePictureServiceModel>(updateProfilePictureWebModel); + // updateProfilePictureServiceModel.UserId = userId; + + // ProfilePictureServiceModel profilePictureServiceModel = await this._userService.UpdateProfilePicture(updateProfilePictureServiceModel); + // ProfilePictureWebModel profilePictureWebModel = this._userMapper.Map<ProfilePictureWebModel>(profilePictureServiceModel); + + // return new AcceptedResult("UpdateProfilePicture", profilePictureWebModel); + } + } +} diff --git a/src/Web/DevHive.Web/Controllers/RateController.cs b/src/Web/DevHive.Web/Controllers/RateController.cs deleted file mode 100644 index 72eb932..0000000 --- a/src/Web/DevHive.Web/Controllers/RateController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading.Tasks; -using AutoMapper; -using DevHive.Services.Interfaces; -using DevHive.Services.Models.Post.Rating; -using DevHive.Web.Models.Rating; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace DevHive.Web.Controllers -{ - [ApiController] - [Route("api/[controller]")] - public class RateController - { - private readonly IRateService _rateService; - private readonly IUserService _userService; - private readonly IMapper _mapper; - - public RateController(IRateService rateService, IUserService userService, IMapper mapper) - { - this._rateService = rateService; - this._userService = userService; - this._mapper = mapper; - } - - [HttpPost] - [Authorize(Roles = "Admin,User")] - public async Task<IActionResult> RatePost(Guid userId, [FromBody] RatePostWebModel ratePostWebModel, [FromHeader] string authorization) - { - RatePostServiceModel ratePostServiceModel = this._mapper.Map<RatePostServiceModel>(ratePostWebModel); - ratePostServiceModel.UserId = userId; - - ReadPostRatingServiceModel readPostRatingServiceModel = await this._rateService.RatePost(ratePostServiceModel); - ReadPostRatingWebModel readPostRatingWebModel = this._mapper.Map<ReadPostRatingWebModel>(readPostRatingServiceModel); - - return new OkObjectResult(readPostRatingWebModel); - } - } -} diff --git a/src/Web/DevHive.Web/Controllers/RatingController.cs b/src/Web/DevHive.Web/Controllers/RatingController.cs new file mode 100644 index 0000000..5716b85 --- /dev/null +++ b/src/Web/DevHive.Web/Controllers/RatingController.cs @@ -0,0 +1,99 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Common.Jwt.Interfaces; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Post.Rating; +using DevHive.Web.Models.Rating; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace DevHive.Web.Controllers +{ + [ApiController] + //[Authorize(Roles = "Admin,User")] + [Route("api/[controller]")] + public class RatingController + { + private readonly IRatingService _rateService; + private readonly IUserService _userService; + private readonly IMapper _mapper; + private readonly IJwtService _jwtService; + + public RatingController(IRatingService rateService, IUserService userService, IMapper mapper, IJwtService jwtService) + { + this._rateService = rateService; + this._userService = userService; + this._mapper = mapper; + this._jwtService = jwtService; + } + + [HttpPost] + public async Task<IActionResult> RatePost(Guid userId, [FromBody] CreateRatingWebModel createRatingWebModel, [FromHeader] string authorization) + { + if (!this._jwtService.ValidateToken(userId, authorization)) + return new UnauthorizedResult(); + + CreateRatingServiceModel ratePostServiceModel = this._mapper.Map<CreateRatingServiceModel>(createRatingWebModel); + ratePostServiceModel.UserId = userId; + + Guid id = await this._rateService.RatePost(ratePostServiceModel); + + if (Guid.Empty == id) + return new BadRequestResult(); + + return new OkObjectResult(new { Id = id }); + } + + [HttpGet] + public async Task<IActionResult> GetRatingById(Guid id) + { + ReadRatingServiceModel readRatingServiceModel = await this._rateService.GetRatingById(id); + ReadRatingWebModel readPostRatingWebModel = this._mapper.Map<ReadRatingWebModel>(readRatingServiceModel); + + return new OkObjectResult(readPostRatingWebModel); + } + + [HttpGet] + [Route("GetByUserAndPost")] + public async Task<IActionResult> GetRatingByUserAndPost(Guid userId, Guid postId) + { + ReadRatingServiceModel readRatingServiceModel = await this._rateService.GetRatingByPostAndUser(userId, postId); + ReadRatingWebModel readPostRatingWebModel = this._mapper.Map<ReadRatingWebModel>(readRatingServiceModel); + + return new OkObjectResult(readPostRatingWebModel); + } + + [HttpPut] + public async Task<IActionResult> UpdateRating(Guid userId, [FromBody] UpdateRatingWebModel updateRatingWebModel, [FromHeader] string authorization) + { + if (!this._jwtService.ValidateToken(userId, authorization)) + return new UnauthorizedResult(); + + UpdateRatingServiceModel updateRatingServiceModel = + this._mapper.Map<UpdateRatingServiceModel>(updateRatingWebModel); + updateRatingServiceModel.UserId = userId; + + ReadRatingServiceModel readRatingServiceModel = await this._rateService.UpdateRating(updateRatingServiceModel); + + if (readRatingServiceModel == null) + return new BadRequestResult(); + else + { + ReadRatingWebModel readRatingWebModel = this._mapper.Map<ReadRatingWebModel>(readRatingServiceModel); + return new OkObjectResult(readRatingWebModel); + } + } + + [HttpDelete] + public async Task<IActionResult> DeleteTating(Guid userId, Guid ratingId, [FromHeader] string authorization) + { + if (!this._jwtService.ValidateToken(userId, authorization)) + return new UnauthorizedResult(); + + return await this._rateService.DeleteRating(ratingId) ? + new OkResult() : + new BadRequestObjectResult("Could not delete Rating"); + } + } +} diff --git a/src/Web/DevHive.Web/Controllers/RoleController.cs b/src/Web/DevHive.Web/Controllers/RoleController.cs index 1465795..ebb305e 100644 --- a/src/Web/DevHive.Web/Controllers/RoleController.cs +++ b/src/Web/DevHive.Web/Controllers/RoleController.cs @@ -9,6 +9,9 @@ using Microsoft.AspNetCore.Authorization; namespace DevHive.Web.Controllers { + /// <summary> + /// All endpoints for interacting with the roles layer + /// </summary> [ApiController] [Route("/api/[controller]")] public class RoleController @@ -22,6 +25,11 @@ namespace DevHive.Web.Controllers this._roleMapper = mapper; } + /// <summary> + /// Create a new role for the roles hierarchy. Admin only! + /// </summary> + /// <param name="createRoleWebModel">The new role's parametars</param> + /// <returns>The new role's Id</returns> [HttpPost] [Authorize(Roles = "Admin")] public async Task<IActionResult> Create([FromBody] CreateRoleWebModel createRoleWebModel) @@ -36,6 +44,11 @@ namespace DevHive.Web.Controllers new OkObjectResult(new { Id = id }); } + /// <summary> + /// Get a role's full data, querying it by it's Id + /// </summary> + /// <param name="id">The role's Id</param> + /// <returns>Full info of the role</returns> [HttpGet] [Authorize(Roles = "User,Admin")] public async Task<IActionResult> GetById(Guid id) @@ -46,6 +59,12 @@ namespace DevHive.Web.Controllers return new OkObjectResult(roleWebModel); } + /// <summary> + /// Update a role's parametars. Admin only! + /// </summary> + /// <param name="id">The role's Id</param> + /// <param name="updateRoleWebModel">The new parametrats for that role</param> + /// <returns>Ok result</returns> [HttpPut] [Authorize(Roles = "Admin")] public async Task<IActionResult> Update(Guid id, [FromBody] UpdateRoleWebModel updateRoleWebModel) @@ -62,6 +81,11 @@ namespace DevHive.Web.Controllers return new OkResult(); } + /// <summary> + /// Delete a role. Admin only! + /// </summary> + /// <param name="id">The role's Id</param> + /// <returns>Ok result</returns> [HttpDelete] [Authorize(Roles = "Admin")] public async Task<IActionResult> Delete(Guid id) diff --git a/src/Web/DevHive.Web/Controllers/TechnologyController.cs b/src/Web/DevHive.Web/Controllers/TechnologyController.cs index e507899..ecf2bd7 100644 --- a/src/Web/DevHive.Web/Controllers/TechnologyController.cs +++ b/src/Web/DevHive.Web/Controllers/TechnologyController.cs @@ -10,6 +10,9 @@ using Microsoft.AspNetCore.Mvc; namespace DevHive.Web.Controllers { + /// <summary> + /// All endpoints for interacting with the technology layer + /// </summary> [ApiController] [Route("/api/[controller]")] public class TechnologyController @@ -23,6 +26,11 @@ namespace DevHive.Web.Controllers this._technologyMapper = technologyMapper; } + /// <summary> + /// Create a new technology, so users can have a choice. Admin only! + /// </summary> + /// <param name="createTechnologyWebModel">Data for the new technology</param> + /// <returns>The new technology's Id</returns> [HttpPost] [Authorize(Roles = "Admin")] public async Task<IActionResult> Create([FromBody] CreateTechnologyWebModel createTechnologyWebModel) @@ -36,6 +44,11 @@ namespace DevHive.Web.Controllers new OkObjectResult(new { Id = id }); } + /// <summary> + /// Get technology's data by it's Id + /// </summary> + /// <param name="id">The technology's Id</param> + /// <returns>The technology's full data</returns> [HttpGet] [AllowAnonymous] public async Task<IActionResult> GetById(Guid id) @@ -46,6 +59,10 @@ namespace DevHive.Web.Controllers return new OkObjectResult(readTechnologyWebModel); } + /// <summary> + /// Get all technologies from our database + /// </summary> + /// <returns>All technologies</returns> [HttpGet] [Route("GetTechnologies")] [Authorize(Roles = "User,Admin")] @@ -57,6 +74,12 @@ namespace DevHive.Web.Controllers return new OkObjectResult(languageWebModels); } + /// <summary> + /// Alter a technology's parameters. Admin only! + /// </summary> + /// <param name="id">Technology's Id</param> + /// <param name="updateModel">The new parametars</param> + /// <returns>Ok result</returns> [HttpPut] [Authorize(Roles = "Admin")] public async Task<IActionResult> Update(Guid id, [FromBody] UpdateTechnologyWebModel updateModel) @@ -72,6 +95,11 @@ namespace DevHive.Web.Controllers return new OkResult(); } + /// <summary> + /// Delete a etchnology from the database. Admin only! + /// </summary> + /// <param name="id">The technology's Id</param> + /// <returns>Ok result</returns> [HttpDelete] [Authorize(Roles = "Admin")] public async Task<IActionResult> Delete(Guid id) diff --git a/src/Web/DevHive.Web/Controllers/UserController.cs b/src/Web/DevHive.Web/Controllers/UserController.cs index 214fba7..4d01447 100644 --- a/src/Web/DevHive.Web/Controllers/UserController.cs +++ b/src/Web/DevHive.Web/Controllers/UserController.cs @@ -7,26 +7,40 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using DevHive.Common.Models.Identity; using DevHive.Services.Interfaces; +using DevHive.Common.Jwt.Interfaces; +using NSwag.Annotations; namespace DevHive.Web.Controllers { + /// <summary> + /// All endpoints for integration with the User + /// </summary> [ApiController] [Route("/api/[controller]")] + [OpenApiController("User Controller")] public class UserController : ControllerBase { private readonly IUserService _userService; private readonly IMapper _userMapper; + private readonly IJwtService _jwtService; - public UserController(IUserService userService, IMapper mapper) + public UserController(IUserService userService, IMapper mapper, IJwtService jwtService) { this._userService = userService; this._userMapper = mapper; + this._jwtService = jwtService; } #region Authentication + /// <summary> + /// Login endpoint for the DevHive Social Platform + /// </summary> + /// <param name="loginModel">Login model with username and password</param> + /// <returns>A JWT Token for further validation</returns> [HttpPost] - [Route("Login")] [AllowAnonymous] + [Route("Login")] + [OpenApiTags("Authorization")] public async Task<IActionResult> Login([FromBody] LoginWebModel loginModel) { LoginServiceModel loginServiceModel = this._userMapper.Map<LoginServiceModel>(loginModel); @@ -37,9 +51,15 @@ namespace DevHive.Web.Controllers return new OkObjectResult(tokenWebModel); } + /// <summary> + /// Register a new User in the DevHive Social Platform + /// </summary> + /// <param name="registerModel">Register model with the new data to provide</param> + /// <returns>A JWT Token for further validation</returns> [HttpPost] - [Route("Register")] [AllowAnonymous] + [Route("Register")] + [OpenApiTag("Authorization")] public async Task<IActionResult> Register([FromBody] RegisterWebModel registerModel) { RegisterServiceModel registerServiceModel = this._userMapper.Map<RegisterServiceModel>(registerModel); @@ -52,11 +72,17 @@ namespace DevHive.Web.Controllers #endregion #region Read + /// <summary> + /// Get a User's information using the Guid + /// </summary> + /// <param name="id">User's Id</param> + /// <param name="authorization">The JWT Token, contained in the header and used for validation</param> + /// <returns>A full User's read model</returns> [HttpGet] [Authorize(Roles = "User,Admin")] public async Task<IActionResult> GetById(Guid id, [FromHeader] string authorization) { - if (!await this._userService.ValidJWT(id, authorization)) + if (!this._jwtService.ValidateToken(id, authorization)) return new UnauthorizedResult(); UserServiceModel userServiceModel = await this._userService.GetUserById(id); @@ -65,6 +91,11 @@ namespace DevHive.Web.Controllers return new OkObjectResult(userWebModel); } + /// <summary> + /// Get a User's profile using his username. Does NOT require authorization + /// </summary> + /// <param name="username">User's username</param> + /// <returns>A trimmed version of the full User's read model</returns> [HttpGet] [Route("GetUser")] [AllowAnonymous] @@ -78,11 +109,18 @@ namespace DevHive.Web.Controllers #endregion #region Update + /// <summary> + /// Full update on User's data. A PUSTINQK can only edit his account + /// </summary> + /// <param name="id">The User's Id</param> + /// <param name="updateUserWebModel">A full User update model</param> + /// <param name="authorization">The JWT Token, contained in the header and used for validation</param> + /// <returns>A full User's read model</returns> [HttpPut] [Authorize(Roles = "User,Admin")] public async Task<IActionResult> Update(Guid id, [FromBody] UpdateUserWebModel updateUserWebModel, [FromHeader] string authorization) { - if (!await this._userService.ValidJWT(id, authorization)) + if (!this._jwtService.ValidateToken(id, authorization)) return new UnauthorizedResult(); UpdateUserServiceModel updateUserServiceModel = this._userMapper.Map<UpdateUserServiceModel>(updateUserWebModel); @@ -93,31 +131,20 @@ namespace DevHive.Web.Controllers return new AcceptedResult("UpdateUser", userWebModel); } - - [HttpPut] - [Route("ProfilePicture")] - [Authorize(Roles = "User,Admin")] - public async Task<IActionResult> UpdateProfilePicture(Guid userId, [FromForm] UpdateProfilePictureWebModel updateProfilePictureWebModel, [FromHeader] string authorization) - { - if (!await this._userService.ValidJWT(userId, authorization)) - return new UnauthorizedResult(); - - UpdateProfilePictureServiceModel updateProfilePictureServiceModel = this._userMapper.Map<UpdateProfilePictureServiceModel>(updateProfilePictureWebModel); - updateProfilePictureServiceModel.UserId = userId; - - ProfilePictureServiceModel profilePictureServiceModel = await this._userService.UpdateProfilePicture(updateProfilePictureServiceModel); - ProfilePictureWebModel profilePictureWebModel = this._userMapper.Map<ProfilePictureWebModel>(profilePictureServiceModel); - - return new AcceptedResult("UpdateProfilePicture", profilePictureWebModel); - } #endregion #region Delete + /// <summary> + /// Delete a User with his Id. A PUSTINQK can only delete his account. An Admin can delete all accounts + /// </summary> + /// <param name="id">The User's Id</param> + /// <param name="authorization">The JWT Token, contained in the header and used for validation</param> + /// <returns>Ok, BadRequest or Unauthorized</returns> [HttpDelete] [Authorize(Roles = "User,Admin")] public async Task<IActionResult> Delete(Guid id, [FromHeader] string authorization) { - if (!await this._userService.ValidJWT(id, authorization)) + if (!this._jwtService.ValidateToken(id, authorization)) return new UnauthorizedResult(); bool result = await this._userService.DeleteUser(id); @@ -128,7 +155,13 @@ namespace DevHive.Web.Controllers } #endregion + /// <summary> + /// We don't talk about that, NIGGA! + /// </summary> + /// <param name="userId"></param> + /// <returns></returns> [HttpPost] + [OpenApiIgnore] [Authorize(Roles = "User,Admin")] [Route("SuperSecretPromotionToAdmin")] public async Task<IActionResult> SuperSecretPromotionToAdmin(Guid userId) diff --git a/src/Web/DevHive.Web/DevHive.Web.csproj b/src/Web/DevHive.Web/DevHive.Web.csproj index 6511c37..39322ae 100644 --- a/src/Web/DevHive.Web/DevHive.Web.csproj +++ b/src/Web/DevHive.Web/DevHive.Web.csproj @@ -5,6 +5,8 @@ <PropertyGroup> <EnableNETAnalyzers>true</EnableNETAnalyzers> <AnalysisLevel>latest</AnalysisLevel> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + <AllowUntrustedCertificate>true</AllowUntrustedCertificate> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.3" NoWarn="NU1605"/> @@ -14,15 +16,23 @@ <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2"/> - <PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.5"/> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1"/> <PackageReference Include="AutoMapper" Version="10.1.1"/> <PackageReference Include="Newtonsoft.Json" Version="12.0.3"/> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> + <PackageReference Include="NSwag.AspNetCore" Version="13.10.7"/> + <PackageReference Include="NSwag.Generation.AspNetCore" Version="13.10.7"/> + <PackageReference Include="NSwag.Annotations" Version="13.10.7"/> + <PackageReference Include="NSwag.Core" Version="13.10.7"/> + <PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.1.0"/> + <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.1.0"/> + <PackageReference Include="NSwag.SwaggerGeneration.WebApi" Version="12.3.0"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\DevHive.Web.Models\DevHive.Web.Models.csproj"/> <ProjectReference Include="..\..\Services\DevHive.Services\DevHive.Services.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.Models.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common\DevHive.Common.csproj"/> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/Web/DevHive.Web/Startup.cs b/src/Web/DevHive.Web/Startup.cs index dbcf131..ebd091e 100644 --- a/src/Web/DevHive.Web/Startup.cs +++ b/src/Web/DevHive.Web/Startup.cs @@ -29,11 +29,11 @@ namespace DevHive.Web x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); + services.DependencyInjectionConfiguration(this.Configuration); services.DatabaseConfiguration(Configuration); services.SwaggerConfiguration(); services.JWTConfiguration(Configuration); services.AutoMapperConfiguration(); - services.DependencyInjectionConfiguration(this.Configuration); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -48,7 +48,6 @@ namespace DevHive.Web if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseSwaggerConfiguration(); } else { @@ -56,6 +55,7 @@ namespace DevHive.Web app.UseExceptionHandlerMiddlewareConfiguration(); } + app.UseSwaggerConfiguration(); app.UseDatabaseConfiguration(); app.UseAutoMapperConfiguration(); diff --git a/src/Web/DevHive.Web/appsettings.json b/src/Web/DevHive.Web/appsettings.json index bcdcae7..fcf9805 100644 --- a/src/Web/DevHive.Web/appsettings.json +++ b/src/Web/DevHive.Web/appsettings.json @@ -1,20 +1,22 @@ { - "AppSettings": { - "Secret": "gXfQlU6qpDleFWyimscjYcT3tgFsQg3yoFjcvSLxG56n1Vu2yptdIUq254wlJWjm" - }, - "ConnectionStrings": { - "DEV": "Server=localhost;Port=5432;Database=API;User Id=postgres;Password=;" + "Jwt": { + "signingKey": "", + "validationIssuer": "", + "audience": "" + }, + "ConnectionStrings": { + "DEV": "Server=localhost;Port=5432;Database=API;User Id=postgres;Password=;" }, "Cloud": { "cloudName": "devhive", "apiKey": "488664116365813", "apiSecret": "" }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } } |
