chore: strengthen backend eslint profile
This commit is contained in:
@@ -1,24 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
|
||||
import type { Prisma } from '../../../../../generated/prisma/client';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
import type { TbankSyncAttempt } from '../../../../integrations/tbank/tbank.api';
|
||||
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
import {
|
||||
confirmedCandleCoverage,
|
||||
type CandleRange,
|
||||
confirmedCandleCoverage,
|
||||
type SavedCandle,
|
||||
} from '../domain/candle-coverage';
|
||||
|
||||
const provider = 'tbank';
|
||||
const interval = 'day';
|
||||
type CandleIntent = {
|
||||
instrumentType?: 'share' | 'bond';
|
||||
uid: string;
|
||||
ranges: CandleRange[];
|
||||
period?: string;
|
||||
now?: string;
|
||||
force?: boolean;
|
||||
instrumentType?: 'bond' | 'share';
|
||||
now?: string;
|
||||
period?: string;
|
||||
ranges: CandleRange[];
|
||||
uid: string;
|
||||
};
|
||||
@Injectable()
|
||||
export class CandleRepository {
|
||||
@@ -57,7 +58,7 @@ export class CandleRepository {
|
||||
}
|
||||
|
||||
reserve(
|
||||
owner: { userId: string; revision: string },
|
||||
owner: { revision: string; userId: string },
|
||||
scopeKey: string,
|
||||
intent: CandleIntent,
|
||||
runId: string,
|
||||
@@ -88,29 +89,31 @@ export class CandleRepository {
|
||||
(!intent.force ||
|
||||
(previousIntent.force === true &&
|
||||
(!intent.now || intent.now <= previousNow)))
|
||||
)
|
||||
) {
|
||||
return { work: previous, launch: false };
|
||||
}
|
||||
const work = await tx.tbankSyncWork.update({
|
||||
where: { id: previous.id },
|
||||
data: {
|
||||
priority: nextPriority,
|
||||
intent: {
|
||||
...previousIntent,
|
||||
...(intent.force
|
||||
? {
|
||||
...(intent.force ?
|
||||
{
|
||||
force: true,
|
||||
...(intent.now && intent.now > previousNow
|
||||
? { now: intent.now }
|
||||
: {}),
|
||||
}
|
||||
: {}),
|
||||
...(intent.now && intent.now > previousNow ?
|
||||
{ now: intent.now } :
|
||||
{}),
|
||||
} :
|
||||
{}),
|
||||
},
|
||||
},
|
||||
});
|
||||
return { work, launch: false };
|
||||
}
|
||||
if (previous)
|
||||
if (previous) {
|
||||
await tx.tbankSyncStage.deleteMany({ where: { workId: previous.id } });
|
||||
}
|
||||
const data = {
|
||||
attemptId: randomUUID(),
|
||||
processRunId: runId,
|
||||
@@ -142,12 +145,15 @@ export class CandleRepository {
|
||||
fetchedAt: Date,
|
||||
) {
|
||||
// Reject malformed financial input instead of publishing a false empty range.
|
||||
for (const candle of candles)
|
||||
for (const candle of candles) {
|
||||
if (
|
||||
!Number.isFinite(Date.parse(candle.time)) ||
|
||||
// eslint-disable-next-line security/detect-unsafe-regex -- Anchored decimal grammar: the literal decimal point separates disjoint digit runs; backtracking is linear.
|
||||
!/^-?\d+(?:\.\d+)?$/.test(candle.close)
|
||||
)
|
||||
) {
|
||||
throw new Error('Invalid candle');
|
||||
}
|
||||
}
|
||||
const key = `${range.from.toISOString()}/${range.to.toISOString()}`;
|
||||
const rows = [
|
||||
...new Map(
|
||||
@@ -155,7 +161,7 @@ export class CandleRepository {
|
||||
.filter(
|
||||
(candle) =>
|
||||
new Date(candle.time) >= range.from &&
|
||||
new Date(candle.time) < range.to,
|
||||
new Date(candle.time) < range.to,
|
||||
)
|
||||
.map((candle) => [candle.time, candle]),
|
||||
).values(),
|
||||
@@ -222,8 +228,9 @@ export class CandleRepository {
|
||||
`${range.from.toISOString()}/${range.to.toISOString()}`,
|
||||
),
|
||||
)
|
||||
)
|
||||
) {
|
||||
throw new Error('Incomplete candle reconciliation');
|
||||
}
|
||||
const identity = { provider, instrumentUid: intent.uid, interval };
|
||||
for (const range of intent.ranges) {
|
||||
const row = staged.find(
|
||||
@@ -254,21 +261,22 @@ export class CandleRepository {
|
||||
// Preserve coverage outside the requested reconciliation boundaries.
|
||||
for (const previous of old) {
|
||||
for (const part of [
|
||||
...(previous.from < range.from
|
||||
? [{ from: previous.from, to: range.from }]
|
||||
: []),
|
||||
...(previous.to > range.to
|
||||
? [{ from: range.to, to: previous.to }]
|
||||
: []),
|
||||
])
|
||||
...(previous.from < range.from ?
|
||||
[{ from: previous.from, to: range.from }] :
|
||||
[]),
|
||||
...(previous.to > range.to ?
|
||||
[{ from: range.to, to: previous.to }] :
|
||||
[]),
|
||||
]) {
|
||||
await tx.tbankCandleCoverage.create({
|
||||
data: { ...previous, ...part },
|
||||
});
|
||||
}
|
||||
}
|
||||
await tx.tbankCandle.deleteMany({
|
||||
where: { ...identity, time: { gte: range.from, lt: range.to } },
|
||||
});
|
||||
for (const candle of payload.candles)
|
||||
for (const candle of payload.candles) {
|
||||
await tx.tbankCandle.create({
|
||||
data: {
|
||||
...identity,
|
||||
@@ -279,14 +287,16 @@ export class CandleRepository {
|
||||
fetchedAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
for (const covered of confirmedCandleCoverage(
|
||||
range,
|
||||
payload.candles,
|
||||
fetchedAt,
|
||||
))
|
||||
)) {
|
||||
await tx.tbankCandleCoverage.create({
|
||||
data: { ...identity, ...covered },
|
||||
});
|
||||
}
|
||||
}
|
||||
await tx.tbankSyncStage.deleteMany({
|
||||
where: { workId: attempt.id, attemptId: attempt.attemptId },
|
||||
|
||||
Reference in New Issue
Block a user