Skip to content
Closed
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: 4 additions & 1 deletion frontend/src/views/SettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,10 @@ onMounted(() => {
<Card class="settings-card" v-if="voteRewardsSettings.providers && voteRewardsSettings.providers.length > 0">
<template #title>{{ t('settings.voteRewardsProviderHeader') }}</template>
<template #content>
<div v-for="(provider, idx) in voteRewardsSettings.providers" :key="provider.key" class="provider-block vote-provider">
<!-- :key includes idx so duplicate provider keys (legacy state) don't collide
in Vue's v-for diffing. Backend dedups on load now (see EnsureDefaultProviders),
but this is defense in depth for any future regression. -->
<div v-for="(provider, idx) in voteRewardsSettings.providers" :key="`${provider.key}-${idx}`" class="provider-block vote-provider">
<h3 class="provider-title">{{ voteProviderDisplayName(provider.key) }}</h3>

<div class="vote-provider-grid">
Expand Down
24 changes: 24 additions & 0 deletions src/KitsuneCommand/Features/VoteRewardsFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,30 @@ private void LoadPersistedSettings()
private static void EnsureDefaultProviders(VoteRewardsSettings settings)
{
if (settings.Providers == null) settings.Providers = new List<VoteProviderSettings>();

// Deduplicate by Key. PR #50 prevented NEW duplicates forming (the
// Newtonsoft-append bug), but admins whose state was poisoned while
// that bug was active still carry the doubled list in their saved
// JSON. Strip duplicates here on every load — the entry with the
// most "configured" signal wins (enabled + has-api-key > enabled-only
// > has-api-key > anything else) so a real config is never displaced
// by an empty stub.
var beforeCount = settings.Providers.Count;
settings.Providers = settings.Providers
.Where(p => p != null && !string.IsNullOrWhiteSpace(p.Key))
.GroupBy(p => p.Key, StringComparer.OrdinalIgnoreCase)
.Select(group => group
.OrderByDescending(p => p.Enabled && !string.IsNullOrEmpty(p.ApiKey))
.ThenByDescending(p => p.Enabled)
.ThenByDescending(p => !string.IsNullOrEmpty(p.ApiKey))
.First())
.ToList();
var afterCount = settings.Providers.Count;
if (afterCount < beforeCount)
{
Log.Out($"[KitsuneCommand] VoteRewards: deduped {beforeCount - afterCount} duplicate provider entry/entries on load.");
}

if (!settings.Providers.Any(p => string.Equals(p.Key, "7daystodie-servers", StringComparison.OrdinalIgnoreCase)))
{
settings.Providers.Add(new VoteProviderSettings { Key = "7daystodie-servers", Enabled = false });
Expand Down
Loading