From ea77ea361b01df54c077ba8136e985b7f3eae627 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:57:28 +0200 Subject: [PATCH] Automatically delete old trust relationships --- app/db/tables.ts | 1 + app/entry.server.tsx | 9 ++++++ app/features/sendouq/QRepository.server.ts | 28 ++++++++++++++++++- app/features/sendouq/routes/q.preparing.tsx | 4 +++ .../actions/to.$id.register.server.ts | 4 +++ .../tournament/queries/giveTrust.server.ts | 6 ++-- migrations/073-trust-timestamp.js | 11 ++++++++ 7 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 migrations/073-trust-timestamp.js diff --git a/app/db/tables.ts b/app/db/tables.ts index ac45ce585..630a5254c 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -679,6 +679,7 @@ export interface TournamentOrganizationSeries { export interface TrustRelationship { trustGiverUserId: number; trustReceiverUserId: number; + lastUsedAt: number; } export interface UnvalidatedUserSubmittedImage { diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 72a108cf1..a1b13aa24 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -9,10 +9,12 @@ import { isbot } from "isbot"; import cron from "node-cron"; import { renderToPipeableStream } from "react-dom/server"; import { I18nextProvider, initReactI18next } from "react-i18next"; +import * as QRepository from "~/features/sendouq/QRepository.server"; import { config } from "~/modules/i18n/config"; // your i18n configuration file import i18next from "~/modules/i18n/i18next.server"; import { resources } from "./modules/i18n/resources.server"; import { updatePatreonData } from "./modules/patreon"; +import { logger } from "./utils/logger"; const ABORT_DELAY = 5000; @@ -88,6 +90,13 @@ if (!global.appStartSignal && process.env.NODE_ENV === "production") { cron.schedule("0 */2 * * *", () => updatePatreonData().catch((err) => console.error(err)), ); + + // every hour + cron.schedule("0 */1 * * *", async () => { + const { numDeletedRows } = await QRepository.deleteOldTrust(); + + logger.info(`Deleted ${numDeletedRows} old trusts`); + }); } process.on("unhandledRejection", (reason: string, p: Promise) => { diff --git a/app/features/sendouq/QRepository.server.ts b/app/features/sendouq/QRepository.server.ts index 836d172e4..a92e4ab4d 100644 --- a/app/features/sendouq/QRepository.server.ts +++ b/app/features/sendouq/QRepository.server.ts @@ -1,3 +1,4 @@ +import { sub } from "date-fns"; import { sql } from "kysely"; import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite"; import { nanoid } from "nanoid"; @@ -8,7 +9,7 @@ import type { TablesInsertable, UserMapModePreferences, } from "~/db/tables"; -import { dateToDatabaseTimestamp } from "~/utils/dates"; +import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates"; import { COMMON_USER_FIELDS } from "~/utils/kysely.server"; import { userIsBanned } from "../ban/core/banned.server"; import type { LookingGroupWithInviteCode } from "./q-types"; @@ -333,3 +334,28 @@ export async function usersThatTrusted(userId: number) { trusters: deduplicatedRows, }; } + +/** Update the timestamp of the trust relationship, delaying its automatic deletion */ +export async function refreshTrust({ + trustGiverUserId, + trustReceiverUserId, +}: { + trustGiverUserId: number; + trustReceiverUserId: number; +}) { + return db + .updateTable("TrustRelationship") + .set({ lastUsedAt: databaseTimestampNow() }) + .where("trustGiverUserId", "=", trustGiverUserId) + .where("trustReceiverUserId", "=", trustReceiverUserId) + .execute(); +} + +export async function deleteOldTrust() { + const twoMonthsAgo = sub(new Date(), { months: 2 }); + + return db + .deleteFrom("TrustRelationship") + .where("lastUsedAt", "<", dateToDatabaseTimestamp(twoMonthsAgo)) + .executeTakeFirst(); +} diff --git a/app/features/sendouq/routes/q.preparing.tsx b/app/features/sendouq/routes/q.preparing.tsx index 0ba54f0ee..2a33e9369 100644 --- a/app/features/sendouq/routes/q.preparing.tsx +++ b/app/features/sendouq/routes/q.preparing.tsx @@ -108,6 +108,10 @@ export const action = async ({ request }: ActionFunctionArgs) => { userId: data.id, role: "MANAGER", }); + await QRepository.refreshTrust({ + trustGiverUserId: data.id, + trustReceiverUserId: user.id, + }); return null; } diff --git a/app/features/tournament/actions/to.$id.register.server.ts b/app/features/tournament/actions/to.$id.register.server.ts index fa11c1f49..fc510d08a 100644 --- a/app/features/tournament/actions/to.$id.register.server.ts +++ b/app/features/tournament/actions/to.$id.register.server.ts @@ -239,6 +239,10 @@ export const action: ActionFunction = async ({ request, params }) => { userId: data.userId, }), }); + await QRepository.refreshTrust({ + trustGiverUserId: data.userId, + trustReceiverUserId: user.id, + }); ShowcaseTournaments.addToParticipationInfoMap({ tournamentId, diff --git a/app/features/tournament/queries/giveTrust.server.ts b/app/features/tournament/queries/giveTrust.server.ts index f03f658de..1da8a2b16 100644 --- a/app/features/tournament/queries/giveTrust.server.ts +++ b/app/features/tournament/queries/giveTrust.server.ts @@ -3,10 +3,12 @@ import { sql } from "~/db/sql"; const stm = sql.prepare(/*sql */ ` insert into "TrustRelationship" ( "trustGiverUserId", - "trustReceiverUserId" + "trustReceiverUserId", + "lastUsedAt" ) values ( @trustGiverUserId, - @trustReceiverUserId + @trustReceiverUserId, + strftime('%s', 'now') ) on conflict do nothing `); diff --git a/migrations/073-trust-timestamp.js b/migrations/073-trust-timestamp.js new file mode 100644 index 000000000..f649ff636 --- /dev/null +++ b/migrations/073-trust-timestamp.js @@ -0,0 +1,11 @@ +export function up(db) { + db.transaction(() => { + db.prepare( + /* sql */ `alter table "TrustRelationship" add "lastUsedAt" integer default 0`, + ).run(); + + db.prepare( + /* sql */ `update "TrustRelationship" set "lastUsedAt" = strftime('%s', 'now')`, + ).run(); + })(); +}