diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteView.axaml new file mode 100644 index 000000000..113a21d96 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteView.axaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteView.axaml.cs new file mode 100644 index 000000000..5c131be6a --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public partial class EntryVoteView : ReactiveUserControl +{ + public EntryVoteView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteViewModel.cs new file mode 100644 index 000000000..14e449974 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryVoteViewModel.cs @@ -0,0 +1,91 @@ +using System; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.Builders; +using Artemis.WebClient.Workshop; +using Artemis.WebClient.Workshop.Services; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public partial class EntryVoteViewModel : ActivatableViewModelBase +{ + private readonly IEntrySummary _entry; + private readonly INotificationService _notificationService; + private readonly IVoteClient _voteClient; + private bool _voting; + + [Notify] private int _score; + [Notify] private bool _upvoted; + [Notify] private bool _downvoted; + + public EntryVoteViewModel(IEntrySummary entry, IAuthenticationService authenticationService, INotificationService notificationService, IVoteClient voteClient) + { + _entry = entry; + _notificationService = notificationService; + _voteClient = voteClient; + + IsLoggedIn = authenticationService.IsLoggedIn; + Score = entry.UpvoteCount - entry.DownvoteCount; + this.WhenActivated(d => IsLoggedIn.Subscribe(l => _ = GetVoteStatus(l)).DisposeWith(d)); + } + + public IObservable IsLoggedIn { get; } + + public async Task CastVote(bool upvote) + { + // Could use a ReactiveCommand to achieve the same thing but that disables the button + // while executing which grays it out for a fraction of a second and looks bad + if (_voting) + return; + + _voting = true; + try + { + IVoteCount? result; + // If the vote was removed, reset the upvote/downvote state + if ((Upvoted && upvote) || (Downvoted && !upvote)) + { + result = await _voteClient.ClearVote(_entry.Id); + Upvoted = false; + Downvoted = false; + } + else + { + result = await _voteClient.CastVote(_entry.Id, upvote); + Upvoted = upvote; + Downvoted = !upvote; + } + + if (result != null) + Score = result.UpvoteCount - result.DownvoteCount; + else + _notificationService.CreateNotification().WithTitle("Failed to cast vote").WithMessage("Please try again later.").WithSeverity(NotificationSeverity.Error).Show(); + } + finally + { + _voting = false; + } + } + + private async Task GetVoteStatus(bool isLoggedIn) + { + if (!isLoggedIn) + { + Upvoted = false; + Downvoted = false; + } + else + { + bool? vote = await _voteClient.GetVote(_entry.Id); + if (vote != null) + { + Upvoted = vote.Value; + Downvoted = !vote.Value; + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml index 8fdc91d29..476ae9bd2 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListInputView.axaml @@ -21,6 +21,7 @@ Recently updated Recently added Download count + Score diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml index 22d723a06..7473034d3 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml @@ -21,9 +21,12 @@ HorizontalContentAlignment="Stretch" Command="{CompiledBinding NavigateToEntry}" IsVisible="{CompiledBinding Entry, Converter={x:Static ObjectConverters.IsNotNull}}"> - + + + + - - + @@ -79,7 +82,7 @@ - + @@ -89,7 +92,7 @@ - + installed diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs index d129f9be6..a02325bc4 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs @@ -1,5 +1,4 @@ using System; -using System.Reactive; using System.Reactive.Disposables; using System.Threading.Tasks; using Artemis.UI.Shared; @@ -18,12 +17,12 @@ public partial class EntryListItemViewModel : ActivatableViewModelBase [Notify] private bool _isInstalled; [Notify] private bool _updateAvailable; - public EntryListItemViewModel(IEntrySummary entry, IRouter router, IWorkshopService workshopService) + public EntryListItemViewModel(IEntrySummary entry, IRouter router, IWorkshopService workshopService, Func getEntryVoteViewModel) { _router = router; Entry = entry; - NavigateToEntry = ReactiveCommand.CreateFromTask(ExecuteNavigateToEntry); + VoteViewModel = getEntryVoteViewModel(entry); this.WhenActivated((CompositeDisposable _) => { @@ -34,9 +33,9 @@ public partial class EntryListItemViewModel : ActivatableViewModelBase } public IEntrySummary Entry { get; } - public ReactiveCommand NavigateToEntry { get; } + public EntryVoteViewModel VoteViewModel { get; } - private async Task ExecuteNavigateToEntry() + public async Task NavigateToEntry() { switch (Entry.EntryType) { diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs index a6b5309c7..48388aeb5 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs @@ -137,6 +137,9 @@ public partial class EntryListViewModel : RoutableScreen if (InputViewModel.SortBy == 2) return new[] {new EntrySortInput {Downloads = SortEnumType.Desc}}; + // Sort by score + if (InputViewModel.SortBy == 3) + return new[] {new EntrySortInput {Score = SortEnumType.Desc}}; // Sort by latest release, then by created at return new[] diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemView.axaml b/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemView.axaml index 71ab5983c..b8347c3df 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemView.axaml @@ -11,7 +11,6 @@ x:DataType="tabs:InstalledTabItemViewModel"> - diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemViewModel.cs index 6307a8557..75a58ce75 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/Tabs/InstalledTabItemViewModel.cs @@ -10,6 +10,7 @@ using Artemis.Core.Services; using Artemis.UI.DryIoc.Factories; using Artemis.UI.Extensions; using Artemis.UI.Screens.Plugins; +using Artemis.UI.Screens.Workshop.Entries; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; @@ -35,6 +36,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase [Notify] private bool _updateAvailable; [Notify] private bool _autoUpdate; + [Notify] private EntryVoteViewModel _voteViewModel; public InstalledTabItemViewModel(InstalledEntry entry, IWorkshopClient client, @@ -43,7 +45,8 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase IRouter router, IWindowService windowService, IPluginManagementService pluginManagementService, - ISettingsVmFactory settingsVmFactory) + ISettingsVmFactory settingsVmFactory, + Func getEntryVoteViewModel) { _client = client; _workshopService = workshopService; @@ -53,9 +56,9 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase _pluginManagementService = pluginManagementService; _settingsVmFactory = settingsVmFactory; _autoUpdate = entry.AutoUpdate; - - Entry = entry; + Entry = entry; + this.WhenActivatedAsync(async _ => { // Grab the latest entry summary from the workshop @@ -65,6 +68,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase if (entrySummary.Data?.Entry != null) { Entry.ApplyEntrySummary(entrySummary.Data.Entry); + VoteViewModel = getEntryVoteViewModel(entrySummary.Data.Entry); _workshopService.SaveInstalledEntry(Entry); } } @@ -73,7 +77,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase UpdateAvailable = Entry.ReleaseId != Entry.LatestReleaseId; } }); - + this.WhenAnyValue(vm => vm.AutoUpdate).Skip(1).Subscribe(_ => AutoUpdateToggled()); } @@ -122,10 +126,10 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase private void AutoUpdateToggled() { _workshopService.SetAutoUpdate(Entry, AutoUpdate); - + if (!AutoUpdate) return; - + Task.Run(async () => { await _workshopUpdateService.AutoUpdateEntry(Entry); diff --git a/src/Artemis.UI/Services/Updating/WorkshopUpdateService.cs b/src/Artemis.UI/Services/Updating/WorkshopUpdateService.cs index c89ff34fe..6f1f267e5 100644 --- a/src/Artemis.UI/Services/Updating/WorkshopUpdateService.cs +++ b/src/Artemis.UI/Services/Updating/WorkshopUpdateService.cs @@ -22,7 +22,8 @@ public class WorkshopUpdateService : IWorkshopUpdateService private readonly Lazy _updateNotificationProvider; private readonly PluginSetting _showNotifications; - public WorkshopUpdateService(ILogger logger, IWorkshopClient client, IWorkshopService workshopService, ISettingsService settingsService, Lazy updateNotificationProvider) + public WorkshopUpdateService(ILogger logger, IWorkshopClient client, IWorkshopService workshopService, ISettingsService settingsService, + Lazy updateNotificationProvider) { _logger = logger; _client = client; @@ -56,20 +57,19 @@ public class WorkshopUpdateService : IWorkshopUpdateService public async Task AutoUpdateEntry(InstalledEntry installedEntry) { - // Query the latest version - IOperationResult latestReleaseResult = await _client.GetEntryLatestReleaseById.ExecuteAsync(installedEntry.Id); - IEntrySummary? entry = latestReleaseResult.Data?.Entry?.LatestRelease?.Entry; - if (entry == null) - return false; - if (latestReleaseResult.Data?.Entry?.LatestRelease is not IRelease latestRelease) - return false; - if (latestRelease.Id == installedEntry.ReleaseId) - return false; - - _logger.Information("Auto-updating entry {Entry} to version {Version}", entry, latestRelease.Version); - try { + // Query the latest version + IOperationResult latestReleaseResult = await _client.GetEntryLatestReleaseById.ExecuteAsync(installedEntry.Id); + IEntrySummary? entry = latestReleaseResult.Data?.Entry?.LatestRelease?.Entry; + if (entry == null) + return false; + if (latestReleaseResult.Data?.Entry?.LatestRelease is not IRelease latestRelease) + return false; + if (latestRelease.Id == installedEntry.ReleaseId) + return false; + + _logger.Information("Auto-updating entry {Entry} to version {Version}", entry, latestRelease.Version); EntryInstallResult updateResult = await _workshopService.InstallEntry(entry, latestRelease, new Progress(), CancellationToken.None); // This happens during installation too but not on our reference of the entry @@ -85,7 +85,7 @@ public class WorkshopUpdateService : IWorkshopUpdateService } catch (Exception e) { - _logger.Warning(e, "Auto-update failed for entry {Entry}", entry); + _logger.Warning(e, "Auto-update failed for entry {Entry}", installedEntry); } return false; diff --git a/src/Artemis.WebClient.Workshop/DryIoc/ContainerExtensions.cs b/src/Artemis.WebClient.Workshop/DryIoc/ContainerExtensions.cs index bebc8caf1..dc5a12d67 100644 --- a/src/Artemis.WebClient.Workshop/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.WebClient.Workshop/DryIoc/ContainerExtensions.cs @@ -53,6 +53,7 @@ public static class ContainerExtensions container.Register(Reuse.Singleton); container.Register(Reuse.Singleton); container.Register(Reuse.Singleton); + container.Register(Reuse.Singleton); container.Register(Reuse.Singleton); container.Register(); diff --git a/src/Artemis.WebClient.Workshop/Mutations/CastVote.graphql b/src/Artemis.WebClient.Workshop/Mutations/CastVote.graphql new file mode 100644 index 000000000..22dbd80d8 --- /dev/null +++ b/src/Artemis.WebClient.Workshop/Mutations/CastVote.graphql @@ -0,0 +1,16 @@ +mutation CastVote($input: CastVoteInput!) { + castVote(input: $input) { + ...voteCount + } +} + +mutation ClearVote($entryId: Long!) { + clearVote(entryId: $entryId) { + ...voteCount + } +} + +fragment voteCount on Entry { + upvoteCount + downvoteCount +} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Mutations/CreateEntry.graphql b/src/Artemis.WebClient.Workshop/Mutations/CreateEntry.graphql index 7191bc704..b4b621356 100644 --- a/src/Artemis.WebClient.Workshop/Mutations/CreateEntry.graphql +++ b/src/Artemis.WebClient.Workshop/Mutations/CreateEntry.graphql @@ -2,4 +2,4 @@ mutation AddEntry ($input: CreateEntryInput!) { addEntry(input: $input) { id } -} +} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql index cf0c3c779..ba23037d5 100644 --- a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql @@ -36,6 +36,8 @@ fragment entrySummary on Entry { summary entryType downloads + upvoteCount + downvoteCount createdAt latestReleaseId categories { @@ -51,6 +53,8 @@ fragment entryDetails on Entry { summary entryType downloads + upvoteCount + downvoteCount createdAt description categories { diff --git a/src/Artemis.WebClient.Workshop/Queries/GetVotes.graphql b/src/Artemis.WebClient.Workshop/Queries/GetVotes.graphql new file mode 100644 index 000000000..e671b6e7d --- /dev/null +++ b/src/Artemis.WebClient.Workshop/Queries/GetVotes.graphql @@ -0,0 +1,6 @@ +query GetVotes { + votes { + entryId + upvote + } +} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Services/VoteClient.cs b/src/Artemis.WebClient.Workshop/Services/VoteClient.cs new file mode 100644 index 000000000..7c4ba8b9f --- /dev/null +++ b/src/Artemis.WebClient.Workshop/Services/VoteClient.cs @@ -0,0 +1,105 @@ +using StrawberryShake; + +namespace Artemis.WebClient.Workshop.Services; + +public class VoteClient : IVoteClient +{ + private readonly Dictionary _cache = new(); + private readonly IWorkshopClient _client; + private readonly SemaphoreSlim _lock = new(1, 1); + private DateTime _cacheAge = DateTime.MinValue; + + public VoteClient(IWorkshopClient client, IAuthenticationService authenticationService) + { + _client = client; + authenticationService.IsLoggedIn.Subscribe(_ => _cacheAge = DateTime.MinValue); + } + + /// + public async Task GetVote(long entryId) + { + await _lock.WaitAsync(); + + try + { + if (_cacheAge < DateTime.UtcNow.AddMinutes(-15)) + { + _cache.Clear(); + IOperationResult result = await _client.GetVotes.ExecuteAsync(); + if (result.Data?.Votes != null) + foreach (IGetVotes_Votes vote in result.Data.Votes) + _cache.Add(vote.EntryId, vote.Upvote); + _cacheAge = DateTime.UtcNow; + } + + return _cache.TryGetValue(entryId, out bool upvote) ? upvote : null; + } + finally + { + _lock.Release(); + } + } + + /// + public async Task CastVote(long entryId, bool upvote) + { + await _lock.WaitAsync(); + + try + { + IOperationResult result = await _client.CastVote.ExecuteAsync(new CastVoteInput {EntryId = entryId, Upvote = upvote}); + if (result.IsSuccessResult() && result.Data?.CastVote != null) + _cache[entryId] = upvote; + + return result.Data?.CastVote; + } + finally + { + _lock.Release(); + } + } + + /// + public async Task ClearVote(long entryId) + { + await _lock.WaitAsync(); + + try + { + IOperationResult result = await _client.ClearVote.ExecuteAsync(entryId); + if (result.IsSuccessResult() && result.Data?.ClearVote != null) + _cache.Remove(entryId); + + return result.Data?.ClearVote; + } + finally + { + _lock.Release(); + } + } +} + +public interface IVoteClient +{ + /// + /// Gets the vote status for a specific entry. + /// + /// The ID of the entry + /// A Task containing the vote status. + Task GetVote(long entryId); + + /// + /// Casts a vote for a specific entry. + /// + /// The ID of the entry. + /// A boolean indicating whether the vote is an upvote. + /// A Task containing the cast vote. + Task CastVote(long entryId, bool upvote); + + /// + /// Clears a vote for a specific entry. + /// + /// The ID of the entry + /// A Task containing the vote status. + Task ClearVote(long entryId); +} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/WorkshopConstants.cs b/src/Artemis.WebClient.Workshop/WorkshopConstants.cs index 10807064c..907ddb843 100644 --- a/src/Artemis.WebClient.Workshop/WorkshopConstants.cs +++ b/src/Artemis.WebClient.Workshop/WorkshopConstants.cs @@ -2,10 +2,10 @@ namespace Artemis.WebClient.Workshop; public static class WorkshopConstants { - // public const string AUTHORITY_URL = "https://localhost:5001"; - // public const string WORKSHOP_URL = "https://localhost:7281"; - public const string AUTHORITY_URL = "https://identity.artemis-rgb.com"; - public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com"; + public const string AUTHORITY_URL = "https://localhost:5001"; + public const string WORKSHOP_URL = "https://localhost:7281"; + // public const string AUTHORITY_URL = "https://identity.artemis-rgb.com"; + // public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com"; public const string IDENTITY_CLIENT_NAME = "IdentityApiClient"; public const string WORKSHOP_CLIENT_NAME = "WorkshopApiClient"; } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/graphql.config.yml b/src/Artemis.WebClient.Workshop/graphql.config.yml index 9662a514f..4e6e409e5 100644 --- a/src/Artemis.WebClient.Workshop/graphql.config.yml +++ b/src/Artemis.WebClient.Workshop/graphql.config.yml @@ -2,7 +2,7 @@ schema: schema.graphql extensions: endpoints: Default GraphQL Endpoint: - url: https://workshop.artemis-rgb.com/graphql + url: https://localhost:7281/graphql/ headers: user-agent: JS GraphQL introspect: true diff --git a/src/Artemis.WebClient.Workshop/schema.graphql b/src/Artemis.WebClient.Workshop/schema.graphql index 12317cc59..e14597edf 100644 --- a/src/Artemis.WebClient.Workshop/schema.graphql +++ b/src/Artemis.WebClient.Workshop/schema.graphql @@ -56,11 +56,13 @@ type Entry { dependantReleases: [Release!]! description: String! downloads: Long! + downvoteCount: Int! entryType: EntryType! icon: Image iconId: UUID id: Long! images: [Image!]! + isDefault: Boolean! isOfficial: Boolean! latestRelease: Release latestReleaseId: Long @@ -68,8 +70,10 @@ type Entry { name: String! pluginInfo: PluginInfo releases: [Release!]! + score: Int! summary: String! tags: [Tag!]! + upvoteCount: Int! } type Image { @@ -99,6 +103,8 @@ type LayoutInfo { type Mutation { addEntry(input: CreateEntryInput!): Entry addLayoutInfo(input: CreateLayoutInfoInput!): LayoutInfo + castVote(input: CastVoteInput!): Entry + clearVote(entryId: Long!): Entry removeEntry(id: Long!): Entry removeLayoutInfo(id: Long!): LayoutInfo! removeRelease(id: Long!): Release! @@ -168,6 +174,7 @@ type Query { searchKeyboardLayout(deviceProvider: UUID!, logicalLayout: String, model: String!, physicalLayout: KeyboardLayoutType!, vendor: String!): LayoutInfo searchLayout(deviceProvider: UUID!, deviceType: RGBDeviceType!, model: String!, vendor: String!): LayoutInfo submittedEntries(order: [EntrySortInput!], where: EntryFilterInput): [Entry!]! + votes(order: [VoteSortInput!], where: VoteFilterInput): [Vote!]! } type Release { @@ -188,6 +195,15 @@ type Tag { name: String! } +type Vote { + entry: Entry! + entryId: Long! + id: Long! + upvote: Boolean! + userId: UUID! + votedAt: DateTime! +} + enum ApplyPolicy { AFTER_RESOLVER BEFORE_RESOLVER @@ -250,6 +266,11 @@ input BooleanOperationFilterInput { neq: Boolean } +input CastVoteInput { + entryId: Long! + upvote: Boolean! +} + input CategoryFilterInput { and: [CategoryFilterInput!] icon: StringOperationFilterInput @@ -268,6 +289,7 @@ input CreateEntryInput { categories: [Long!]! description: String! entryType: EntryType! + isDefault: Boolean! name: String! summary: String! tags: [String!]! @@ -307,11 +329,13 @@ input EntryFilterInput { dependantReleases: ListFilterInputTypeOfReleaseFilterInput description: StringOperationFilterInput downloads: LongOperationFilterInput + downvoteCount: IntOperationFilterInput entryType: EntryTypeOperationFilterInput icon: ImageFilterInput iconId: UuidOperationFilterInput id: LongOperationFilterInput images: ListFilterInputTypeOfImageFilterInput + isDefault: BooleanOperationFilterInput isOfficial: BooleanOperationFilterInput latestRelease: ReleaseFilterInput latestReleaseId: LongOperationFilterInput @@ -320,8 +344,10 @@ input EntryFilterInput { or: [EntryFilterInput!] pluginInfo: PluginInfoFilterInput releases: ListFilterInputTypeOfReleaseFilterInput + score: IntOperationFilterInput summary: StringOperationFilterInput tags: ListFilterInputTypeOfTagFilterInput + upvoteCount: IntOperationFilterInput } input EntrySortInput { @@ -330,16 +356,20 @@ input EntrySortInput { createdAt: SortEnumType description: SortEnumType downloads: SortEnumType + downvoteCount: SortEnumType entryType: SortEnumType icon: ImageSortInput iconId: SortEnumType id: SortEnumType + isDefault: SortEnumType isOfficial: SortEnumType latestRelease: ReleaseSortInput latestReleaseId: SortEnumType name: SortEnumType pluginInfo: PluginInfoSortInput + score: SortEnumType summary: SortEnumType + upvoteCount: SortEnumType } input EntryTypeOperationFilterInput { @@ -580,6 +610,7 @@ input UpdateEntryInput { categories: [Long!]! description: String! id: Long! + isDefault: Boolean! name: String! summary: String! tags: [String!]! @@ -604,3 +635,23 @@ input UuidOperationFilterInput { nlt: UUID nlte: UUID } + +input VoteFilterInput { + and: [VoteFilterInput!] + entry: EntryFilterInput + entryId: LongOperationFilterInput + id: LongOperationFilterInput + or: [VoteFilterInput!] + upvote: BooleanOperationFilterInput + userId: UuidOperationFilterInput + votedAt: DateTimeOperationFilterInput +} + +input VoteSortInput { + entry: EntrySortInput + entryId: SortEnumType + id: SortEnumType + upvote: SortEnumType + userId: SortEnumType + votedAt: SortEnumType +}