Skip to content

Commit

Permalink
Convert song list edit to React
Browse files Browse the repository at this point in the history
  • Loading branch information
ycanardeau committed Jul 23, 2022
1 parent 96a5142 commit cc6425c
Show file tree
Hide file tree
Showing 24 changed files with 928 additions and 80 deletions.
5 changes: 3 additions & 2 deletions Tests/Web/Controllers/DataAccess/SongListQueriesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Net.Mime;
using VocaDb.Model.Database.Queries;
using VocaDb.Model.DataContracts;
using VocaDb.Model.DataContracts.SongLists;
using VocaDb.Model.DataContracts.Songs;
using VocaDb.Model.DataContracts.Users;
using VocaDb.Model.Domain;
Expand All @@ -28,7 +29,7 @@ public class SongListQueriesTests
private InMemoryImagePersister _imagePersister;
private FakePermissionContext _permissionContext;
private FakeSongListRepository _repository;
private SongListForEditContract _songListContract;
private SongListForEditForApiContract _songListContract;
private SongListQueries _queries;
private Song _song1;
private Song _song2;
Expand Down Expand Up @@ -60,7 +61,7 @@ public void SetUp()
_repository.Add(_userWithSongList);
_repository.Add(_song1, _song2);

_songListContract = new SongListForEditContract
_songListContract = new SongListForEditForApiContract
{
Name = "Mikunopolis Setlist",
Description = "MIKUNOPOLIS in LOS ANGELES - Hatsune Miku US debut concert held at Nokia Theatre for Anime Expo 2011 on 2nd July 2011.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Newtonsoft.Json.Converters;
using VocaDb.Model.DataContracts.Songs;
using VocaDb.Model.Domain;
using VocaDb.Model.Domain.Images;
using VocaDb.Model.Domain.Security;
using VocaDb.Model.Domain.Songs;

namespace VocaDb.Model.DataContracts.SongLists;

[DataContract(Namespace = Schemas.VocaDb)]
public sealed record SongListForEditForApiContract
{
[DataMember]
public bool Deleted { get; init; }

[DataMember]
public string Description { get; init; }

[DataMember]
public DateTime? EventDate { get; init; }

[DataMember]
[JsonConverter(typeof(StringEnumConverter))]
public SongListFeaturedCategory FeaturedCategory { get; init; }

[DataMember]
public int Id { get; set; }

[DataMember(EmitDefaultValue = false)]
public EntryThumbForApiContract? MainPicture { get; init; }

[DataMember]
public string Name { get; init; }

[DataMember]
public SongInListEditContract[] SongLinks { get; set; }

[DataMember]
public EntryStatus Status { get; init; }

[DataMember]
public string UpdateNotes { get; init; }

public SongListForEditForApiContract()
{
Description = string.Empty;
Name = string.Empty;
SongLinks = Array.Empty<SongInListEditContract>();
UpdateNotes = string.Empty;
}

public SongListForEditForApiContract(
SongList songList,
IUserPermissionContext permissionContext,
IAggregatedEntryImageUrlFactory imagePersister
)
{
Deleted = songList.Deleted;
Description = songList.Description;
EventDate = songList.EventDate;
FeaturedCategory = songList.FeaturedCategory;
Id = songList.Id;
MainPicture = songList.Thumb is not null
? new EntryThumbForApiContract(songList.Thumb, imagePersister)
: null;
Name = songList.Name;
SongLinks = songList.SongLinks
.OrderBy(s => s.Order)
.Select(s => new SongInListEditContract(s, permissionContext.LanguagePreference))
.ToArray();
Status = songList.Status;
UpdateNotes = string.Empty;
}
}
1 change: 1 addition & 0 deletions VocaDbModel/DataContracts/Songs/SongListForEditContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace VocaDb.Model.DataContracts.Songs
{
[Obsolete]
[DataContract(Namespace = Schemas.VocaDb)]
public class SongListForEditContract : SongListContract
{
Expand Down
48 changes: 34 additions & 14 deletions VocaDbModel/Database/Queries/SongListQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ private PartialImportedSongs FindSongs(PartialImportedSongs songs)
});
}

private PartialFindResult<T> GetSongsInList<T>(IDatabaseContext<SongList> session, SongInListQueryParams queryParams,
Func<SongInList, T> fac)
private PartialFindResult<T> GetSongsInList<T>(
IDatabaseContext<SongList> session,
SongInListQueryParams queryParams,
Func<SongInList, T> fac
)
{
var q = session.OfType<SongInList>().Query()
.Where(a => !a.Song.Deleted && a.List.Id == queryParams.ListId)
Expand All @@ -80,7 +83,7 @@ private PartialFindResult<T> GetSongsInList<T>(IDatabaseContext<SongList> sessio
return new PartialFindResult<T>(contracts, totalCount);
}

private SongList CreateSongList(IDatabaseContext<SongList> ctx, SongListForEditContract contract, UploadedFileContract uploadedFile)
private SongList CreateSongList(IDatabaseContext<SongList> ctx, SongListForEditForApiContract contract, UploadedFileContract uploadedFile)
{
var user = GetLoggedUser(ctx);
var newList = new SongList(contract.Name, user);
Expand Down Expand Up @@ -123,8 +126,14 @@ private void SetThumb(SongList list, UploadedFileContract? uploadedFile)
}
#nullable disable

public SongListQueries(ISongListRepository repository, IUserPermissionContext permissionContext, IEntryLinkFactory entryLinkFactory,
IEntryThumbPersister imagePersister, IAggregatedEntryImageUrlFactory thumbStore, IUserIconFactory userIconFactory)
public SongListQueries(
ISongListRepository repository,
IUserPermissionContext permissionContext,
IEntryLinkFactory entryLinkFactory,
IEntryThumbPersister imagePersister,
IAggregatedEntryImageUrlFactory thumbStore,
IUserIconFactory userIconFactory
)
: base(repository, permissionContext)
{
_entryLinkFactory = entryLinkFactory;
Expand Down Expand Up @@ -220,7 +229,13 @@ public SongListForApiContract GetDetails(int listId)
{
return _repository.HandleQuery(ctx =>
{
return new SongListForApiContract(ctx.Load(listId), LanguagePreference, _userIconFactory, _thumbStore, SongListOptionalFields.Description | SongListOptionalFields.Events | SongListOptionalFields.MainPicture | SongListOptionalFields.Tags)
return new SongListForApiContract(
list: ctx.Load(listId),
languagePreference: LanguagePreference,
userIconFactory: _userIconFactory,
imagePersister: _thumbStore,
fields: SongListOptionalFields.Description | SongListOptionalFields.Events | SongListOptionalFields.MainPicture | SongListOptionalFields.Tags
)
{
LatestComments = Comments(ctx).GetList(listId, 3)
};
Expand All @@ -242,9 +257,9 @@ public SongListContract GetSongList(int listId)
return _repository.HandleQuery(session => new SongListContract(session.Load(listId), PermissionContext));
}

public SongListForEditContract GetSongListForEdit(int listId)
public SongListForEditForApiContract GetSongListForEdit(int listId)
{
return _repository.HandleQuery(session => new SongListForEditContract(session.Load(listId), PermissionContext));
return _repository.HandleQuery(session => new SongListForEditForApiContract(session.Load(listId), PermissionContext, _thumbStore));
}

[Obsolete]
Expand Down Expand Up @@ -290,7 +305,7 @@ public async Task<PartialImportedSongs> ImportSongs(string url, string pageToken
}

#nullable enable
public int UpdateSongList(SongListForEditContract contract, UploadedFileContract? uploadedFile)
public int UpdateSongList(SongListForEditForApiContract contract, UploadedFileContract? uploadedFile)
{
ParamIs.NotNull(() => contract);

Expand Down Expand Up @@ -375,12 +390,15 @@ public int UpdateSongList(SongListForEditContract contract, UploadedFileContract
}
#nullable disable

public void DeleteComment(int commentId) => HandleTransaction(ctx => Comments(ctx).Delete(commentId));
public void DeleteComment(int commentId) =>
HandleTransaction(ctx => Comments(ctx).Delete(commentId));

public IEnumerable<string> GetFeaturedListNames(string query = "",
public IEnumerable<string> GetFeaturedListNames(
string query = "",
NameMatchMode nameMatchMode = NameMatchMode.Auto,
SongListFeaturedCategory? featuredCategory = null,
int maxResults = 10)
int maxResults = 10
)
{
var textQuery = SearchTextQuery.Create(query, nameMatchMode);

Expand All @@ -397,9 +415,11 @@ public IEnumerable<string> GetFeaturedListNames(string query = "",
});
}

public void PostEditComment(int commentId, CommentForApiContract contract) => HandleTransaction(ctx => Comments(ctx).Update(commentId, contract));
public void PostEditComment(int commentId, CommentForApiContract contract) =>
HandleTransaction(ctx => Comments(ctx).Update(commentId, contract));

public string GetTagString(int id, string formatString) => HandleQuery(ctx => new SongListFormatter(_entryLinkFactory).ApplyFormat(ctx.Load(id), formatString, PermissionContext.LanguagePreference, true));
public string GetTagString(int id, string formatString) =>
HandleQuery(ctx => new SongListFormatter(_entryLinkFactory).ApplyFormat(ctx.Load(id), formatString, PermissionContext.LanguagePreference, true));

#nullable enable
public SongListBaseContract GetOne(int id)
Expand Down
42 changes: 38 additions & 4 deletions VocaDbWeb/Controllers/Api/SongListApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using VocaDb.Model.Service.Search;
using VocaDb.Model.Service.Search.SongSearch;
using VocaDb.Model.Service.SongImport;
using VocaDb.Web.Code;
using VocaDb.Web.Code.Security;
using VocaDb.Web.Models.Shared;
using ApiController = Microsoft.AspNetCore.Mvc.ControllerBase;
Expand Down Expand Up @@ -95,7 +96,7 @@ public void Delete(int id, string notes = "", bool hardDelete = false)

[HttpGet("{id:int}/for-edit")]
[ApiExplorerSettings(IgnoreApi = true)]
public SongListForEditContract GetForEdit(int id) => _queries.GetSongListForEdit(id);
public SongListForEditForApiContract GetForEdit(int id) => _queries.GetSongListForEdit(id);

#nullable enable
/// <summary>
Expand Down Expand Up @@ -213,7 +214,8 @@ public PartialFindResult<SongInListForApiContract> GetSongs(
AdvancedFilters = advancedFilters?.Select(advancedFilter => advancedFilter.ToAdvancedSearchFilter()).ToArray(),
SongTypes = types,
},
songInList => new SongInListForApiContract(songInList, lang, fields));
songInList => new SongInListForApiContract(songInList, lang, fields)
);
}
#nullable disable

Expand Down Expand Up @@ -252,12 +254,12 @@ public async Task<ActionResult<PartialImportedSongs>> GetImportSongs(string url,
/// <returns>ID of the created list.</returns>
[HttpPost("")]
[Authorize]
public ActionResult<int> Post(SongListForEditContract list)
public ActionResult<int> Post(SongListForEditForApiContract list)
{
if (list == null)
return BadRequest();

return _queries.UpdateSongList(list, null);
return _queries.UpdateSongList(list, uploadedFile: null);
}

/// <summary>
Expand Down Expand Up @@ -296,6 +298,38 @@ public EntryWithArchivedVersionsForApiContract<SongListForApiContract> GetSongLi
[HttpGet("{id:int}")]
[ApiExplorerSettings(IgnoreApi = true)]
public SongListBaseContract GetOne(int id) => _queries.GetOne(id);

[HttpPost("{id:int}")]
[Authorize]
[EnableCors(AuthenticationConstants.AuthenticatedCorsApiPolicy)]
[ValidateAntiForgeryToken]
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult<int> Edit(
[ModelBinder(BinderType = typeof(JsonModelBinder))] SongListForEditForApiContract contract
)
{
if (contract is null)
{
return BadRequest("View model was null - probably JavaScript is disabled");
}

var coverPicUpload = Request.Form.Files["thumbPicUpload"];
UploadedFileContract? uploadedPicture = null;
if (coverPicUpload is not null && coverPicUpload.Length > 0)
{
ControllerBase.CheckUploadedPicture(this, coverPicUpload, "thumbPicUpload");
uploadedPicture = new UploadedFileContract { Mime = coverPicUpload.ContentType, Stream = coverPicUpload.OpenReadStream() };
}

if (!ModelState.IsValid)
{
return ValidationProblem(ModelState);
}

var listId = _queries.UpdateSongList(contract, uploadedPicture);

return listId;
}
#nullable disable
}
}
3 changes: 2 additions & 1 deletion VocaDbWeb/Controllers/SongController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Mvc;
using NLog;
using VocaDb.Model.Database.Queries;
using VocaDb.Model.DataContracts.SongLists;
using VocaDb.Model.DataContracts.Songs;
using VocaDb.Model.Domain;
using VocaDb.Model.Domain.PVs;
Expand Down Expand Up @@ -72,7 +73,7 @@ public void AddSongToList(int listId, int songId, string notes = null, string ne
}
else if (!string.IsNullOrWhiteSpace(newListName))
{
var contract = new SongListForEditContract
var contract = new SongListForEditForApiContract
{
Name = newListName,
SongLinks = new[] {new SongInListEditContract {
Expand Down
37 changes: 3 additions & 34 deletions VocaDbWeb/Controllers/SongListController.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#nullable disable

using System.Net;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using VocaDb.Model.Database.Queries;
using VocaDb.Model.DataContracts;
using VocaDb.Model.DataContracts.Songs;
using VocaDb.Model.Domain.Images;
using VocaDb.Model.Domain.Songs;
using VocaDb.Model.Service;
Expand Down Expand Up @@ -77,43 +74,15 @@ public ActionResult Details(int id = InvalidId)
return View("React/Index");
}

#nullable enable
//
// GET: /SongList/Edit/
[Authorize]
public ActionResult Edit(int? id)
{
var contract = id != null ? _queries.GetSongList(id.Value) : new SongListContract();
var model = new SongListEditViewModel(contract, PermissionContext);

return View(model);
}

[HttpPost]
[Authorize]
public ActionResult Edit(SongListEditViewModel model)
{
if (model == null)
{
return HttpStatusCodeResult(HttpStatusCode.BadRequest, "View model was null - probably JavaScript is disabled");
}

var coverPicUpload = Request.Form.Files["thumbPicUpload"];
UploadedFileContract uploadedPicture = null;
if (coverPicUpload != null && coverPicUpload.Length > 0)
{
CheckUploadedPicture(coverPicUpload, "thumbPicUpload");
uploadedPicture = new UploadedFileContract { Mime = coverPicUpload.ContentType, Stream = coverPicUpload.OpenReadStream() };
}

if (!ModelState.IsValid)
{
return View(new SongListEditViewModel(model.ToContract(), PermissionContext));
}

var listId = _queries.UpdateSongList(model.ToContract(), uploadedPicture);

return RedirectToAction("Details", new { id = listId });
return View("React/Index");
}
#nullable disable

public ActionResult Export(int id)
{
Expand Down
2 changes: 1 addition & 1 deletion VocaDbWeb/Scripts/Components/Event/EventEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import EventCategory from '@Models/Events/EventCategory';
import ContentLanguageSelection from '@Models/Globalization/ContentLanguageSelection';
import ImageSize from '@Models/Images/ImageSize';
import LoginManager from '@Models/LoginManager';
import SongListFeaturedCategory from '@Models/SongLists/SongListFeaturedCategory';
import ArtistRepository from '@Repositories/ArtistRepository';
import PVRepository from '@Repositories/PVRepository';
import ReleaseEventRepository from '@Repositories/ReleaseEventRepository';
Expand All @@ -22,7 +23,6 @@ import EntryUrlMapper from '@Shared/EntryUrlMapper';
import HttpClient from '@Shared/HttpClient';
import UrlMapper from '@Shared/UrlMapper';
import ReleaseEventEditStore from '@Stores/ReleaseEvent/ReleaseEventEditStore';
import { SongListFeaturedCategory } from '@Stores/SongList/FeaturedSongListsStore';
import _ from 'lodash';
import { runInAction } from 'mobx';
import { observer } from 'mobx-react-lite';
Expand Down
Loading

0 comments on commit cc6425c

Please sign in to comment.