Photos controller

Photos controller

Create a new PhotosController.cs with Authorization and a custom route.

[Authorize]
[Route("api/users/{userId}/photos")]
public class PhotosController : Controller
{
}

Create a constructor for DI with IDatingRepository, IMapper and the configuration for Cloudinary. We also create a new Account and a new Cloudinary objects for Cloudinary.

private readonly IDatingRepository _repo;
private readonly IMapper _mapper;
private readonly IOptions<CloudinarySettings> _cloudinarySettings;
private Cloudinary _cloudinary;

public PhotosController(IDatingRepository repo, IMapper mapper, IOptions<CloudinarySettings> cloudinarySettings)
{
    _cloudinarySettings = cloudinarySettings;
    _mapper = mapper;
    _repo = repo;

    // Initialize Cloudinary
    Account account = new Account(
        cloudinarySettings.Value.CloudName,
        cloudinarySettings.Value.ApiKey,
        cloudinarySettings.Value.ApiSecret
    );

    _cloudinary = new Cloudinary(account);
}

To post a photo we need a new DTO PhotoForCreation

public class PhotoForCreation
{
    public string Url { get; set; }
    public IFormFile File { get; set; }
    public string  Description { get; set; }
    public DateTime Created { get; }
    public string PublicId { get; set; }

    public PhotoForCreation()
    {
        this.Created = DateTime.UtcNow;
    }
}

Now we can create a new POST method AddPhotoForUser. Note that this method still needs a mapping configuration and have a TODO for the return value.

[HttpPost]
public async Task<IActionResult> AddPhotoForUser(int userId, PhotoForCreation photoDto)
{
    var user = await _repo.GetUser(userId);

    if (user == null)
    {
        return BadRequest("Could not find user");
    }

    var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);

    // Only the current user can upload it's photos
    if (user.Id != currentUserId)
    {
        return Unauthorized();
    }

    var file = photoDto.File;

    if (file.Length > 0)
    {
        var uploadResult = new ImageUploadResult();

        using (var stream = file.OpenReadStream())
        {
            var uploadParams = new ImageUploadParams()
            {
                File = new FileDescription(file.Name, stream)
            };
            uploadResult = _cloudinary.Upload(uploadParams);
        }

        // Check if the upload was fine
        if(uploadResult.Error != null)
        {
            return BadRequest("Unable to upload photo.\n" + uploadResult.Error.Message);
        }

        // Save the results back in the DTO
        photoDto.Url = uploadResult.Uri.ToString();
        photoDto.PublicId = uploadResult.PublicId;

        // Map the dto to a Photo model
        var photo = _mapper.Map<Photo>(photoDto);
        photo.User = user;

        // Check if the photo has to be the main photo for the user
        if (!user.Photos.Any(x => x.IsMain))
        {
            photo.IsMain = true;
        }

        user.Photos.Add(photo);

        if (await _repo.SaveAll())
        {
            // TODO this Ok is just a temporary solution, we should return a CreatedAtRoute
            return Ok();
        }
    }
    return BadRequest("Could not add the photo");
}

To have a CreatedAtRoute we have to implement a GET method for the photo.

Open IDatingRepository and add a new signature.

Task<Photo> GetPhoto(int id);

And a concrete implementation in DatingRepository

public async Task<Photo> GetPhoto(int id)
{
    return await _context.Photos.FirstOrDefaultAsync(x => x.Id == id);
}

Create a new DTO PhotoForReturn

public class PhotoForReturn
{
    public int Id { get; set; }
    public string Url { get; set; }
    public string Description { get; set; }
    public DateTime DateAdded { get; set; }
    public bool IsMain { get; set; }
    public string PublicId { get; set; }
}

Create the mappings for both the DTOs in AutoMapperProfiles

...
CreateMap<PhotoForCreation, Photo>();
CreateMap<Photo, PhotoForReturn>();
...

Now we have all the pieces for the GET method in the controller

[HttpGet("{id}", Name="GetPhoto")]
public async Task<IActionResult> GetPhoto(int id) {
    var photoFromRepo = await _repo.GetPhoto(id);

    var photoForReturn = _mapper.Map<PhotoForReturn>(photoFromRepo);

    return Ok(photoForReturn);
}

Let’s fix the return for AddPhotoForUser

...
if (await _repo.SaveAll())
{
    var photoForReturn = _mapper.Map<PhotoForReturn>(photo);
    return CreatedAtRoute("GetPhoto", new { id = photo.Id }, photoForReturn);
}
...