Skip to content
3 changes: 2 additions & 1 deletion apps/backend/Jobs/Magic/MagicAggregateMiscReportsJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public override async Task Run(string[] data)
var dataCounts = new Dictionary<string, int>();
var reportQuery = Context.MiscReport
.AsNoTracking()
.Where(mr => mr.Region == (short)region &&
.Where(mr => mr.ReportType == reportType &&
mr.Region == (short)region &&
mr.ExpiresAt > DateTime.UtcNow)
.AsAsyncEnumerable();
await foreach (var report in reportQuery)
Expand Down
129 changes: 95 additions & 34 deletions apps/backend/Jobs/User/UserUploadJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ public class UserUploadJob : JobBase
private static readonly Regex PlayerGuidRegex = new(@"^Player-\d+-([0-9A-Fa-f]+)$", RegexOptions.Compiled);
private static readonly Regex ValidGuildRegex = new(@"^\d+/", RegexOptions.Compiled);

private static readonly Dictionary<int, MiscReportType> ExpansionToMiscReportType = new()
{
{ 6, MiscReportType.LegionEmissaries },
{ 7, MiscReportType.BattleForAzerothEmissaries },
{ 8, MiscReportType.ShadowlandsCallings },
};

private static readonly AsyncRetryPolicy<bool> LockRetryPolicy = Policy
.HandleResult<bool>(r => r == false)
.WaitAndRetryAsync(
Expand Down Expand Up @@ -1080,69 +1087,123 @@ await localContext.PlayerWarbankItem

private async Task HandleMiscReports(WowRegion accountRegion, Upload parsedData)
{
if (parsedData.Delves == null)
{
return;
}

await using var localContext = await ContextFactory.CreateDbContextAsync();

var reportMap = await localContext.MiscReport
.Where(mr => mr.UserId == _userId && mr.Region == (short)accountRegion)
.ToDictionaryAsync(mr => mr.ReportType);

var now = DateTime.UtcNow;
long currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
DateTime? expiresAt = null;
List<(int, string)> delves = [];
foreach (string delveString in parsedData.Delves)
var reports = new List<(MiscReportType ReportType, DateTime ExpiresAt, string Data)>();

// Delves
if (parsedData.Delves != null)
{
string[] parts = delveString.Split(":");
if (parts.Length != 3)
DateTime? expiresAt = null;
List<(int, string)> delves = [];
foreach (string delveString in parsedData.Delves)
{
continue;
string[] parts = delveString.Split(":");
if (parts.Length != 3)
{
continue;
}

// unixTimestamp:poiId:story
if (!int.TryParse(parts[0], out int validUntil) ||
!int.TryParse(parts[1], out int poiId) ||
string.IsNullOrWhiteSpace(parts[2]) ||
validUntil < currentTimestamp ||
poiId == 0)
{
// log
continue;
}

expiresAt ??= validUntil.AsUtcDateTime();

delves.Add((poiId, parts[2]));
}

// unixTimestamp:poiId:story
int.TryParse(parts[0], out int validUntil);
int.TryParse(parts[1], out int poiId);
if (validUntil < currentTimestamp || poiId == 0 || string.IsNullOrWhiteSpace(parts[2]))
if (expiresAt != null && delves.Count > 0)
{
// log
continue;
}
List<object[]> dataParts = [];
foreach ((int poiId, string story) in delves.OrderBy(tup => tup.Item1))
{
dataParts.Add([poiId, story]);
}

expiresAt ??= validUntil.AsUtcDateTime();
string data = JsonSerializer.Serialize(dataParts);

delves.Add((poiId, parts[2]));
reports.Add((MiscReportType.Delve, expiresAt.Value, data));
}
}

if (expiresAt != null && delves.Count > 0)
// Emissary Quests
if (parsedData.EmissaryQuests != null)
{
List<object[]> dataParts = [];
foreach ((int poiId, string story) in delves.OrderBy(tup => tup.Item1))
foreach ((int expansion, var emissaryQuestStrings) in parsedData.EmissaryQuests)
{
dataParts.Add([poiId, story]);
}
if (!ExpansionToMiscReportType.TryGetValue(expansion, out var reportType))
{
// log
continue;
}

List<(int QuestId, int ExpiresAt)> emissaryQuests = new();
foreach (string emissaryQuestString in emissaryQuestStrings)
{
string[] parts = emissaryQuestString.Split(":");
if (parts.Length != 2)
{
continue;
}

string data = JsonSerializer.Serialize(dataParts);
// questId:expires
if (!int.TryParse(parts[0], out int questId) ||
!int.TryParse(parts[1], out int validUntil) ||
questId == 0 ||
validUntil < currentTimestamp)
{
// log
continue;
}

if (reportMap.TryGetValue(MiscReportType.Delve, out var dbReport))
{
dbReport.Data = data;
emissaryQuests.Add((questId, validUntil));
}

if (emissaryQuests.Count > 0)
{
List<object[]> dataParts = [];
foreach ((int questId, int expiresAt) in emissaryQuests.OrderBy(tup => tup.ExpiresAt))
{
dataParts.Add([questId, expiresAt]);
}

string data = JsonSerializer.Serialize(dataParts);

reports.Add((reportType, emissaryQuests.Last().ExpiresAt.AsUtcDateTime(), data));
}
}
else
}

foreach (var report in reports)
{
if (!reportMap.TryGetValue(report.ReportType, out var dbReport))
{
dbReport = new MiscReport
{
UserId = _userId,
ExpiresAt = expiresAt.Value,
ReportedAt = DateTime.UtcNow,
ReportType = MiscReportType.Delve,
ReportedAt = now,
ReportType = report.ReportType,
Region = (short)accountRegion,
Data = data,
};
localContext.MiscReport.Add(dbReport);
}

dbReport.Data = report.Data;
dbReport.ExpiresAt = report.ExpiresAt;
}

await localContext.SaveChangesAsync();
Expand Down
1 change: 1 addition & 0 deletions apps/backend/Models/Uploads/Upload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class Upload

public Dictionary<int, string> Decor { get; set; }
public List<string> Delves { get; set; }
public Dictionary<int, List<string>> EmissaryQuests { get; set; }
public List<int> Illusions { get; set; }
public Dictionary<int, string> Paragons { get; set; }
public List<int> Quests { get; set; }
Expand Down
6 changes: 4 additions & 2 deletions apps/backend/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ public class Program

public static void Main(string[] args)
{
ServicePointManager.DefaultConnectionLimit = 10;

Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.Enrich.FromLogContext()
Expand Down Expand Up @@ -135,6 +133,10 @@ private static void ConfigureServices(HostBuilderContext hostContext, IServiceCo
{
client.Timeout = TimeSpan.FromSeconds(120);
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
MaxConnectionsPerServer = 10,
})
.AddPolicyHandler(rateLimitPolicy)
.SetHandlerLifetime(Timeout.InfiniteTimeSpan);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,23 @@
{@const [taskName, choreName] = fullTaskName.split('|', 2)}
{@const task = taskMap[taskName] || settingsState.customTaskMap[fullTaskName]}
{@const chore = task.chores.find((task) => task?.key === choreName)}
{@const customExpiry = chore?.decorationFunc?.(
chore?.customExpiryFunc(userState.general.characters[0], timeState.slowTime)
{@const customExpiry = chore?.customExpiryFunc?.(
chore,
userState.general.characters[0],
timeState.slowTime
)}
{@const decoratorClass = chore?.decorationFunc?.(customExpiry)}

<td
class="sortable sorted-{getSortState(fullTaskName)} {customExpiry || ''}"
class="sortable sorted-{getSortState(fullTaskName)} {decoratorClass || ''}"
data-task={taskName}
style:--scale="1"
onclick={() => setSortState(fullTaskName)}
use:componentTooltip={{
component: Tooltip,
props: {
chore,
expiresAt: customExpiry,
fullTaskName,
task,
},
Expand Down
14 changes: 13 additions & 1 deletion apps/frontend/components/tooltips/task/TooltipTaskHead.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<script lang="ts">
import { DateTime } from 'luxon';

import { QuestStatus } from '@/enums/quest-status';
import { settingsState } from '@/shared/state/settings.svelte';
import { timeState } from '@/shared/state/time.svelte';
import { userState } from '@/user-home/state/user';
import { toNiceDuration } from '@/utils/formatting';
import type { Chore, Task } from '@/types/tasks';
import type { CharacterTask } from '@/user-home/state/user/types/tasks.svelte';

Expand All @@ -11,8 +15,9 @@
chore: Chore;
fullTaskName: string;
task: Task;
expiresAt?: DateTime;
};
let { chore, fullTaskName, task }: Props = $props();
let { chore, fullTaskName, task, expiresAt }: Props = $props();

// TODO: get title from character quests?

Expand Down Expand Up @@ -115,4 +120,11 @@
{/each}
</tbody>
</table>

{#if expiresAt}
{@const expiresIn = Math.floor(expiresAt.diff(timeState.time).toMillis())}
<div class="bottom">
Expires in {toNiceDuration(expiresIn, { useNbsp: false })}
</div>
{/if}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
charTaskChore.status
]}"
>
{#if chore?.icon}
{#if chore?.icon && !chore.noTooltipIcon}
{#if typeof chore.icon === 'string'}
<ParsedText text={chore.icon} />
{:else}
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/data/quests.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Holiday } from '@/enums/holiday';
import { ItemQuality } from '@/enums/item-quality';
import { QuestStatus } from '@/enums/quest-status';
import { GlobalDailyQuest } from '@/types/data';
Expand Down
Loading
Loading