Skip to content

Commit

Permalink
Fix up consent buttons and api functions
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec committed Nov 20, 2023
1 parent 76046f1 commit 26c5129
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 545 deletions.
51 changes: 11 additions & 40 deletions Backend.Tests/Models/SpeakerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,41 @@ public class SpeakerTests
private const string ProjectId = "SpeakerTestsProjectId";
private const string Name = "Ms. Given Family";
private const string FileName = "audio.mp3";
private readonly Consent _consent = new() { FileName = FileName, FileType = ConsentType.Audio };

[Test]
public void TestClone()
{
var speakerA = new Speaker { Id = Id, ProjectId = ProjectId, Name = Name, Consent = _consent };
var speakerA = new Speaker { Id = Id, ProjectId = ProjectId, Name = Name, Consent = ConsentType.Audio };
Assert.That(speakerA.Equals(speakerA.Clone()), Is.True);
}

[Test]
public void TestEquals()
{
var speaker = new Speaker { Name = Name, Consent = _consent };
var speaker = new Speaker { Name = Name, Consent = ConsentType.Audio };
Assert.That(speaker.Equals(null), Is.False);
Assert.That(new Speaker { Id = "diff-id", ProjectId = ProjectId, Name = Name, Consent = _consent }
Assert.That(new Speaker { Id = "diff-id", ProjectId = ProjectId, Name = Name, Consent = ConsentType.Audio }
.Equals(speaker), Is.False);
Assert.That(new Speaker { Id = Id, ProjectId = "diff-proj-id", Name = Name, Consent = _consent }
Assert.That(new Speaker { Id = Id, ProjectId = "diff-proj-id", Name = Name, Consent = ConsentType.Audio }
.Equals(speaker), Is.False);
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = "Mr. Different", Consent = _consent }
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = "Mr. Diff", Consent = ConsentType.Audio }
.Equals(speaker), Is.False);
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = Name, Consent = new() }
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = Name, Consent = ConsentType.Image }
.Equals(speaker), Is.False);
}

[Test]
public void TestHashCode()
{
var code = new Speaker { Name = Name, Consent = _consent }.GetHashCode();
Assert.That(new Speaker { Id = "diff-id", ProjectId = ProjectId, Name = Name, Consent = _consent }
var code = new Speaker { Name = Name, Consent = ConsentType.Audio }.GetHashCode();
Assert.That(new Speaker { Id = "diff-id", ProjectId = ProjectId, Name = Name, Consent = ConsentType.Audio }
.GetHashCode(), Is.Not.EqualTo(code));
Assert.That(new Speaker { Id = Id, ProjectId = "diff-proj-id", Name = Name, Consent = _consent }
Assert.That(new Speaker { Id = Id, ProjectId = "diff-proj-id", Name = Name, Consent = ConsentType.Audio }
.GetHashCode(), Is.Not.EqualTo(code));
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = "Mr. Different", Consent = _consent }
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = "Mr. Diff", Consent = ConsentType.Audio }
.GetHashCode(), Is.Not.EqualTo(code));
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = Name, Consent = new() }
Assert.That(new Speaker { Id = Id, ProjectId = ProjectId, Name = Name, Consent = ConsentType.Image }
.GetHashCode(), Is.Not.EqualTo(code));
}
}

public class ConsentTests
{
private const string FileName = "audio.mp3";
private readonly Consent _consent = new() { FileName = FileName, FileType = ConsentType.Audio };

[Test]
public void TestClone()
{
Assert.That(_consent.Equals(_consent.Clone()), Is.True);
}

[Test]
public void TestEquals()
{
Assert.That(_consent.Equals(null), Is.False);
Assert.That(new Consent { FileName = "diff", FileType = ConsentType.Audio }.Equals(_consent), Is.False);
Assert.That(new Consent { FileName = FileName, FileType = ConsentType.Image }.Equals(_consent), Is.False);
}

[Test]
public void TestHashCode()
{
var code = _consent.GetHashCode();
Assert.That(new Consent { FileName = "diff", FileType = ConsentType.Audio }.GetHashCode(), Is.Not.EqualTo(code));
Assert.That(new Consent { FileName = FileName, FileType = ConsentType.Image }.GetHashCode(), Is.Not.EqualTo(code));
}
}
}
107 changes: 24 additions & 83 deletions Backend/Controllers/SpeakerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public async Task<IActionResult> DeleteSpeaker(string projectId, string speakerI

/// <summary> Removes consent of the <see cref="Speaker"/> for specified projectId and speakerId </summary>
/// <returns> Id of updated Speaker </returns>
[HttpGet("removeconsent/{speakerId}", Name = "RemoveConsent")]
[HttpDelete("consent/{speakerId}", Name = "RemoveConsent")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))]
public async Task<IActionResult> RemoveConsent(string projectId, string speakerId)
{
Expand All @@ -141,22 +141,18 @@ public async Task<IActionResult> RemoveConsent(string projectId, string speakerI
}

// Delete consent file
var filePath = speaker.Consent.FileName;
if (string.IsNullOrEmpty(filePath))
if (speaker.Consent is ConsentType.None)
{
return StatusCode(StatusCodes.Status304NotModified, speakerId);
}
if (speaker.Consent.FileType == ConsentType.Audio)
var path = FileStorage.GenerateConsentFilePath(speaker.Id);
if (IO.File.Exists(path))
{
filePath = FileStorage.GenerateAudioFilePath(projectId, filePath);
}
if (IO.File.Exists(filePath))
{
IO.File.Delete(filePath);
IO.File.Delete(path);
}

// Update speaker and return result with id
speaker.Consent = new();
speaker.Consent = ConsentType.None;
return await _speakerRepo.Update(speakerId, speaker) switch
{
ResultOfUpdate.NotFound => NotFound(speakerId),
Expand Down Expand Up @@ -195,16 +191,12 @@ public async Task<IActionResult> UpdateSpeakerName(string projectId, string spea
};
}

/// <summary>
/// Adds an audio consent from <see cref="FileUpload"/>
/// locally to ~/.CombineFiles/{ProjectId}/Import/ExtractedLocation/Lift/audio
/// and updates the <see cref="Consent"/> of the specified <see cref="Speaker"/>
/// </summary>
/// <summary> Saves a consent file locally and updates the specified <see cref="Speaker"/> </summary>
/// <returns> Updated speaker </returns>
[HttpPost("uploadconsentaudio/{speakerId}", Name = "UploadConsentAudio")]
[HttpPost("consent/{speakerId}", Name = "UploadConsent")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Speaker))]
public async Task<IActionResult> UploadConsentAudio(
string projectId, string speakerId, [FromForm] FileUpload fileUpload)
public async Task<IActionResult> UploadConsent(
string projectId, string speakerId, [FromForm] FileUpload upload)
{
// Sanitize user input
try
Expand All @@ -231,7 +223,7 @@ public async Task<IActionResult> UploadConsentAudio(
}

// Ensure file is valid
var file = fileUpload.File;
var file = upload.File;
if (file is null)
{
return BadRequest("Null File");
Expand All @@ -240,79 +232,27 @@ public async Task<IActionResult> UploadConsentAudio(
{
return BadRequest("Empty File");
}

// Copy file data to a local file with speakerId-dependent name
var path = FileStorage.GenerateAudioFilePathForWord(projectId, speakerId);
await using (var fs = new IO.FileStream(path, IO.FileMode.Create))
{
await file.CopyToAsync(fs);
}

// Update speaker consent and return result with speaker
var fileName = IO.Path.GetFileName(path);
speaker.Consent = new() { FileName = fileName, FileType = ConsentType.Audio };
return await _speakerRepo.Update(speakerId, speaker) switch
{
ResultOfUpdate.NotFound => NotFound(speaker),
_ => Ok(speaker),
};
}

/// <summary>
/// Adds an image consent from <see cref="FileUpload"/>
/// locally to ~/.CombineFiles/{ProjectId}/Avatars
/// and updates the <see cref="Consent"/> of the specified <see cref="Speaker"/>
/// </summary>
/// <returns> Updated speaker </returns>
[HttpPost("uploadconsentimage/{speakerId}", Name = "UploadConsentImage")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Speaker))]
public async Task<IActionResult> UploadConsentImage(
string projectId, string speakerId, [FromForm] FileUpload fileUpload)
{
// Sanitize user input
try
{
projectId = Sanitization.SanitizeId(projectId);
speakerId = Sanitization.SanitizeId(speakerId);
}
catch
{
return new UnsupportedMediaTypeResult();
}

// Check permissions
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry, projectId))
{
return Forbid();
}

// Ensure the speaker exists
var speaker = await _speakerRepo.GetSpeaker(projectId, speakerId);
if (speaker is null)
if (file.ContentType.Contains("audio"))
{
return NotFound(speakerId);
speaker.Consent = ConsentType.Audio;
}

// Ensure file is valid
var file = fileUpload.File;
if (file is null)
else if (file.ContentType.Contains("image"))
{
return BadRequest("Null File");
speaker.Consent = ConsentType.Image;
}
if (file.Length == 0)
else
{
return BadRequest("Empty File");
return BadRequest("File should be audio or image");
}

// Copy file data to a new local file
var path = FileStorage.GenerateAvatarFilePath(speakerId);
var path = FileStorage.GenerateConsentFilePath(speakerId);
await using (var fs = new IO.FileStream(path, IO.FileMode.OpenOrCreate))
{
await file.CopyToAsync(fs);
}

// Update speaker consent and return result with speaker
speaker.Consent = new() { FileName = path, FileType = ConsentType.Image };
// Update and return speaker
return await _speakerRepo.Update(speakerId, speaker) switch
{
ResultOfUpdate.NotFound => NotFound(speaker),
Expand All @@ -321,10 +261,11 @@ public async Task<IActionResult> UploadConsentImage(
}

/// <summary> Get speaker's consent </summary>
/// <returns> Stream of local image file </returns>
[HttpGet("downloadconsentimage/{speakerId}", Name = "DownloadConsentImage")]
/// <returns> Stream of local audio/image file </returns>
[AllowAnonymous]
[HttpGet("consent/{speakerId}", Name = "DownloadConsent")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FileContentResult))]
public IActionResult DownloadConsentImage(string speakerId)
public IActionResult DownloadConsent(string speakerId)
{
// SECURITY: Omitting authentication so the frontend can use the API endpoint directly as a URL.
// if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
Expand All @@ -333,7 +274,7 @@ public IActionResult DownloadConsentImage(string speakerId)
// }

// Ensure file exists
var path = FileStorage.GenerateAvatarFilePath(speakerId);
var path = FileStorage.GenerateConsentFilePath(speakerId);
if (!IO.File.Exists(path))
{
return NotFound(speakerId);
Expand Down
37 changes: 17 additions & 20 deletions Backend/Helper/FileStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ public static class FileStorage
{
private const string CombineFilesDir = ".CombineFiles";
private const string AvatarsDir = "Avatars";
private const string ConsentDir = "Consent";
private static readonly string ImportExtractedLocation = Path.Combine("Import", "ExtractedLocation");
private static readonly string LiftImportSuffix = Path.Combine(ImportExtractedLocation, "Lift");
private static readonly string AudioPathSuffix = Path.Combine(LiftImportSuffix, "audio");

public enum FileType
{
Audio,
Avatar
Avatar,
}

/// <summary> Indicates that an error occurred locating the current user's home directory. </summary>
Expand Down Expand Up @@ -51,9 +52,7 @@ public static string GenerateAudioFilePathForWord(string projectId, string wordI
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GenerateAudioFilePath(string projectId, string fileName)
{
projectId = Sanitization.SanitizeId(projectId);

return GenerateProjectFilePath(projectId, AudioPathSuffix, fileName);
return GenerateProjectFilePath(Sanitization.SanitizeId(projectId), AudioPathSuffix, fileName);
}

/// <summary>
Expand All @@ -62,9 +61,7 @@ public static string GenerateAudioFilePath(string projectId, string fileName)
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GenerateAudioFileDirPath(string projectId, bool createDir = true)
{
projectId = Sanitization.SanitizeId(projectId);

return GenerateProjectDirPath(projectId, AudioPathSuffix, createDir);
return GenerateProjectDirPath(Sanitization.SanitizeId(projectId), AudioPathSuffix, createDir);
}

/// <summary>
Expand All @@ -74,9 +71,7 @@ public static string GenerateAudioFileDirPath(string projectId, bool createDir =
/// <remarks> This function is not expected to be used often. </remarks>
public static string GenerateImportExtractedLocationDirPath(string projectId, bool createDir = true)
{
projectId = Sanitization.SanitizeId(projectId);

return GenerateProjectDirPath(projectId, ImportExtractedLocation, createDir);
return GenerateProjectDirPath(Sanitization.SanitizeId(projectId), ImportExtractedLocation, createDir);
}

/// <summary>
Expand All @@ -85,9 +80,7 @@ public static string GenerateImportExtractedLocationDirPath(string projectId, bo
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GenerateLiftImportDirPath(string projectId, bool createDir = true)
{
projectId = Sanitization.SanitizeId(projectId);

return GenerateProjectDirPath(projectId, LiftImportSuffix, createDir);
return GenerateProjectDirPath(Sanitization.SanitizeId(projectId), LiftImportSuffix, createDir);
}

/// <summary>
Expand All @@ -96,9 +89,16 @@ public static string GenerateLiftImportDirPath(string projectId, bool createDir
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GenerateAvatarFilePath(string userId)
{
userId = Sanitization.SanitizeId(userId);
return GenerateFilePath(AvatarsDir, Sanitization.SanitizeId(userId), FileType.Avatar);
}

return GenerateFilePath(AvatarsDir, userId, FileType.Avatar);
/// <summary>
/// Generate the path to where Consent audio/images are stored.
/// </summary>
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GenerateConsentFilePath(string speakerId)
{
return GenerateFilePath(ConsentDir, Sanitization.SanitizeId(speakerId));
}

/// <summary>
Expand All @@ -107,9 +107,7 @@ public static string GenerateAvatarFilePath(string userId)
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GetProjectDir(string projectId)
{
projectId = Sanitization.SanitizeId(projectId);

return GenerateProjectDirPath(projectId, "", false);
return GenerateProjectDirPath(Sanitization.SanitizeId(projectId), "", false);
}

/// <summary> Get the path to the home directory of the current user. </summary>
Expand Down Expand Up @@ -172,8 +170,7 @@ private static string GenerateProjectFilePath(

private static string GenerateFilePath(string suffixPath, string fileName)
{
var dirPath = GenerateDirPath(suffixPath, true);
return Path.Combine(dirPath, fileName);
return Path.Combine(GenerateDirPath(suffixPath, true), fileName);
}

private static string GenerateFilePath(string suffixPath, string fileNameSuffix, FileType type)
Expand Down
Loading

0 comments on commit 26c5129

Please sign in to comment.