Message api

GetMessage and CreateMessage

Add a new signature in IDatingRepository

Task<Message> GetMessage(int id);

Implement in DatingRepository

public async Task<Message> GetMessage(int id)
{
    return await _context.Messages.FirstOrDefaultAsync(x => x.Id == id);
}

Controller

Create MessagesController

namespace DatingApp.API.Controllers
{
    [ServiceFilter(typeof(Filters.LogUserActivity))]
    [Authorize]
    [Route("api/users/{userId}/[controller]")]
    [ApiController]
    public class MessagesController : ControllerBase
    {

        private readonly IDatingRepository _repo;
        private readonly IMapper _mapper;

        public MessagesController(IDatingRepository repo, IMapper mapper)
        {
            _repo = repo;
            _mapper = mapper;
        }

        [HttpGet("{id}", Name="GetMessage")]
        public async Task<IActionResult> GetMessage(int userId, int id) {
            if (userId != int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value))
                return Unauthorized();

            var messageFromRepo = await _repo.GetMessage(id);

            if(messageFromRepo == null)
                return NotFound();

            if(messageFromRepo.SenderId != userId && messageFromRepo.RecipientId != userId)
                return Unauthorized();

            return Ok(messageFromRepo);
        }
    }
}

Add a new DTO MessageForCreation

public class MessageForCreation
{
    public int RecipientId { get; set; }
    public string Content { get; set; }
}

And a similar DTO MessageForCreated

public class MessageForCreated
{
    public int Id { get; set; }
    public int SenderId { get; set; }
    public int RecipientId { get; set; }
    public string Content { get; set; }
    public DateTime SentDate { get; set; }
}

Create mappings in AutoMapperProfiles

CreateMap<Message, MessageForCreated>();
CreateMap<MessageForCreation, Message>();

Use it in MessagesController for the CreateMessage method

[HttpPost]
public async Task<IActionResult> CreateMessage(int userId, MessageForCreation messageForCreation)
{
    if (userId != int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value))
    {
        return Unauthorized();
    }

    var recipient = await _repo.GetUser(messageForCreation.RecipientId);

    if (recipient == null)
    {
        return NotFound();
    }

    var message = _mapper.Map<Message>(messageForCreation);
    message.SentDate = DateTime.UtcNow;
    message.SenderId = userId;

    _repo.Add(message);

    if (await _repo.SaveAll())
    {
        var messageToReturn = _mapper.Map<MessageForCreated>(message);
        return CreatedAtRoute("GetMessage", new { id = message.Id }, messageToReturn);
    }

    throw new Exception("Creation failed on save");
}

Test with Postman

Create a new POST with body of type application/JSON

{{url}}/api/users/1/messages/

{
	"recipientId": 13,
	"content": "Message from Alicia to Apophis"
}

GetMessagesForUser

Create a new DTO MessageParams

public class MessageParams
{
    private const int MaxPageSize = 50;
    public int PageNumber { get; set; } = 1;

    private int pageSize = 10;
    public int PageSize
    {
        get { return pageSize; }
        set { pageSize = (value > MaxPageSize) ? MaxPageSize : value; }
    }
    public int UserId { get; set; }

    public string MessageContainer { get; set; } = "Unread";
}

Add a new signature in IDatingRepository

Task<PagedList<Message>> GetMessagesForUser(MessageParams messageParams);

Implement in DatingRepository

public async Task<PagedList<Message>> GetMessagesForUser(MessageParams messageParams)
{
    var messages = _context.Messages
        .Include(m => m.Sender).ThenInclude(u => u.Photos)
        .Include(m => m.Recipient).ThenInclude(u => u.Photos)
        .AsQueryable();

    switch(messageParams.MessageContainer)
    {
        case "Inbox":
            messages = messages.Where(m => m.RecipientId == messageParams.UserId);
            break;
        case "Outbox":
            messages = messages.Where(m => m.SenderId == messageParams.UserId);
            break;
        default: //"Unread"
            messages = messages.Where(m => m.RecipientId == messageParams.UserId && !m.IsRead);
            break;
    }

    messages = messages.OrderByDescending(m=>m.SentDate);
    return await PagedList<Message>.CreateAsync(messages, messageParams.PageNumber, messageParams.PageSize);
}

Create a new DTO MessageForList very similar to the original Message class. Note that Automapper recognizes properties like SenderKnownAs as Sender.KnownAs in the source object (good boy automapper, good boy!)

public class MessageForList
{
    public int Id { get; set; }
    public int SenderId { get; set; }
    public string SenderKnownAs { get; set; }
    public string SenderPhotoUrl { get; set; }
    public int RecipientId { get; set; }
    public String RecipientKnownAs { get; set; }
    public String RecipientPhotoUrl  { get; set; }
    public string Content { get; set; }
    public DateTime SentDate { get; set; }
    public bool IsRead { get; set; }
    public DateTime? ReadDate { get; set; }
}

Create mappings in AutoMapperProfiles

CreateMap<Message, MessageForList>();

Add a new method in MessagesController

[HttpGet]
public async Task<IActionResult> GetMessagesForUser(int userId, [FromQuery]MessageParams messageParams)
{
    if (userId != int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value))
    {
        return Unauthorized();
    }

    messageParams.UserId = userId;

    var messagesFromRepo = await _repo.GetMessagesForUser(messageParams);
    var messages = _mapper.Map<IEnumerable<MessageForList>>(messagesFromRepo);

    Response.AddPagination(messagesFromRepo.CurrentPage, messagesFromRepo.PageSize,
        messagesFromRepo.TotalCount, messagesFromRepo.TotalPages);

    return Ok(messages);
}

Test with Postman

Create a new GET

{{url}}/api/users/1/messages?messageContainer=Outbox

Message thread

Add a new signature in IDatingRepository

Task<IEnumerable<Message>> GetMessageThread(int userId, int otherUserId);

Implement in DatingRepository

public async Task<IEnumerable<Message>> GetMessageThread(int userId, int otherUserId)
{
    var messages = _context.Messages
        .Include(m => m.Sender).ThenInclude(u => u.Photos)
        .Include(m => m.Recipient).ThenInclude(u => u.Photos)
        .Where(m =>
            (m.SenderId == userId && m.RecipientId == otherUserId)
            || (m.SenderId == otherUserId && m.RecipientId == userId))
        .OrderByDescending(m => m.SentDate);

    return await messages.ToListAsync();
}

Add a new method in MessagesController

[HttpGet("thread/{otherUserId}")]
public async Task<IActionResult> GetMessageThread(int userId, int otherUserId)
{
    if (userId != int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value))
    {
        return Unauthorized();
    }

    var messagesFromRepo = await _repo.GetMessageThread(userId, otherUserId);
    var messages = _mapper.Map<IEnumerable<MessageForList>>(messagesFromRepo);

    return Ok(messages);
}

Test with Postman

Create a new GET

{{url}}/api/users/1/messages/thread/13