Skip to content

Instantly share code, notes, and snippets.

@JaimeStill
Created August 2, 2024 20:32

Revisions

  1. JaimeStill created this gist Aug 2, 2024.
    147 changes: 147 additions & 0 deletions UploadService.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,147 @@
    using System.Text.RegularExpressions;
    using Core.Data;
    using Core.Models;
    using Core.Services.Settings;
    using Microsoft.AspNetCore.Http;
    using Microsoft.EntityFrameworkCore;
    using Platform.Messages;

    namespace Core.Services;
    public class UploadService(CoreContext db, UploadPathSettings paths)
    {
    readonly CoreContext db = db;
    readonly UploadPathSettings paths = paths;
    readonly string urlPattern = "[^a-zA-Z0-9-.]";

    public async Task<PersonImage?> GetPersonImage(int personId) =>
    await db.PersonImages
    .FirstOrDefaultAsync(x =>
    x.PersonId == personId
    );

    public async Task<ApiMessage<PersonImage>> UploadPersonImage(IFormFile file, int personId)
    {
    ValidationMessage validation = Validate(file, "image");

    if (validation.IsValid)
    {
    await RemovePersonImage(personId);
    PersonImage image = await AddPersonImage(file, personId);
    return new(image, $"Person image {image.Name} successfully uploaded");
    }
    else
    return new(validation);
    }

    public async Task<ApiMessage<PersonImage>> RemovePersonImage(int personId)
    {
    PersonImage? image = await GetPersonImage(personId);

    if (image is not null)
    {
    db.PersonImages.Remove(image);
    await db.SaveChangesAsync();
    Delete(image);
    return new(image, $"Person image {image.Name} successfully removed");
    }
    else
    return new("RemoveUserImage", new Exception("Person image not found"));
    }

    async Task<PersonImage> AddPersonImage(IFormFile file, int personId)
    {
    PersonImage image = await Write<PersonImage>(file, new());
    image.PersonId = personId;
    await db.PersonImages.AddAsync(image);
    await db.SaveChangesAsync();

    return image;
    }

    #region Internal

    string CreateSafeName(IFormFile file, string path)
    {
    int increment = 0;
    string fileName = UrlEncode(file.FileName);
    string newName = fileName;

    while (File.Exists(Path.Combine(path, newName)))
    {
    string extension = fileName.Split('.').Last();
    newName = $"{fileName.Replace($".{extension}", "")}_{++increment}.{extension}";
    }

    return newName;
    }

    static void Delete<T>(T upload) where T : Upload
    {
    if (File.Exists(upload.Path))
    File.Delete(upload.Path);
    }

    static string GetUploadPath<T>(T upload) where T : Upload => upload switch
    {
    PersonImage _ => "person-images",
    _ => "uploads"
    };

    void SetUpload<T>(IFormFile file, T upload, string path, string url) where T : Upload
    {
    if (!Directory.Exists(path))
    Directory.CreateDirectory(path);

    string name = CreateSafeName(file, path);

    upload.File = name;
    upload.Name = file.Name;
    upload.Path = $"{path}{name}";
    upload.Url = $"{url}{name}";
    upload.FileType = file.ContentType;
    upload.Size = file.Length;
    }

    string UrlEncode(string url) => UrlEncode(url, urlPattern, "-");

    static string UrlEncode(string url, string pattern, string replace = "")
    {
    string friendlyUrl = Regex.Replace(url, @"\s", string.Empty).ToLower();
    friendlyUrl = Regex.Replace(friendlyUrl, pattern, replace);
    return friendlyUrl;
    }

    static ValidationMessage Validate(IFormFile file, string? filetype = null)
    {
    ValidationMessage message = new();

    if (!(file.Length > 0))
    message.AddMessage("File is empty");

    if (!string.IsNullOrWhiteSpace(filetype))
    {
    var invalid = !file.ContentType.Split('/')[0].Equals(filetype, StringComparison.CurrentCultureIgnoreCase);

    if (invalid)
    message.AddMessage($"File is not of type {filetype}");
    }

    return message;
    }

    async Task<T> Write<T>(IFormFile file, T upload) where T : Upload
    {
    string uploadPath = GetUploadPath(upload);
    string path = Path.Combine(paths.Directory, uploadPath);
    string url = Path.Combine(paths.Url, uploadPath);

    SetUpload(file, upload, path, url);

    using FileStream stream = new(upload.Path, FileMode.Create);
    await file.CopyToAsync(stream);

    return upload;
    }

    #endregion
    }