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

Convert song list edit to React #1144

Merged
merged 1 commit into from
Jul 23, 2022
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
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