diff --git a/src/Admin/Controllers/UsersController.cs b/src/Admin/Controllers/UsersController.cs new file mode 100644 index 0000000000..75bee22c7c --- /dev/null +++ b/src/Admin/Controllers/UsersController.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Bit.Core.Repositories; +using System.Threading.Tasks; +using Bit.Admin.Models; +using System.Collections.Generic; +using Bit.Core.Models.Table; + +namespace Bit.Admin.Controllers +{ + [Authorize] + public class UsersController : Controller + { + private readonly IUserRepository _userRepository; + + public UsersController(IUserRepository userRepository) + { + _userRepository = userRepository; + } + + public async Task Index(string email, int page = 1, int count = 25) + { + if(page < 1) + { + page = 1; + } + + if(count < 1) + { + count = 1; + } + + var skip = (page - 1) * count; + var users = await _userRepository.SearchByEmailAsync(email, skip, count); + return View(new UsersModel + { + Users = users as List, + Email = string.IsNullOrWhiteSpace(email) ? null : email, + Page = page, + Count = count + }); + } + } +} diff --git a/src/Admin/Models/UsersModel.cs b/src/Admin/Models/UsersModel.cs new file mode 100644 index 0000000000..22ac845815 --- /dev/null +++ b/src/Admin/Models/UsersModel.cs @@ -0,0 +1,16 @@ +using System.Collections; +using System.Collections.Generic; +using Bit.Core.Models.Table; + +namespace Bit.Admin.Models +{ + public class UsersModel + { + public List Users { get; set; } + public string Email { get; set; } + public int Page { get; set; } + public int Count { get; set; } + public int? PreviousPage => Page < 2 ? (int?)null : Page - 1; + public int? NextPage => Users.Count < Count ? (int?)null : Page + 1; + } +} diff --git a/src/Admin/Views/Shared/_Layout.cshtml b/src/Admin/Views/Shared/_Layout.cshtml index 9870f1632e..c7e1dc76e3 100644 --- a/src/Admin/Views/Shared/_Layout.cshtml +++ b/src/Admin/Views/Shared/_Layout.cshtml @@ -28,10 +28,10 @@ diff --git a/src/Admin/Views/Users/Index.cshtml b/src/Admin/Views/Users/Index.cshtml new file mode 100644 index 0000000000..3ec29ad29c --- /dev/null +++ b/src/Admin/Views/Users/Index.cshtml @@ -0,0 +1,110 @@ +@model UsersModel +@{ + ViewData["Title"] = "Users"; +} + +

Users

+ +
+ +
+ +
+ +
+
+
+ +
+ + + + + + + + + + @if(!Model.Users.Any()) + { + + + + } + else + { + @foreach(var user in Model.Users) + { + + + + + + } + } + +
EmailCreatedDetails
No results to list.
+ @user.Email + + + @user.CreationDate.ToShortDateString() + + + @if(user.Premium) + { + + } + else + { + + } + @if(user.EmailVerified) + { + + } + else + { + + } + @if(user.TwoFactorIsEnabled()) + { + + } + else + { + + } +
+
+ + diff --git a/src/Core/Repositories/IUserRepository.cs b/src/Core/Repositories/IUserRepository.cs index 56e68692e7..410a2d25a8 100644 --- a/src/Core/Repositories/IUserRepository.cs +++ b/src/Core/Repositories/IUserRepository.cs @@ -8,6 +8,7 @@ namespace Bit.Core.Repositories public interface IUserRepository : IRepository { Task GetByEmailAsync(string email); + Task> SearchByEmailAsync(string email, int skip, int take); Task> GetManyByPremiumAsync(bool premium); Task GetPublicKeyAsync(Guid id); Task GetAccountRevisionDateAsync(Guid id); diff --git a/src/Core/Repositories/SqlServer/UserRepository.cs b/src/Core/Repositories/SqlServer/UserRepository.cs index 821b3f51d7..6444dea0d4 100644 --- a/src/Core/Repositories/SqlServer/UserRepository.cs +++ b/src/Core/Repositories/SqlServer/UserRepository.cs @@ -37,6 +37,19 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task> SearchByEmailAsync(string email, int skip, int take) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[{Table}_SearchByEmail]", + new { Email = email, Skip = skip, Take = take }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + public async Task> GetManyByPremiumAsync(bool premium) { using(var connection = new SqlConnection(ConnectionString)) diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 485cdaf91f..ba4db087fe 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -224,5 +224,6 @@ + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/User_SearchByEmail.sql b/src/Sql/dbo/Stored Procedures/User_SearchByEmail.sql new file mode 100644 index 0000000000..444b0d6cf1 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/User_SearchByEmail.sql @@ -0,0 +1,19 @@ +CREATE PROCEDURE [dbo].[User_SearchByEmail] + @Email NVARCHAR(50), + @Skip INT = 0, + @Take INT = 25 +AS +BEGIN + SET NOCOUNT ON + DECLARE @EmailLikeSearch NVARCHAR(55) = @Email + '%' + + SELECT + * + FROM + [dbo].[UserView] + WHERE + (@Email IS NULL OR [Email] LIKE @EmailLikeSearch) + ORDER BY [Email] ASC + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY +END \ No newline at end of file diff --git a/util/Setup/DbScripts/2018-03-21_00_AdminPortal.sql b/util/Setup/DbScripts/2018-03-21_00_AdminPortal.sql new file mode 100644 index 0000000000..359bed380b --- /dev/null +++ b/util/Setup/DbScripts/2018-03-21_00_AdminPortal.sql @@ -0,0 +1,26 @@ +IF OBJECT_ID('[dbo].[User_SearchByEmail]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[User_SearchByEmail] +END +GO + +CREATE PROCEDURE [dbo].[User_SearchByEmail] + @Email NVARCHAR(50), + @Skip INT = 0, + @Take INT = 25 +AS +BEGIN + SET NOCOUNT ON + DECLARE @EmailLikeSearch NVARCHAR(55) = @Email + '%' + + SELECT + * + FROM + [dbo].[UserView] + WHERE + (@Email IS NULL OR [Email] LIKE @EmailLikeSearch) + ORDER BY [Email] ASC + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY +END +GO diff --git a/util/Setup/Setup.csproj b/util/Setup/Setup.csproj index e8e8dc5c8f..a5b96036d3 100644 --- a/util/Setup/Setup.csproj +++ b/util/Setup/Setup.csproj @@ -8,11 +8,7 @@ - - - - - +