mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 20:35:02 +00:00
counters: move triggers to counters plugin; architectural tweaks
This commit is contained in:
parent
7f75d6d8d3
commit
ab8ea2e7e5
17 changed files with 357 additions and 200 deletions
|
@ -1,44 +1,18 @@
|
|||
import { BaseGuildRepository } from "./BaseGuildRepository";
|
||||
import { getRepository, In, IsNull, LessThan, Not, Repository } from "typeorm";
|
||||
import { FindConditions, getRepository, In, IsNull, LessThan, Not, Repository } from "typeorm";
|
||||
import { Counter } from "./entities/Counter";
|
||||
import { CounterValue } from "./entities/CounterValue";
|
||||
import { CounterTrigger, TRIGGER_COMPARISON_OPS, TriggerComparisonOp } from "./entities/CounterTrigger";
|
||||
import {
|
||||
CounterTrigger,
|
||||
isValidCounterComparisonOp,
|
||||
TRIGGER_COMPARISON_OPS,
|
||||
TriggerComparisonOp,
|
||||
} from "./entities/CounterTrigger";
|
||||
import { CounterTriggerState } from "./entities/CounterTriggerState";
|
||||
import moment from "moment-timezone";
|
||||
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils";
|
||||
import { connection } from "./db";
|
||||
|
||||
const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})([1-9]\\d*)$`);
|
||||
|
||||
/**
|
||||
* @return Parsed comparison op and value, or null if the comparison string was invalid
|
||||
*/
|
||||
export function parseCondition(str: string): [TriggerComparisonOp, number] | null {
|
||||
const matches = str.match(comparisonStringRegex);
|
||||
return matches ? [matches[1] as TriggerComparisonOp, parseInt(matches[2], 10)] : null;
|
||||
}
|
||||
|
||||
export function buildConditionString(comparisonOp: TriggerComparisonOp, comparisonValue: number): string {
|
||||
return `${comparisonOp}${comparisonValue}`;
|
||||
}
|
||||
|
||||
function isValidComparisonOp(op: string): boolean {
|
||||
return TRIGGER_COMPARISON_OPS.includes(op as any);
|
||||
}
|
||||
|
||||
const REVERSE_OPS: Record<TriggerComparisonOp, TriggerComparisonOp> = {
|
||||
"=": "!=",
|
||||
"!=": "=",
|
||||
">": "<=",
|
||||
"<": ">=",
|
||||
">=": "<",
|
||||
"<=": ">",
|
||||
};
|
||||
|
||||
function getReverseComparisonOp(op: TriggerComparisonOp): TriggerComparisonOp {
|
||||
return REVERSE_OPS[op];
|
||||
}
|
||||
|
||||
const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
|
||||
const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;
|
||||
|
||||
|
@ -92,6 +66,8 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
// If the existing counter's properties match the ones we're looking for, return it.
|
||||
// Otherwise, delete the existing counter and re-create it with the proper properties.
|
||||
if (existing.per_channel === perChannel && existing.per_user === perUser) {
|
||||
await this.counters.update({ id: existing.id }, { delete_at: null });
|
||||
|
||||
return existing;
|
||||
}
|
||||
|
||||
|
@ -114,24 +90,23 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
}
|
||||
|
||||
async markUnusedCountersToBeDeleted(idsToKeep: number[]): Promise<void> {
|
||||
if (idsToKeep.length === 0) {
|
||||
return;
|
||||
const criteria: FindConditions<Counter> = {
|
||||
guild_id: this.guildId,
|
||||
delete_at: IsNull(),
|
||||
};
|
||||
|
||||
if (idsToKeep.length) {
|
||||
criteria.id = Not(In(idsToKeep));
|
||||
}
|
||||
|
||||
const deleteAt = moment
|
||||
.utc()
|
||||
.add(DELETE_UNUSED_COUNTERS_AFTER, "ms")
|
||||
.format(DBDateFormat);
|
||||
await this.counters.update(
|
||||
{
|
||||
guild_id: this.guildId,
|
||||
id: Not(In(idsToKeep)),
|
||||
delete_at: IsNull(),
|
||||
},
|
||||
{
|
||||
delete_at: deleteAt,
|
||||
},
|
||||
);
|
||||
|
||||
await this.counters.update(criteria, {
|
||||
delete_at: deleteAt,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteCountersMarkedToBeDeleted(): Promise<void> {
|
||||
|
@ -230,17 +205,37 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
);
|
||||
}
|
||||
|
||||
async markAllTriggersTobeDeleted() {
|
||||
const deleteAt = moment
|
||||
.utc()
|
||||
.add(DELETE_UNUSED_COUNTER_TRIGGERS_AFTER, "ms")
|
||||
.format(DBDateFormat);
|
||||
await this.counterTriggers.update(
|
||||
{},
|
||||
{
|
||||
delete_at: deleteAt,
|
||||
},
|
||||
);
|
||||
async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) {
|
||||
let triggersToMarkQuery = this.counterTriggers
|
||||
.createQueryBuilder("counterTriggers")
|
||||
.innerJoin(Counter, "counters", "counters.id = counterTriggers.counter_id")
|
||||
.where("counters.guild_id = :guildId", { guildId: this.guildId });
|
||||
|
||||
// If there are no active triggers, we just mark all triggers from the guild to be deleted.
|
||||
// Otherwise, we mark all but the active triggers in the guild.
|
||||
if (triggerIdsToKeep.length) {
|
||||
triggersToMarkQuery = triggersToMarkQuery.andWhere("counterTriggers.id NOT IN (:...triggerIds)", {
|
||||
triggerIds: triggerIdsToKeep,
|
||||
});
|
||||
}
|
||||
|
||||
const triggersToMark = await triggersToMarkQuery.getMany();
|
||||
|
||||
if (triggersToMark.length) {
|
||||
const deleteAt = moment
|
||||
.utc()
|
||||
.add(DELETE_UNUSED_COUNTER_TRIGGERS_AFTER, "ms")
|
||||
.format(DBDateFormat);
|
||||
|
||||
await this.counterTriggers.update(
|
||||
{
|
||||
id: In(triggersToMark.map(t => t.id)),
|
||||
},
|
||||
{
|
||||
delete_at: deleteAt,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTriggersMarkedToBeDeleted(): Promise<void> {
|
||||
|
@ -253,34 +248,53 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
|
||||
async initCounterTrigger(
|
||||
counterId: number,
|
||||
triggerName: string,
|
||||
comparisonOp: TriggerComparisonOp,
|
||||
comparisonValue: number,
|
||||
reverseComparisonOp: TriggerComparisonOp,
|
||||
reverseComparisonValue: number,
|
||||
): Promise<CounterTrigger> {
|
||||
if (!isValidComparisonOp(comparisonOp)) {
|
||||
if (!isValidCounterComparisonOp(comparisonOp)) {
|
||||
throw new Error(`Invalid comparison op: ${comparisonOp}`);
|
||||
}
|
||||
|
||||
if (!isValidCounterComparisonOp(reverseComparisonOp)) {
|
||||
throw new Error(`Invalid comparison op: ${reverseComparisonOp}`);
|
||||
}
|
||||
|
||||
if (typeof comparisonValue !== "number") {
|
||||
throw new Error(`Invalid comparison value: ${comparisonValue}`);
|
||||
}
|
||||
|
||||
if (typeof reverseComparisonValue !== "number") {
|
||||
throw new Error(`Invalid comparison value: ${reverseComparisonValue}`);
|
||||
}
|
||||
|
||||
return connection.transaction(async entityManager => {
|
||||
const existing = await entityManager.findOne(CounterTrigger, {
|
||||
counter_id: counterId,
|
||||
comparison_op: comparisonOp,
|
||||
comparison_value: comparisonValue,
|
||||
name: triggerName,
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
// Since all existing triggers are marked as to-be-deleted before they are re-initialized, this needs to be reset
|
||||
await entityManager.update(CounterTrigger, existing.id, { delete_at: null });
|
||||
await entityManager.update(CounterTrigger, existing.id, {
|
||||
comparison_op: comparisonOp,
|
||||
comparison_value: comparisonValue,
|
||||
reverse_comparison_op: reverseComparisonOp,
|
||||
reverse_comparison_value: reverseComparisonValue,
|
||||
delete_at: null,
|
||||
});
|
||||
return existing;
|
||||
}
|
||||
|
||||
const insertResult = await entityManager.insert(CounterTrigger, {
|
||||
counter_id: counterId,
|
||||
name: triggerName,
|
||||
comparison_op: comparisonOp,
|
||||
comparison_value: comparisonValue,
|
||||
reverse_comparison_op: reverseComparisonOp,
|
||||
reverse_comparison_value: reverseComparisonValue,
|
||||
});
|
||||
|
||||
return (await entityManager.findOne(CounterTrigger, insertResult.identifiers[0].id))!;
|
||||
|
@ -375,8 +389,8 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
CounterTriggerState,
|
||||
matchingValues.map(row => ({
|
||||
trigger_id: counterTrigger.id,
|
||||
channelId: row.channel_id,
|
||||
userId: row.user_id,
|
||||
channel_id: row.channel_id,
|
||||
user_id: row.user_id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
@ -408,7 +422,6 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
userId = userId || "0";
|
||||
|
||||
return connection.transaction(async entityManager => {
|
||||
const reverseOp = getReverseComparisonOp(counterTrigger.comparison_op);
|
||||
const matchingValue = await entityManager
|
||||
.createQueryBuilder(CounterValue, "cv")
|
||||
.innerJoin(
|
||||
|
@ -417,7 +430,9 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
"triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id",
|
||||
{ triggerId: counterTrigger.id },
|
||||
)
|
||||
.where(`cv.value ${reverseOp} :value`, { value: counterTrigger.comparison_value })
|
||||
.where(`cv.value ${counterTrigger.reverse_comparison_op} :value`, {
|
||||
value: counterTrigger.reverse_comparison_value,
|
||||
})
|
||||
.andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id })
|
||||
.andWhere(`cv.channel_id = :channelId AND cv.user_id = :userId`, { channelId, userId })
|
||||
.getOne();
|
||||
|
@ -446,7 +461,6 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
counterTrigger: CounterTrigger,
|
||||
): Promise<Array<{ channelId: string; userId: string }>> {
|
||||
return connection.transaction(async entityManager => {
|
||||
const reverseOp = getReverseComparisonOp(counterTrigger.comparison_op);
|
||||
const matchingValues: Array<{
|
||||
id: string;
|
||||
triggerStateId: string;
|
||||
|
@ -460,7 +474,9 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
"triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id",
|
||||
{ triggerId: counterTrigger.id },
|
||||
)
|
||||
.where(`cv.value ${reverseOp} :value`, { value: counterTrigger.comparison_value })
|
||||
.where(`cv.value ${counterTrigger.reverse_comparison_op} :value`, {
|
||||
value: counterTrigger.reverse_comparison_value,
|
||||
})
|
||||
.andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id })
|
||||
.select([
|
||||
"cv.id AS id",
|
||||
|
|
|
@ -4,6 +4,37 @@ export const TRIGGER_COMPARISON_OPS = ["=", "!=", ">", "<", ">=", "<="] as const
|
|||
|
||||
export type TriggerComparisonOp = typeof TRIGGER_COMPARISON_OPS[number];
|
||||
|
||||
const REVERSE_OPS: Record<TriggerComparisonOp, TriggerComparisonOp> = {
|
||||
"=": "!=",
|
||||
"!=": "=",
|
||||
">": "<=",
|
||||
"<": ">=",
|
||||
">=": "<",
|
||||
"<=": ">",
|
||||
};
|
||||
|
||||
export function getReverseCounterComparisonOp(op: TriggerComparisonOp): TriggerComparisonOp {
|
||||
return REVERSE_OPS[op];
|
||||
}
|
||||
|
||||
const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})([1-9]\\d*)$`);
|
||||
|
||||
/**
|
||||
* @return Parsed comparison op and value, or null if the comparison string was invalid
|
||||
*/
|
||||
export function parseCounterConditionString(str: string): [TriggerComparisonOp, number] | null {
|
||||
const matches = str.match(comparisonStringRegex);
|
||||
return matches ? [matches[1] as TriggerComparisonOp, parseInt(matches[2], 10)] : null;
|
||||
}
|
||||
|
||||
export function buildCounterConditionString(comparisonOp: TriggerComparisonOp, comparisonValue: number): string {
|
||||
return `${comparisonOp}${comparisonValue}`;
|
||||
}
|
||||
|
||||
export function isValidCounterComparisonOp(op: string): boolean {
|
||||
return TRIGGER_COMPARISON_OPS.includes(op as any);
|
||||
}
|
||||
|
||||
@Entity("counter_triggers")
|
||||
export class CounterTrigger {
|
||||
@PrimaryGeneratedColumn()
|
||||
|
@ -12,12 +43,21 @@ export class CounterTrigger {
|
|||
@Column()
|
||||
counter_id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ type: "varchar" })
|
||||
comparison_op: TriggerComparisonOp;
|
||||
|
||||
@Column()
|
||||
comparison_value: number;
|
||||
|
||||
@Column({ type: "varchar" })
|
||||
reverse_comparison_op: TriggerComparisonOp;
|
||||
|
||||
@Column()
|
||||
reverse_comparison_value: number;
|
||||
|
||||
@Column({ type: "datetime", nullable: true })
|
||||
delete_at: string | null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue