mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Workshop - Avoid crashes when auto-updating without internet
Workshop - Added vote based entry score system
This commit is contained in:
1 parent
7f5bb589af
commit
1e8c68bbeb
19 files changed
+408
-43
No files matched your search
@@ -53,6 +53,7 @@ public static class ContainerExtensions
|
||||
container.Register<IAuthenticationRepository, AuthenticationRepository>(Reuse.Singleton);
|
||||
container.Register<IAuthenticationService, AuthenticationService>(Reuse.Singleton);
|
||||
container.Register<IWorkshopService, WorkshopService>(Reuse.Singleton);
|
||||
container.Register<IVoteClient, VoteClient>(Reuse.Singleton);
|
||||
container.Register<ILayoutProvider, WorkshopLayoutProvider>(Reuse.Singleton);
|
||||
container.Register<IUserManagementService, UserManagementService>();
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -2,4 +2,4 @@ mutation AddEntry ($input: CreateEntryInput!) {
|
||||
addEntry(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
query GetVotes {
|
||||
votes {
|
||||
entryId
|
||||
upvote
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.WebClient.Workshop.Services;
|
||||
|
||||
public class VoteClient : IVoteClient
|
||||
{
|
||||
private readonly Dictionary<long, bool> _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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool?> GetVote(long entryId)
|
||||
{
|
||||
await _lock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
if (_cacheAge < DateTime.UtcNow.AddMinutes(-15))
|
||||
{
|
||||
_cache.Clear();
|
||||
IOperationResult<IGetVotesResult> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IVoteCount?> CastVote(long entryId, bool upvote)
|
||||
{
|
||||
await _lock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
IOperationResult<ICastVoteResult> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IVoteCount?> ClearVote(long entryId)
|
||||
{
|
||||
await _lock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
IOperationResult<IClearVoteResult> 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the vote status for a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry</param>
|
||||
/// <returns>A Task containing the vote status.</returns>
|
||||
Task<bool?> GetVote(long entryId);
|
||||
|
||||
/// <summary>
|
||||
/// Casts a vote for a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry.</param>
|
||||
/// <param name="upvote">A boolean indicating whether the vote is an upvote.</param>
|
||||
/// <returns>A Task containing the cast vote.</returns>
|
||||
Task<IVoteCount?> CastVote(long entryId, bool upvote);
|
||||
|
||||
/// <summary>
|
||||
/// Clears a vote for a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry</param>
|
||||
/// <returns>A Task containing the vote status.</returns>
|
||||
Task<IVoteCount?> ClearVote(long entryId);
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user