Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cwdoe 1335 copy/upload/download collections and exhibits #25

Merged
merged 3 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Gallery.Api/Controllers/CollectionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,25 @@ public async Task<IActionResult> Create([FromBody] Collection collection, Cancel
return CreatedAtAction(nameof(this.Get), new { id = createdCollection.Id }, createdCollection);
}

/// <summary>
/// Creates a new Collection by copying an existing Collection
/// </summary>
/// <remarks>
/// Creates a new Collection from the specified existing Collection
/// <para />
/// Accessible only to a ContentDeveloper or an Administrator
/// </remarks>
/// <param name="id">The ID of the Collection to be copied</param>
/// <param name="ct"></param>
[HttpPost("collections/{id}/copy")]
[ProducesResponseType(typeof(Collection), (int)HttpStatusCode.Created)]
[SwaggerOperation(OperationId = "copyCollection")]
public async Task<IActionResult> Copy(Guid id, CancellationToken ct)
{
var createdCollection = await _collectionService.CopyAsync(id, ct);
return CreatedAtAction(nameof(this.Get), new { id = createdCollection.Id }, createdCollection);
}

/// <summary>
/// Updates a Collection
/// </summary>
Expand Down Expand Up @@ -144,6 +163,32 @@ public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
return NoContent();
}

/// <summary> Upload a json Collection file </summary>
/// <param name="form"> The files to upload and their settings </param>
/// <param name="ct"></param>
[HttpPost("collections/json")]
[ProducesResponseType(typeof(Collection), (int)HttpStatusCode.OK)]
[SwaggerOperation(OperationId = "uploadJsonFiles")]
public async Task<IActionResult> UploadJsonAsync([FromForm] FileForm form, CancellationToken ct)
{
var result = await _collectionService.UploadJsonAsync(form, ct);
return Ok(result);
}

/// <summary> Download a Collection by id as json file </summary>
/// <param name="id"> The id of the collection </param>
/// <param name="ct"></param>
[HttpGet("collections/{id}/json")]
[ProducesResponseType(typeof(FileResult), (int)HttpStatusCode.OK)]
[SwaggerOperation(OperationId = "downloadJson")]
public async Task<IActionResult> DownloadJsonAsync(Guid id, CancellationToken ct)
{
(var stream, var fileName) = await _collectionService.DownloadJsonAsync(id, ct);

// If this is wrapped in an Ok, it throws an exception
return File(stream, "application/octet-stream", fileName);
}

}
}

45 changes: 45 additions & 0 deletions Gallery.Api/Controllers/ExhibitController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@ public async Task<IActionResult> Create([FromBody] Exhibit exhibit, Cancellation
return CreatedAtAction(nameof(this.Get), new { id = createdExhibit.Id }, createdExhibit);
}

/// <summary>
/// Creates a new Exhibit by copying an existing Exhibit
/// </summary>
/// <remarks>
/// Creates a new Exhibit from the specified existing Exhibit
/// <para />
/// Accessible only to a ContentDeveloper or an Administrator
/// </remarks>
/// <param name="id">The ID of the Exhibit to be copied</param>
/// <param name="ct"></param>
[HttpPost("exhibits/{id}/copy")]
[ProducesResponseType(typeof(Exhibit), (int)HttpStatusCode.Created)]
[SwaggerOperation(OperationId = "copyExhibit")]
public async Task<IActionResult> Copy(Guid id, CancellationToken ct)
{
var createdExhibit = await _exhibitService.CopyAsync(id, ct);
return CreatedAtAction(nameof(this.Get), new { id = createdExhibit.Id }, createdExhibit);
}

/// <summary>
/// Updates an Exhibit
/// </summary>
Expand Down Expand Up @@ -217,6 +236,32 @@ public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
return NoContent();
}

/// <summary> Upload a json Exhibit file </summary>
/// <param name="form"> The files to upload and their settings </param>
/// <param name="ct"></param>
[HttpPost("exhibits/json")]
[ProducesResponseType(typeof(Exhibit), (int)HttpStatusCode.OK)]
[SwaggerOperation(OperationId = "uploadJsonFiles")]
public async Task<IActionResult> UploadJsonAsync([FromForm] FileForm form, CancellationToken ct)
{
var result = await _exhibitService.UploadJsonAsync(form, ct);
return Ok(result);
}

/// <summary> Download a Exhibit by id as json file </summary>
/// <param name="id"> The id of the exhibit </param>
/// <param name="ct"></param>
[HttpGet("exhibits/{id}/json")]
[ProducesResponseType(typeof(FileResult), (int)HttpStatusCode.OK)]
[SwaggerOperation(OperationId = "downloadJson")]
public async Task<IActionResult> DownloadJsonAsync(Guid id, CancellationToken ct)
{
(var stream, var fileName) = await _exhibitService.DownloadJsonAsync(id, ct);

// If this is wrapped in an Ok, it throws an exception
return File(stream, "application/octet-stream", fileName);
}

}
}

2 changes: 1 addition & 1 deletion Gallery.Api/Gallery.Api.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<Version>1.5.2-rc1</Version>
<Version>1.6.0-rc1</Version>
<TargetFramework>net6.0</TargetFramework>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
Expand Down
156 changes: 156 additions & 0 deletions Gallery.Api/Services/CollectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
Expand All @@ -26,6 +30,9 @@ public interface ICollectionService
Task<IEnumerable<ViewModels.Collection>> GetMineAsync(CancellationToken ct);
Task<ViewModels.Collection> GetAsync(Guid id, CancellationToken ct);
Task<ViewModels.Collection> CreateAsync(ViewModels.Collection collection, CancellationToken ct);
Task<ViewModels.Collection> CopyAsync(Guid collectionId, CancellationToken ct);
Task<Tuple<MemoryStream, string>> DownloadJsonAsync(Guid collectionId, CancellationToken ct);
Task<Collection> UploadJsonAsync(FileForm form, CancellationToken ct);
Task<ViewModels.Collection> UpdateAsync(Guid id, ViewModels.Collection collection, CancellationToken ct);
Task<bool> DeleteAsync(Guid id, CancellationToken ct);
}
Expand Down Expand Up @@ -103,6 +110,148 @@ public CollectionService(
return collection;
}

public async Task<ViewModels.Collection> CopyAsync(Guid collectionId, CancellationToken ct)
{
if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
throw new ForbiddenException();

var collectionEntity = await _context.Collections
.AsNoTracking()
.SingleOrDefaultAsync(m => m.Id == collectionId);
if (collectionEntity == null)
throw new EntityNotFoundException<CollectionEntity>("Collection not found with ID=" + collectionId.ToString());

var cards = await _context.Cards
.AsNoTracking()
.Where(c => c.CollectionId == collectionId)
.ToListAsync(ct);
var articles = await _context.Articles
.AsNoTracking()
.Where(c => c.CollectionId == collectionId)
.ToListAsync(ct);
var newCollectionEntity = await privateCollectionCopyAsync(collectionEntity, cards, articles, ct);
var collection = _mapper.Map<Collection>(newCollectionEntity);

return collection;
}

private async Task<CollectionEntity> privateCollectionCopyAsync(
CollectionEntity collectionEntity,
List<CardEntity> cards,
List<ArticleEntity> articles,
CancellationToken ct)
{
var currentUserId = _user.GetId();
var username = (await _context.Users.SingleOrDefaultAsync(u => u.Id == _user.GetId())).Name;
var oldCollectionId = collectionEntity.Id;
var newCollectionId = Guid.NewGuid();
var dateCreated = DateTime.UtcNow;
collectionEntity.Id = newCollectionId;
collectionEntity.DateCreated = dateCreated;
collectionEntity.CreatedBy = currentUserId;
collectionEntity.DateModified = collectionEntity.DateCreated;
collectionEntity.ModifiedBy = collectionEntity.CreatedBy;
collectionEntity.Name = collectionEntity.Name + " - " + username;
await _context.Collections.AddAsync(collectionEntity, ct);
// copy cards
var newCardIds = new Dictionary<Guid, Guid>();
foreach (var cardEntity in cards)
{
newCardIds[cardEntity.Id] = Guid.NewGuid();
cardEntity.Id = newCardIds[cardEntity.Id];
cardEntity.CollectionId = collectionEntity.Id;
cardEntity.Collection = null;
cardEntity.DateCreated = collectionEntity.DateCreated;
cardEntity.CreatedBy = collectionEntity.CreatedBy;
await _context.Cards.AddAsync(cardEntity, ct);
}
// copy articles
foreach (var articleEntity in articles)
{
articleEntity.Id = Guid.NewGuid();
articleEntity.CollectionId = newCollectionId;
articleEntity.Collection = null;
articleEntity.CardId = articleEntity.CardId == null ? null : newCardIds[(Guid)articleEntity.CardId];
articleEntity.Card = null;
articleEntity.DateCreated = collectionEntity.DateCreated;
articleEntity.CreatedBy = collectionEntity.CreatedBy;
await _context.Articles.AddAsync(articleEntity, ct);
}
await _context.SaveChangesAsync(ct);

// get the new Collection to return
collectionEntity = await _context.Collections
.SingleOrDefaultAsync(sm => sm.Id == newCollectionId, ct);

return collectionEntity;
}

public async Task<Tuple<MemoryStream, string>> DownloadJsonAsync(Guid collectionId, CancellationToken ct)
{
// user must be a Content Developer
if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
throw new ForbiddenException();

var collection = await _context.Collections
.SingleOrDefaultAsync(sm => sm.Id == collectionId, ct);
if (collection == null)
{
throw new EntityNotFoundException<CollectionEntity>("Collection not found " + collectionId);
}
// get the cards
var cards = await _context.Cards
.AsNoTracking()
.Where(c => c.CollectionId == collectionId)
.ToListAsync(ct);
//get the articles
var articles = await _context.Articles
.AsNoTracking()
.Where(c => c.CollectionId == collectionId)
.ToListAsync(ct);
// create the whole object
var collectionFileObject = new CollectionFileFormat(){
Collection = collection,
Cards = cards,
Articles = articles
};
var collectionFileJson = "";
var options = new JsonSerializerOptions()
{
ReferenceHandler = ReferenceHandler.Preserve
};
collectionFileJson = JsonSerializer.Serialize(collectionFileObject, options);
// convert string to stream
byte[] byteArray = Encoding.ASCII.GetBytes(collectionFileJson);
MemoryStream memoryStream = new MemoryStream(byteArray);
var filename = collection.Description.ToLower().EndsWith(".json") ? collection.Description : collection.Description + ".json";

return System.Tuple.Create(memoryStream, filename);
}

public async Task<Collection> UploadJsonAsync(FileForm form, CancellationToken ct)
{
// user must be a Content Developer
if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
throw new ForbiddenException();

var uploadItem = form.ToUpload;
var collectionJson = "";
using (StreamReader reader = new StreamReader(uploadItem.OpenReadStream()))
{
// convert stream to string
collectionJson = reader.ReadToEnd();
}
var options = new JsonSerializerOptions()
{
ReferenceHandler = ReferenceHandler.Preserve
};
var collectionFileObject = JsonSerializer.Deserialize<CollectionFileFormat>(collectionJson, options);
// make a copy and add it to the database
var collectionEntity = await privateCollectionCopyAsync(collectionFileObject.Collection, collectionFileObject.Cards, collectionFileObject.Articles, ct);

return _mapper.Map<Collection>(collectionEntity);
}

public async Task<ViewModels.Collection> UpdateAsync(Guid id, ViewModels.Collection collection, CancellationToken ct)
{
if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
Expand Down Expand Up @@ -144,5 +293,12 @@ public async Task<bool> DeleteAsync(Guid id, CancellationToken ct)
}

}

class CollectionFileFormat
{
public CollectionEntity Collection { get; set; }
public List<CardEntity> Cards { get; set; }
public List<ArticleEntity> Articles { get; set; }
}
}

Loading
Loading