Repository: CodingYourCareer/ConstantReminders-Api
Pull request #9: Created user service and user service tests
Author: login: vphillips1
Created: 2025-02-07T01:29:45Z | Merged: 2025-03-15T22:58:46Z

Created user service and test file. Used the user model from the User class. 


Diff:
diff --git a/ConstantReminders.Api.Tests/ConstantReminders.Api.Tests.csproj b/ConstantReminders.Api.Tests/ConstantReminders.Api.Tests.csproj
index 02862c0..94be80d 100644
--- a/ConstantReminders.Api.Tests/ConstantReminders.Api.Tests.csproj
+++ b/ConstantReminders.Api.Tests/ConstantReminders.Api.Tests.csproj
@@ -41,8 +41,4 @@
     <ProjectReference Include="..\ConstantReminders.Api\ConstantReminder.Api.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="ConstantReminders\Services\" />
-  </ItemGroup>
-
 </Project>
diff --git a/ConstantReminders.Api.Tests/ConstantReminders/Mapper/UserMapperTests.cs b/ConstantReminders.Api.Tests/ConstantReminders/Mapper/UserMapperTests.cs
new file mode 100644
index 0000000..ce0421e
--- /dev/null
+++ b/ConstantReminders.Api.Tests/ConstantReminders/Mapper/UserMapperTests.cs
@@ -0,0 +1,68 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Models;
+using ConstantReminders.Contracts.Mapper;
+
+namespace ConstantReminders.Api.Tests.ConstantReminders.Mapper;
+
+public class UserMapperTests
+{
+    [Fact]
+    public void ToEntity_ShouldMapDtoToUserEntity()
+    {
+        // Arrange
+        var dto = new CreateUpdateUserDto
+        {
+            FirstName = "John",
+            LastName = "Doe",
+            PhoneNumber = "555-555-5555",
+            Email = "john.doe@email.com",
+            DefaultCommunicationMethod = DefaultCommunicationMethod.Email
+        };
+
+        string createdBy = "Admin";
+        string auth0Id = "auth0-123";
+
+        // Act
+        var user = dto.ToEntity(createdBy, auth0Id);
+
+        // Assert
+        Assert.NotNull(user);
+        Assert.Equal(dto.FirstName, user.FirstName);
+        Assert.Equal(dto.LastName, user.LastName);
+        Assert.Equal(dto.PhoneNumber, user.PhoneNumber);
+        Assert.Equal(dto.Email, user.Email);
+        Assert.Equal(dto.DefaultCommunicationMethod, user.DefaultCommunicationMethod);
+        Assert.Equal(auth0Id, user.AuthOId);
+        Assert.Equal(createdBy, user.CreatedBy);
+    }
+
+    [Fact]
+    public void ToDto_ShouldMapUserEntityToUserDto()
+    {
+        // Arrange
+        var user = new User
+        {
+            Id = Guid.NewGuid(),
+            FirstName = "Jane",
+            LastName = "Doe",
+            PhoneNumber = "123-456-7890",
+            Email = "jane.doe@email.com",
+            DefaultCommunicationMethod = DefaultCommunicationMethod.Phone,
+            AuthOId = "auth0-xyz",
+            CreatedBy = "testuser"
+        };
+
+        // Act
+        var dto = user.ToDto();
+
+        // Assert
+        Assert.NotNull(dto);
+        Assert.Equal(user.Id, dto.Id);
+        Assert.Equal(user.FirstName, dto.FirstName);
+        Assert.Equal(user.LastName, dto.LastName);
+        Assert.Equal(user.PhoneNumber, dto.PhoneNumber);
+        Assert.Equal(user.Email, dto.Email);
+        Assert.Equal(user.DefaultCommunicationMethod, dto.DefaultCommunicationMethod);
+        Assert.Equal(user.AuthOId, dto.AuthOId);
+    }
+}
diff --git a/ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs b/ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs
new file mode 100644
index 0000000..1463659
--- /dev/null
+++ b/ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs
@@ -0,0 +1,214 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Mapper;
+using ConstantReminders.Contracts.Models;
+using ConstantReminders.Services;
+using NSubstitute;
+
+namespace ConstantReminders.Api.Tests.ConstantReminders.Services;
+public class UserServiceTests
+{
+    [Fact]
+    public async Task CreateUser_ShouldCreateAndReturnUserWithCreated201()
+    {
+        var mockRepo = Substitute.For<IBaseRepository<User>>();
+        var service = new UserService(mockRepo);
+
+        const string firstName = "John";
+        const string lastName = "Doe";
+        const string email = "test@email.com";
+        const string phoneNumber = "555-555-5555";
+        const DefaultCommunicationMethod communicationMethod = DefaultCommunicationMethod.Email;
+
+        var dto = new CreateUpdateUserDto
+        {
+            FirstName = firstName,
+            LastName = lastName,
+            PhoneNumber = phoneNumber,
+            Email = email,
+            DefaultCommunicationMethod = communicationMethod
+        };
+
+        var time = DateTime.UtcNow;
+
+        var createdUser = new User
+        {
+            Id = Guid.NewGuid(),
+            FirstName = firstName,
+            LastName = lastName,
+            Email = email,
+            PhoneNumber = phoneNumber,
+            DefaultCommunicationMethod = communicationMethod,
+            CreatedBy = "testUser",
+            AuthOId = "testAuth0Id",
+            CreatedDateTime = time,
+            UpdatedDateTime = time
+        };
+
+        mockRepo.CreateAsync(Arg.Any<User>())
+            .Returns(Task.FromResult(createdUser));
+        var response = await service.CreateUser(dto, "testUser", "testAuth0Id");
+        await mockRepo.Received(1).CreateAsync(Arg.Any<User>());
+
+        Assert.Equal(firstName, response.FirstName);
+        Assert.Equal(lastName, response.LastName);
+        Assert.Equal(email, response.Email);
+        Assert.Equal(phoneNumber, response.PhoneNumber);
+        Assert.Equal(communicationMethod, response.DefaultCommunicationMethod);
+    }
+
+    [Fact]
+    public async Task GetUsers_ShouldReturnListOfUsers()
+    {
+        var mockRepo = Substitute.For<IBaseRepository<User>>();
+        var service = new UserService(mockRepo);
+
+        var users = new List<User>()
+        {
+            new User { Id = Guid.NewGuid(), FirstName = "John", LastName = "Doe", PhoneNumber = "555-555-5555",
+                Email = "john@test.com", AuthOId = "1", CreatedBy = "user", UpdatedBy = "user" },
+            new User { Id = Guid.NewGuid(), FirstName = "Jane", LastName = "Smith", PhoneNumber = "444-444-4444",
+                Email ="jane@test.com", AuthOId ="2", CreatedBy = "user", UpdatedBy = "user" }
+
+        };
+
+        mockRepo.List().Returns(users);
+        var response = await service.GetUsers();
+        await mockRepo.Received(1).List();
+
+        Assert.NotNull(response);
+        Assert.Equal(2, response.Count);
+        Assert.Equal("John", response[0].FirstName);
+        Assert.Equal("Jane", response[1].FirstName);
+    }
+
+    [Fact]
+    public async Task GetUserById_ShouldReturnUser()
+    {
+        var mockRepo = Substitute.For<IBaseRepository<User>>();
+        var service = new UserService(mockRepo);
+        var userId = Guid.NewGuid();
+
+        var user = new User 
+        {
+            Id = userId,
+            FirstName = "John",
+            LastName = "Doe",
+            PhoneNumber = "555-555-5555",
+            Email = "john@test.com",
+            AuthOId = "1",
+            CreatedBy = "user",
+            UpdatedBy = "user"
+        };
+
+   
+        mockRepo.GetByIdAsync(userId).Returns(user);
+        var response = await service.GetUserById(userId);
+        await mockRepo.Received(1).GetByIdAsync(userId);
+
+        Assert.NotNull(response);
+        Assert.Equal(userId, response.Id);
+        Assert.Equal("John", response.FirstName);
+    }
+
+    [Fact]
+    public async Task UpdateUser_ShouldUpdateAndReturnUser()
+    {
+        var mockRepo = Substitute.For<IBaseRepository<User>>();
+        var service = new UserService(mockRepo);
+        var userId = Guid.NewGuid();
+        var existingUser = new User
+        {
+            Id = userId,
+            FirstName = "John",
+            LastName = "Doe",
+            PhoneNumber = "555-555-5555",
+            Email = "john@test.com",
+            AuthOId = "1",
+            CreatedBy = "user",
+            UpdatedBy = "user",
+            CreatedDateTime = DateTime.UtcNow,
+            UpdatedDateTime = DateTime.UtcNow
+        };
+
+        var updateDto = new CreateUpdateUserDto
+        {
+            FirstName = "John",
+            LastName = "Updated",
+            PhoneNumber = "123456789",
+            Email = "john.updated@email.com",
+            DefaultCommunicationMethod = DefaultCommunicationMethod.Email
+        };
+
+        // Mock repository behavior: Return existing user when queried
+        mockRepo.GetByIdAsync(userId).Returns(existingUser);
+        User? capturedUpdatedUser = null;
+
+        // Capture what is passed to UpdateAsync instead of assuming correctness
+        mockRepo.UpdateAsync(Arg.Do<User>(u =>
+        {
+            capturedUpdatedUser = new User
+            {
+                Id = u.Id,
+                FirstName = u.FirstName,
+                LastName = u.LastName,
+                PhoneNumber = u.PhoneNumber,
+                Email = u.Email,
+                DefaultCommunicationMethod = u.DefaultCommunicationMethod,
+                AuthOId = u.AuthOId,
+                CreatedBy = u.CreatedBy,
+                UpdatedBy = u.UpdatedBy,
+                CreatedDateTime = u.CreatedDateTime,
+                UpdatedDateTime = u.UpdatedDateTime
+            };
+        })).Returns(callInfo => Task.FromResult(callInfo.Arg<User>()));
+
+        // Act: Call UpdateUser with DTO
+        var response = await service.UpdateUser(updateDto, userId, "User");
+
+        // Assert repository interactions
+        await mockRepo.Received(1).GetByIdAsync(userId);
+        await mockRepo.Received(1).UpdateAsync(Arg.Any<User>());
+
+        // Assertions on the modified user BEFORE it's saved
+        Assert.NotNull(capturedUpdatedUser);
+        Assert.Equal(updateDto.LastName, capturedUpdatedUser.LastName);
+        Assert.Equal(updateDto.PhoneNumber, capturedUpdatedUser.PhoneNumber);
+        Assert.Equal(updateDto.Email, capturedUpdatedUser.Email);
+        Assert.Equal(DefaultCommunicationMethod.Email, capturedUpdatedUser.DefaultCommunicationMethod);
+        Assert.Equal("User", capturedUpdatedUser.UpdatedBy); // Ensuring UpdatedBy is set
+
+        // Assertions on the response DTO
+        Assert.NotNull(response);
+        Assert.Equal(updateDto.LastName, response.LastName);
+        Assert.Equal(updateDto.PhoneNumber, response.PhoneNumber);
+        Assert.Equal(updateDto.Email, response.Email);
+        Assert.Equal(DefaultCommunicationMethod.Email, response.DefaultCommunicationMethod);
+    }
+
+    [Fact]
+    public async Task DeleteUser_ShouldDeleteAndReturnUser()
+    {
+        var mockRepo = Substitute.For<IBaseRepository<User>>();
+        var service = new UserService(mockRepo);
+        var userId = Guid.NewGuid();
+
+        var user = new User 
+        {   Id = userId, 
+            FirstName = "John",
+            LastName = "Doe",
+            PhoneNumber = "555-555-5555",
+            Email = "john@test.com",
+            AuthOId = "1",
+            CreatedBy = "user",
+            UpdatedBy = "user"
+        };
+
+        mockRepo.GetByIdAsync(userId).Returns(user);
+        mockRepo.DeleteAsync(Arg.Any<User>()).Returns(Task.CompletedTask);
+        var response = await service.DeleteUser(userId);
+        await mockRepo.Received(1).GetByIdAsync(userId);
+        await mockRepo.Received(1).DeleteAsync(Arg.Any<User>());
+       Assert.True(response);
+    }
+}
diff --git a/ConstantReminders.Contracts/DTO/CreateUpdateUserDto.cs b/ConstantReminders.Contracts/DTO/CreateUpdateUserDto.cs
new file mode 100644
index 0000000..0e148dc
--- /dev/null
+++ b/ConstantReminders.Contracts/DTO/CreateUpdateUserDto.cs
@@ -0,0 +1,28 @@
+﻿using ConstantReminders.Contracts.Attribute;
+using ConstantReminders.Contracts.Models;
+using System.ComponentModel.DataAnnotations;
+
+namespace ConstantReminders.Contracts.DTO;
+
+[PhoneOrEmailRequired]
+public class CreateUpdateUserDto
+{
+    [Required]
+    [StringLength(100, MinimumLength = 1, ErrorMessage = "First name must be between 1 and 100 characters.")]
+    public string FirstName { get; set; }
+
+    [Required]
+    [StringLength(100, MinimumLength = 1, ErrorMessage = "Last name must be between 1 and 100 characters.")]
+    public string LastName { get; set; }
+
+    [Phone]
+    [StringLength(20, ErrorMessage = "Phone number cannot exceed 20 characters.")]
+    public string? PhoneNumber { get; set; }
+
+    [EmailAddress]
+    [StringLength(255, ErrorMessage = "Email cannot exceed 255 characters.")]
+    public string? Email { get; set; }
+
+    [Required]
+    public DefaultCommunicationMethod DefaultCommunicationMethod { get; set; }
+}
diff --git a/ConstantReminders.Contracts/DTO/UserDto.cs b/ConstantReminders.Contracts/DTO/UserDto.cs
new file mode 100644
index 0000000..dc4d305
--- /dev/null
+++ b/ConstantReminders.Contracts/DTO/UserDto.cs
@@ -0,0 +1,15 @@
+﻿using ConstantReminders.Contracts.Models;
+using System.ComponentModel.DataAnnotations;
+
+namespace ConstantReminders.Contracts.DTO;
+public class UserDto
+{
+    public Guid Id { get; set; }
+    public string? FirstName { get; set; }
+    public required string LastName { get; set; }
+    public string? PhoneNumber { get; set; }
+    [EmailAddress]
+    public string? Email { get; set; }
+    public DefaultCommunicationMethod DefaultCommunicationMethod { get; set; }
+    public required string AuthOId { get; set; }
+}
diff --git a/ConstantReminders.Contracts/Interfaces/Business/IUserService.cs b/ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
new file mode 100644
index 0000000..1ee9226
--- /dev/null
+++ b/ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
@@ -0,0 +1,11 @@
+﻿using ConstantReminders.Contracts.DTO;
+
+namespace ConstantReminders.Contracts.Interfaces.Business;
+public interface IUserService
+{
+    Task<UserDto> CreateUser(CreateUpdateUserDto dto, string createdBy, string auth0Id);
+    Task<List<UserDto>> GetUsers();
+    Task<UserDto?> GetUserById(Guid id);
+    Task<UserDto?> UpdateUser(CreateUpdateUserDto dto, Guid id, string updatedBy);
+    Task<bool> DeleteUser(Guid id);
+}
diff --git a/ConstantReminders.Contracts/Mapper/UserMapper.cs b/ConstantReminders.Contracts/Mapper/UserMapper.cs
new file mode 100644
index 0000000..fa6c896
--- /dev/null
+++ b/ConstantReminders.Contracts/Mapper/UserMapper.cs
@@ -0,0 +1,35 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Mapper;
+
+public static class UserMapper
+{
+    public static User ToEntity(this CreateUpdateUserDto dto, string createdBy, string auth0Id)
+    {
+        return new User
+        {
+            FirstName = dto.FirstName,
+            LastName = dto.LastName,
+            PhoneNumber = dto.PhoneNumber,
+            Email = dto.Email,
+            DefaultCommunicationMethod = dto.DefaultCommunicationMethod,
+            AuthOId = auth0Id,
+            CreatedBy = createdBy
+        };
+    }
+
+    public static UserDto ToDto(this User user)
+    {
+        return new UserDto
+        {
+            Id = user.Id,
+            FirstName = user.FirstName,
+            LastName = user.LastName,
+            PhoneNumber = user.PhoneNumber,
+            Email = user.Email,
+            DefaultCommunicationMethod = user.DefaultCommunicationMethod,
+            AuthOId = user.AuthOId
+        };
+    }
+}
diff --git a/ConstantReminders.Contracts/Models/User.cs b/ConstantReminders.Contracts/Models/User.cs
index 8db59e8..b6774fd 100755
--- a/ConstantReminders.Contracts/Models/User.cs
+++ b/ConstantReminders.Contracts/Models/User.cs
@@ -18,5 +18,5 @@ public class User : IEntity
     public DateTime CreatedDateTime { get; set; }
     public DateTime UpdatedDateTime { get; set; }
     public required string CreatedBy { get; set; }
-    public required string UpdatedBy { get; set; }
+    public string UpdatedBy { get; set; }
 }
diff --git a/ConstantReminders/ConstantReminders.csproj b/ConstantReminders/ConstantReminders.csproj
index 922818f..af3d2ac 100644
--- a/ConstantReminders/ConstantReminders.csproj
+++ b/ConstantReminders/ConstantReminders.csproj
@@ -8,7 +8,6 @@
 
   <ItemGroup>
     <Folder Include="Extensions\" />
-    <Folder Include="Services\" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/ConstantReminders/Services/UserService.cs b/ConstantReminders/Services/UserService.cs
new file mode 100644
index 0000000..73146fc
--- /dev/null
+++ b/ConstantReminders/Services/UserService.cs
@@ -0,0 +1,56 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Mapper;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services;
+
+public class UserService(IBaseRepository<User> userRepo) : IUserService
+{
+    public async Task<UserDto> CreateUser(CreateUpdateUserDto dto, string createdBy, string auth0Id)
+    {
+        var newUser = dto.ToEntity(createdBy, auth0Id);
+
+        newUser.CreatedDateTime = DateTime.UtcNow;
+
+        var result =  await userRepo.CreateAsync(newUser);
+
+        return result.ToDto();
+    }
+    public async Task <List<UserDto>> GetUsers()
+    {
+        var result = await userRepo.List();
+        return result.Select(x => x.ToDto()).ToList();
+    }
+    public async Task<UserDto?> GetUserById(Guid id)
+    {
+        var result = await userRepo.GetByIdAsync(id);
+        return result?.ToDto();
+    }
+    public async Task<UserDto?> UpdateUser(CreateUpdateUserDto dto, Guid id, string updatedBy)
+    {
+        var user = await userRepo.GetByIdAsync(id);
+        if (user == null)
+            return null;
+
+        user.FirstName = dto.FirstName;
+        user.LastName = dto.LastName;
+        user.PhoneNumber = dto.PhoneNumber;
+        user.Email = dto.Email;
+        user.DefaultCommunicationMethod = dto.DefaultCommunicationMethod;
+        user.UpdatedDateTime = DateTime.UtcNow;
+        user.UpdatedBy = updatedBy;
+
+        await userRepo.UpdateAsync(user);
+        return user.ToDto();
+    }
+    public async Task<bool> DeleteUser(Guid id)
+    {
+        var user = await userRepo.GetByIdAsync(id);
+        if (user == null) return false;
+
+        await userRepo.DeleteAsync(user);
+        return true;
+    }
+}


Reviews:
user: login: yithril
body: Let me know if you have questions.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-18T22:28:27Z
user: login: funkel1989
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-19T13:58:51Z
user: login: funkel1989
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-02-19T14:10:05Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:30:22Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:32:50Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:34:51Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:38:28Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:44:31Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:45:32Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:48:17Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T00:55:42Z
user: login: funkel1989
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-01T01:34:57Z
user: login: vphillips1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-01T16:12:23Z
user: login: yithril
body: LGTM!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-15T22:57:57Z

Review comments:
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<User> CreateUser(string firstName, string lastName,
+               string phoneNumber, string email, string auth0Id, string createdBy)
+        {
+            var newUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                PhoneNumber = phoneNumber,
+                Email = email,
+                AuthOId = auth0Id,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                CreatedBy = createdBy,
+                UpdatedBy = createdBy
+
+            };
+
+           var result =  await userRepo.CreateAsync(newUser);
+
+            return result;
+        }
+
+        public async Task <List<User>> GetUsers()
+        {
+
+            var result = await userRepo.List();
+            return result;
+          
+            
+        }
+
+
+        public async Task<User?> GetUserById(Guid id)
+        {
+            return await userRepo.GetByIdAsync(id);
+        }
+
+
+        public async Task<User?> UpdateUser(Guid id, string firstName, string lastName, string phoneNumber,
+            string email, string auth0Id, string updatedBy)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null)
+                return null;
+
+            user.FirstName = firstName;
+            user.LastName = lastName;
+            user.PhoneNumber = phoneNumber;
+            user.Email = email;
+            user.AuthOId = auth0Id;
+            user.UpdatedDateTime = DateTime.Now;
+            user.UpdatedBy = updatedBy;
+
+            await userRepo.UpdateAsync(user);
+            return user;
+        }
+
+
+        public async Task<User?> DeleteUser(Guid id)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null) return null;
+
+            await userRepo.DeleteAsync(user);
+            return user;
path: ConstantReminders/Services/UserService.cs
user: login: yithril
body: The Delete method doesn't need to return anything since we're deleting the user.
created_at: 2025-02-18T22:25:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<User> CreateUser(string firstName, string lastName,
+               string phoneNumber, string email, string auth0Id, string createdBy)
+        {
+            var newUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                PhoneNumber = phoneNumber,
+                Email = email,
+                AuthOId = auth0Id,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                CreatedBy = createdBy,
+                UpdatedBy = createdBy
+
+            };
+
+           var result =  await userRepo.CreateAsync(newUser);
+
+            return result;
+        }
+
+        public async Task <List<User>> GetUsers()
+        {
+
+            var result = await userRepo.List();
+            return result;
path: ConstantReminders/Services/UserService.cs
user: login: yithril
body: This could be combined into one line
created_at: 2025-02-18T22:26:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
+{
+    public interface IUserService
+    {
+
+        Task<User> CreateUser(string firstName, string lastName,
+              string phoneNumber, string email, string auth0Id, string createdBy);
+
+        Task<List<User>> GetUsers();
+
+        Task<User?> GetUserById(Guid id);
+
+
+        Task<User?> UpdateUser(Guid id, string firstName, string lastName, string phoneNumber,
+            string email, string auth0Id, string updatedBy);
+
+        Task<User?> DeleteUser(Guid id);
+    
+
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: yithril
body: Please remove all these extra white space between the function signatures and the curly braces
created_at: 2025-02-18T22:26:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,223 @@
+﻿
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+using ConstantReminders.Services;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NSubstitute;
+
+
+
+namespace ConstantReminders.Api.Tests.ConstantReminders.Services
+{
+
+    public class UserServiceTests
+    {
+
+        [Fact]
+        public async Task CreateUser_ShouldCreateAndReturnUserWithCreated201()
+        {
+            var mockRepo = Substitute.For<IBaseRepository<User>>();
+            var service = new UserService(mockRepo);
+
+            const string firstName = "John";
+            const string lastName = "Doe";
+            const string email = "test@email.com";
+            const string phoneNumber = "555-555-5555";
+            const string auth0Id = "1";
+            const string createdBy = "User";
+
+            var createdUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                Email = email,
+                PhoneNumber = phoneNumber,
+                AuthOId = auth0Id,
+                CreatedBy = createdBy,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                UpdatedBy = createdBy
+            };
+
+
+            mockRepo.CreateAsync(Arg.Any<User>())
+                .Returns(createdUser);
+
+            var response = await service.CreateUser(firstName, lastName, phoneNumber, email, auth0Id, createdBy);
+
+            await mockRepo.Received(1).CreateAsync(Arg.Any<User>());
+
+
+            Assert.Equal(firstName, response.FirstName);
+            Assert.Equal(lastName, response.LastName);
+            Assert.Equal(email, response.Email);
+            Assert.Equal(phoneNumber, response.PhoneNumber);
+            Assert.Equal(auth0Id, response.AuthOId);
+            Assert.Equal(createdBy, response.CreatedBy);
+            Assert.True(response.CreatedDateTime != default(DateTime));
+            Assert.True(response.UpdatedDateTime != default(DateTime));
+
+
+        }
+
+
+        [Fact]
+
+        public async Task GetUsers_ShouldReturnListOfUsers()
+        {
+            var mockRepo = Substitute.For<IBaseRepository<User>>();
+            var service = new UserService(mockRepo);
+
+            var users = new List<User>()
+            {
+                new User { Id = Guid.NewGuid(), FirstName = "John", LastName = "Doe", PhoneNumber = "555-555-5555",
+                    Email = "john@test.com", AuthOId = "1", CreatedBy = "user", UpdatedBy = "user" },
+                new User { Id = Guid.NewGuid(), FirstName = "Jane", LastName = "Smith", PhoneNumber = "444-444-4444",
+                    Email ="jane@test.com", AuthOId ="2", CreatedBy = "user", UpdatedBy = "user" }
+
+            };
+
+            mockRepo.List().Returns(users);
+
+            var response = await service.GetUsers();
+
+            await mockRepo.Received(1).List();
+
+            Assert.NotNull(response);
+            Assert.Equal(2, response.Count);
+            Assert.Equal("John", response[0].FirstName);
+            Assert.Equal("Jane", response[1].FirstName);
+
+        }
+
+        [Fact]
+        public async Task GetUserById_ShouldReturnUser()
+        {
+     
+            var mockRepo = Substitute.For<IBaseRepository<User>>();
+            var service = new UserService(mockRepo);
+
+            var userId = Guid.NewGuid();
+
+            var user = new User 
+            {
+                Id = userId,
+                FirstName = "John",
+                LastName = "Doe",
+                PhoneNumber = "555-555-5555",
+                Email = "john@test.com",
+                AuthOId = "1",
+                CreatedBy = "user",
+                UpdatedBy = "user"
+            };
+
+       
+            mockRepo.GetByIdAsync(userId).Returns(user);
+
+            var response = await service.GetUserById(userId);
+
+            await mockRepo.Received(1).GetByIdAsync(userId);
+
+            Assert.NotNull(response);
+            Assert.Equal(userId, response.Id);
+            Assert.Equal("John", response.FirstName);
+        }
+
+        [Fact]
+        public async Task UpdateUser_ShouldUpdateAndReturnUser()
+        {
+    
+            var mockRepo = Substitute.For<IBaseRepository<User>>();
+            var service = new UserService(mockRepo);
+
+   
+            var userId = Guid.NewGuid();
+            var existingUser = new User { 
+                Id = userId, 
+                FirstName = "John",
+                LastName = "Doe",
+                PhoneNumber = "555-555-5555",
+                Email = "john@test.com",
+                AuthOId = "1",
+                CreatedBy = "user",
+                UpdatedBy = "user"
+            };
+
+           
+            var updatedUser = new User
+            {
+                Id = userId,
+                FirstName = "John",
+                LastName = "Updated",
+                PhoneNumber = "123456789",
+                Email = "john.updated@email.com",
+                AuthOId = "auth0-id",
+                UpdatedBy = "User",
+                CreatedBy = "User",
+                UpdatedDateTime = DateTime.Now
+            };
+
+       
+            mockRepo.GetByIdAsync(userId).Returns(existingUser);
+
+          
+            mockRepo.UpdateAsync(Arg.Any<User>()).Returns(Task.CompletedTask);  
+
path: ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs
user: login: yithril
body: This test is failing. You can't get your code in if one of your tests is failing. UpdateAsync doesn't return a Completed Task, it returns a user, so you need to change what this mocked function returns and keep at it until the test passes.
created_at: 2025-02-18T22:28:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,223 @@
+﻿
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+using ConstantReminders.Services;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NSubstitute;
+
+
path: ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs
user: login: funkel1989
body: excess spacing should be cleaned up
created_at: 2025-02-19T13:58:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,223 @@
+﻿
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+using ConstantReminders.Services;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NSubstitute;
+
+
+
+namespace ConstantReminders.Api.Tests.ConstantReminders.Services
path: ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs
user: login: funkel1989
body: please use file scoped namespaces. See other files to understand the difference.
created_at: 2025-02-19T13:59:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: funkel1989
body: File scoped namespaces should be used.
created_at: 2025-02-19T13:59:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
+{
+    public interface IUserService
+    {
+
+        Task<User> CreateUser(string firstName, string lastName,
+              string phoneNumber, string email, string auth0Id, string createdBy);
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: funkel1989
body: I don't have a problem and there is nothing wrong with passing all of these parameters, but if this user object is going to grow much larger we might want to consider a DTO object as it is a much cleaner way to handle this.
created_at: 2025-02-19T14:01:14Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: please use File scoped namespaces
created_at: 2025-02-19T14:03:25Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<User> CreateUser(string firstName, string lastName,
+               string phoneNumber, string email, string auth0Id, string createdBy)
+        {
+            var newUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                PhoneNumber = phoneNumber,
+                Email = email,
+                AuthOId = auth0Id,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                CreatedBy = createdBy,
+                UpdatedBy = createdBy
+
+            };
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: - are any of these fields required? 
- Do we need input validation, normalization or sanitization?
- DateTime.Now is actually a pretty heavy operation to perform. Consider setting it once to a variable and setting that variable to both Created and Updated to save on cycles.
- Also consider using DateTime.UtcNow.  Utc time is a general standard when storing time on the database. This can be easily translated to a user timezone or whatever other timezone you want when needed.

Feel free to apply the above comments to the entire file below as it makes sense
created_at: 2025-02-19T14:07:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<User> CreateUser(string firstName, string lastName,
+               string phoneNumber, string email, string auth0Id, string createdBy)
+        {
+            var newUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                PhoneNumber = phoneNumber,
+                Email = email,
+                AuthOId = auth0Id,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                CreatedBy = createdBy,
+                UpdatedBy = createdBy
+
+            };
+
+           var result =  await userRepo.CreateAsync(newUser);
+
+            return result;
+        }
+
+        public async Task <List<User>> GetUsers()
+        {
+
+            var result = await userRepo.List();
+            return result;
+          
+            
+        }
+
+
+        public async Task<User?> GetUserById(Guid id)
+        {
+            return await userRepo.GetByIdAsync(id);
+        }
+
+
+        public async Task<User?> UpdateUser(Guid id, string firstName, string lastName, string phoneNumber,
+            string email, string auth0Id, string updatedBy)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null)
+                return null;
+
+            user.FirstName = firstName;
+            user.LastName = lastName;
+            user.PhoneNumber = phoneNumber;
+            user.Email = email;
+            user.AuthOId = auth0Id;
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: should a user be allowed to update their auth0Id or their Email? Maybe email makes sense but I don't think Auth0Id
created_at: 2025-02-19T14:09:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<User> CreateUser(string firstName, string lastName,
+               string phoneNumber, string email, string auth0Id, string createdBy)
+        {
+            var newUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                PhoneNumber = phoneNumber,
+                Email = email,
+                AuthOId = auth0Id,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                CreatedBy = createdBy,
+                UpdatedBy = createdBy
+
+            };
+
+           var result =  await userRepo.CreateAsync(newUser);
+
+            return result;
+        }
+
+        public async Task <List<User>> GetUsers()
+        {
+
+            var result = await userRepo.List();
+            return result;
+          
+            
+        }
+
+
+        public async Task<User?> GetUserById(Guid id)
+        {
+            return await userRepo.GetByIdAsync(id);
+        }
+
+
+        public async Task<User?> UpdateUser(Guid id, string firstName, string lastName, string phoneNumber,
+            string email, string auth0Id, string updatedBy)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null)
+                return null;
+
+            user.FirstName = firstName;
+            user.LastName = lastName;
+            user.PhoneNumber = phoneNumber;
+            user.Email = email;
+            user.AuthOId = auth0Id;
+            user.UpdatedDateTime = DateTime.Now;
+            user.UpdatedBy = updatedBy;
+
+            await userRepo.UpdateAsync(user);
+            return user;
+        }
+
+
+        public async Task<User?> DeleteUser(Guid id)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null) return null;
+
+            await userRepo.DeleteAsync(user);
+            return user;
path: ConstantReminders/Services/UserService.cs
user: login: vphillips1
body: Updated this section of the code. 
created_at: 2025-02-22T00:30:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<User> CreateUser(string firstName, string lastName,
+               string phoneNumber, string email, string auth0Id, string createdBy)
+        {
+            var newUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                PhoneNumber = phoneNumber,
+                Email = email,
+                AuthOId = auth0Id,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                CreatedBy = createdBy,
+                UpdatedBy = createdBy
+
+            };
+
+           var result =  await userRepo.CreateAsync(newUser);
+
+            return result;
+        }
+
+        public async Task <List<User>> GetUsers()
+        {
+
+            var result = await userRepo.List();
+            return result;
path: ConstantReminders/Services/UserService.cs
user: login: vphillips1
body: change this to one line.
created_at: 2025-02-22T00:32:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
+{
+    public interface IUserService
+    {
+
+        Task<User> CreateUser(string firstName, string lastName,
+              string phoneNumber, string email, string auth0Id, string createdBy);
+
+        Task<List<User>> GetUsers();
+
+        Task<User?> GetUserById(Guid id);
+
+
+        Task<User?> UpdateUser(Guid id, string firstName, string lastName, string phoneNumber,
+            string email, string auth0Id, string updatedBy);
+
+        Task<User?> DeleteUser(Guid id);
+    
+
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: vphillips1
body: Removed all extra white space between the function prototypes. All set. 
created_at: 2025-02-22T00:34:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,223 @@
+﻿
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+using ConstantReminders.Services;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NSubstitute;
+
+
path: ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs
user: login: vphillips1
body: cleaned up extra spacing 
created_at: 2025-02-22T00:38:28Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,223 @@
+﻿
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+using ConstantReminders.Services;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NSubstitute;
+
+
+
+namespace ConstantReminders.Api.Tests.ConstantReminders.Services
path: ConstantReminders.Api.Tests/ConstantReminders/Services/UserServiceTests.cs
user: login: vphillips1
body: Reviewed Event Service to compare file scoped namespaces and made the change 
created_at: 2025-02-22T00:44:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: vphillips1
body: updated to file scoped namespaces
created_at: 2025-02-22T00:45:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<User> CreateUser(string firstName, string lastName,
+               string phoneNumber, string email, string auth0Id, string createdBy)
+        {
+            var newUser = new User
+            {
+                Id = Guid.NewGuid(),
+                FirstName = firstName,
+                LastName = lastName,
+                PhoneNumber = phoneNumber,
+                Email = email,
+                AuthOId = auth0Id,
+                CreatedDateTime = DateTime.Now,
+                UpdatedDateTime = DateTime.Now,
+                CreatedBy = createdBy,
+                UpdatedBy = createdBy
+
+            };
+
+           var result =  await userRepo.CreateAsync(newUser);
+
+            return result;
+        }
+
+        public async Task <List<User>> GetUsers()
+        {
+
+            var result = await userRepo.List();
+            return result;
+          
+            
+        }
+
+
+        public async Task<User?> GetUserById(Guid id)
+        {
+            return await userRepo.GetByIdAsync(id);
+        }
+
+
+        public async Task<User?> UpdateUser(Guid id, string firstName, string lastName, string phoneNumber,
+            string email, string auth0Id, string updatedBy)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null)
+                return null;
+
+            user.FirstName = firstName;
+            user.LastName = lastName;
+            user.PhoneNumber = phoneNumber;
+            user.Email = email;
+            user.AuthOId = auth0Id;
path: ConstantReminders/Services/UserService.cs
user: login: vphillips1
body: I removed it from here user.Auth0Id
created_at: 2025-02-22T00:48:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
+{
+    public interface IUserService
+    {
+
+        Task<User> CreateUser(string firstName, string lastName,
+              string phoneNumber, string email, string auth0Id, string createdBy);
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: vphillips1
body: Hi @funkel1989 I believe creating a user DTO is not in this scope of the story but wanted to ask how should I proceed and where would that class be located ? within Models ? 
created_at: 2025-02-22T00:55:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,28 @@
+﻿using ConstantReminders.Contracts.Attribute;
+using ConstantReminders.Contracts.Models;
+using System.ComponentModel.DataAnnotations;
+
+namespace ConstantReminders.Contracts.DTO;
+
+[PhoneOrEmailRequired]
+public class CreateUpdateUserDto
+{
+    [Required]
path: ConstantReminders.Contracts/DTO/CreateUpdateUserDto.cs
user: login: funkel1989
body: we have a required attribute here but the string of firstname is optional. we should use the required key work after public so make sure this is required.
created_at: 2025-03-01T01:15:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,28 @@
+﻿using ConstantReminders.Contracts.Attribute;
+using ConstantReminders.Contracts.Models;
+using System.ComponentModel.DataAnnotations;
+
+namespace ConstantReminders.Contracts.DTO;
+
+[PhoneOrEmailRequired]
+public class CreateUpdateUserDto
path: ConstantReminders.Contracts/DTO/CreateUpdateUserDto.cs
user: login: funkel1989
body: Using the term Dto is a good standard to use but more importantly is this a Data DTO or a Web DTO. 

Web DTO => WebRequest => Domain Layer => Data layer
Data DTO => WebRequest Is Domain Layer => Data Dto => Data layer

Combination: Web Request => Web DTO => Domain layer => Data DTO => Data layer.

Because these different formats exist and one isn't totally defined in the app yet this is a good opportunity for you to use better terminology

CreateUpdateUserRequest for a web dto

I do think this makes sense to be a web DTO because the data layer doesn't make sense to have CreateUpdate in the name. If I'm wrong here then we should consider renaming this.
created_at: 2025-03-01T01:19:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,16 @@
+﻿using ConstantReminders.Contracts.Models;
+using System.ComponentModel.DataAnnotations;
+
+namespace ConstantReminders.Contracts.DTO;
+
+public class UserDto
path: ConstantReminders.Contracts/DTO/UserDto.cs
user: login: funkel1989
body: This one feels more like a data DTO.

Is this actually a DTO though? is this the domain object? If this isn't the domain object, does the domain object have the identical fields to the DTO? Do we need a data DTO or can we just save the domain logic?

Did Jonathan ask that we follow this pattern or train this pattern? If so, then this is ok.
created_at: 2025-03-01T01:20:25Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: funkel1989
body: this wasn't actually updated to a file scoped namespace? what happened here.
created_at: 2025-03-01T01:20:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,35 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Mapper;
+
+public static class UserMapper
+{
+    public static User ToEntity(this CreateUpdateUserDto dto, string createdBy, string auth0Id)
path: ConstantReminders.Contracts/Mapper/UserMapper.cs
user: login: funkel1989
body: regarding the comments above, this adds some confusion. ToEntity takes in the web DTO request and creates an Entity. To Dto takes in the domain object and returns a data DTO. I guess I answered some of my own questions.
created_at: 2025-03-01T01:22:19Z
author_association: COLLABORATOR
diff_hunk: @@ -18,5 +18,5 @@ public class User : IEntity
     public DateTime CreatedDateTime { get; set; }
     public DateTime UpdatedDateTime { get; set; }
     public required string CreatedBy { get; set; }
-    public required string UpdatedBy { get; set; }
+    public string UpdatedBy { get; set; }
path: ConstantReminders.Contracts/Models/User.cs
user: login: funkel1989
body: why is updated by no longer required? If its not being updated and its just being created then updated by should equal the created by. This allows us to mandate that this field is always input on the database and throw a database error is not. the code will also protect against this.
created_at: 2025-03-01T01:23:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,82 @@
+﻿
+using System;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: still not using file scoped namespaces
created_at: 2025-03-01T01:23:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Mapper;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<UserDto> CreateUser(CreateUpdateUserDto dto, string createdBy, string auth0Id)
+        {
+            var newUser = dto.ToEntity(createdBy, auth0Id);
+
+            newUser.CreatedDateTime = DateTime.UtcNow;
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: so you can do this here but we really should take a deep look at the responsibility of services. Because we have a user service and a user repository its important that each service has the correct job.

In this case the created date time is something that we want to set and only care about when saving the entity. That means this might make more sense inside of the repository layer as how it is set might change based on the type of database we have. 

One more thing! Take a look at the DbContext. there might be extension functions that are doing this for you already.
created_at: 2025-03-01T01:24:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Mapper;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<UserDto> CreateUser(CreateUpdateUserDto dto, string createdBy, string auth0Id)
+        {
+            var newUser = dto.ToEntity(createdBy, auth0Id);
+
+            newUser.CreatedDateTime = DateTime.UtcNow;
+
+            var result =  await userRepo.CreateAsync(newUser);
+
+            return result.ToDto();
+        }
+
+        public async Task <List<UserDto>> GetUsers()
+        {
+            var result = await userRepo.List();
+            return result.Select(x => x.ToDto()).ToList();
+        }
+
+
+        public async Task<UserDto?> GetUserById(Guid id)
+        {
+            var result = await userRepo.GetByIdAsync(id);
+            return result?.ToDto();
+        }
+
+
+        public async Task<UserDto?> UpdateUser(CreateUpdateUserDto dto, Guid id, string updatedBy)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null)
+                return null;
+
+            user.FirstName = dto.FirstName;
+            user.LastName = dto.LastName;
+            user.PhoneNumber = dto.PhoneNumber;
+            user.Email = dto.Email;
+            user.DefaultCommunicationMethod = dto.DefaultCommunicationMethod;
+            user.UpdatedDateTime = DateTime.UtcNow;
+            user.UpdatedBy = updatedBy;
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: This is a very common way to handle updates and there is nothing wrong with it but if we are using entity framework tracking then we can use entity framework to perform the update. Take a look at some documentation regarding entity framework tracking and the set values functionality of entity framework. then this update might be able to move to the data level (repo layer.)

https://stackoverflow.com/a/62546784

Take a look at that answer for an examble.
created_at: 2025-03-01T01:27:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Mapper;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
+    {
+       
+        public async Task<UserDto> CreateUser(CreateUpdateUserDto dto, string createdBy, string auth0Id)
+        {
+            var newUser = dto.ToEntity(createdBy, auth0Id);
+
+            newUser.CreatedDateTime = DateTime.UtcNow;
+
+            var result =  await userRepo.CreateAsync(newUser);
+
+            return result.ToDto();
+        }
+
+        public async Task <List<UserDto>> GetUsers()
+        {
+            var result = await userRepo.List();
+            return result.Select(x => x.ToDto()).ToList();
+        }
+
+
+        public async Task<UserDto?> GetUserById(Guid id)
+        {
+            var result = await userRepo.GetByIdAsync(id);
+            return result?.ToDto();
+        }
+
+
+        public async Task<UserDto?> UpdateUser(CreateUpdateUserDto dto, Guid id, string updatedBy)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null)
+                return null;
+
+            user.FirstName = dto.FirstName;
+            user.LastName = dto.LastName;
+            user.PhoneNumber = dto.PhoneNumber;
+            user.Email = dto.Email;
+            user.DefaultCommunicationMethod = dto.DefaultCommunicationMethod;
+            user.UpdatedDateTime = DateTime.UtcNow;
+            user.UpdatedBy = updatedBy;
+
+            await userRepo.UpdateAsync(user);
+            return user.ToDto();
+        }
+
+
+        public async Task<bool> DeleteUser(Guid id)
+        {
+            var user = await userRepo.GetByIdAsync(id);
+            if (user == null) return false;
+
+            await userRepo.DeleteAsync(user);
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: this is calling the repo layer? does the repo layer return a answer here? I think the delete call from entity framework actually returns a response? is that response a bool? if so then lets just pass it through here.
created_at: 2025-03-01T01:28:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+﻿using ConstantReminders.Contracts.DTO;
+using ConstantReminders.Contracts.Interfaces.Business;
+using ConstantReminders.Contracts.Interfaces.Data;
+using ConstantReminders.Contracts.Mapper;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Services
+{
+    public class UserService(IBaseRepository<User> userRepo) : IUserService
path: ConstantReminders/Services/UserService.cs
user: login: funkel1989
body: so this comment applies to all of the code below: 
https://github.com/CodingYourCareer/ConstantReminders-Api/blob/main/ConstantReminders/Services/EventService.cs

Take a look at that file and notice how the service is handling the construction of our core response object. We want to make sure ALL of our endpoints return a common object with the data or response attached to it for that endpoint.
created_at: 2025-03-01T01:31:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ConstantReminders.Contracts.Models;
+
+namespace ConstantReminders.Contracts.Interfaces.Business
path: ConstantReminders.Contracts/Interfaces/Business/IUserService.cs
user: login: vphillips1
body: @yithril made changes to add DTO so i did not push my changes. I did a git reset hard because there would of been a lot of changes I was not sure to keep. So just to be safe I just pulled down the changes Yithril added 
created_at: 2025-03-01T16:12:23Z
author_association: COLLABORATOR

PR comments:
user: login: yithril
created_at: 2025-02-10T22:35:23Z
body: @vphillips1 Your UserService needs to use EntityFrameworkCore and actually grab Users from the database. The code I see it looks like you have an in-memory list of users and then return that. That will also change how you do your tests because you'll need Ef Core for them as well.
author_association: COLLABORATOR
user: login: yithril
created_at: 2025-02-26T21:10:51Z
body: @funkel1989 I made a lot of changes to help Kia along, do you mind taking a look at them just to make sure we're all on the same page?
author_association: COLLABORATOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-13T01:14:05Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=CodingYourCareer_ConstantReminders-Api&pullRequest=9) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=CodingYourCareer_ConstantReminders-Api&pullRequest=9&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=CodingYourCareer_ConstantReminders-Api&pullRequest=9&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=CodingYourCareer_ConstantReminders-Api&pullRequest=9&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [94.9% Coverage on New Code](https://sonarcloud.io/component_measures?id=CodingYourCareer_ConstantReminders-Api&pullRequest=9&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=CodingYourCareer_ConstantReminders-Api&pullRequest=9&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=CodingYourCareer_ConstantReminders-Api&pullRequest=9)


author_association: NONE