From 86680d2eb07ca2aa25f303106c10fac7732e6a60 Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sat, 20 Dec 2025 12:55:00 +0700 Subject: [PATCH 1/4] feat: `is_break` column on CheckinStreak --- db/schema.prisma | 1 + src/types/checkin-streak.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/db/schema.prisma b/db/schema.prisma index d51a486..e31af67 100644 --- a/db/schema.prisma +++ b/db/schema.prisma @@ -23,6 +23,7 @@ model CheckinStreak { first_date DateTime @default(now()) last_date DateTime? streak Int @default(0) + is_break DateTime? updated_at DateTime? user User @relation(fields: [user_id], references: [id]) diff --git a/src/types/checkin-streak.d.ts b/src/types/checkin-streak.d.ts index 301006b..e85dcb6 100644 --- a/src/types/checkin-streak.d.ts +++ b/src/types/checkin-streak.d.ts @@ -7,6 +7,7 @@ export interface CheckinStreak { first_date: Date last_date?: Date | null streak: number + is_break?: Date | null updated_at?: Date | null user?: User From 35760bf5403ac8ef7b9590e2525994f310cfa336 Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sat, 20 Dec 2025 13:00:11 +0700 Subject: [PATCH 2/4] refactor: column naming into `streak_broken_at` --- db/migrations/20250810031908_init/migration.sql | 1 + db/schema.prisma | 2 +- src/types/checkin-streak.d.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/db/migrations/20250810031908_init/migration.sql b/db/migrations/20250810031908_init/migration.sql index e1c14bd..f01eaeb 100644 --- a/db/migrations/20250810031908_init/migration.sql +++ b/db/migrations/20250810031908_init/migration.sql @@ -13,6 +13,7 @@ CREATE TABLE "public"."CheckinStreak" ( "first_date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "last_date" TIMESTAMP(3), "streak" INTEGER NOT NULL DEFAULT 0, + "streak_broken_at" TIMESTAMP(3) DEFAULT NULL, "updated_at" TIMESTAMP(3), CONSTRAINT "CheckinStreak_pkey" PRIMARY KEY ("id") diff --git a/db/schema.prisma b/db/schema.prisma index e31af67..b63aff1 100644 --- a/db/schema.prisma +++ b/db/schema.prisma @@ -23,7 +23,7 @@ model CheckinStreak { first_date DateTime @default(now()) last_date DateTime? streak Int @default(0) - is_break DateTime? + streak_broken_at DateTime? updated_at DateTime? user User @relation(fields: [user_id], references: [id]) diff --git a/src/types/checkin-streak.d.ts b/src/types/checkin-streak.d.ts index e85dcb6..bb06cd4 100644 --- a/src/types/checkin-streak.d.ts +++ b/src/types/checkin-streak.d.ts @@ -7,7 +7,7 @@ export interface CheckinStreak { first_date: Date last_date?: Date | null streak: number - is_break?: Date | null + streak_broken_at?: Date | null updated_at?: Date | null user?: User From 5b3b84c393317f6972727748b28e4bb2cdb38c59 Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sat, 20 Dec 2025 13:07:47 +0700 Subject: [PATCH 3/4] fix: do not include whole `db` folder for it is detect our OS --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a433552..09711c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: condition: service_healthy volumes: - ./src:/usr/src/app/src - - ./db:/usr/src/app/db + - ./db/schema.prisma:/usr/src/app/db/schema.prisma - ./prisma.config.ts:/usr/src/app/prisma.config.ts - ./package.json:/usr/src/app/package.json - ./tsconfig.json:/usr/src/app/tsconfig.json From 22dbc9d0692600f9b0e35053d0136a4e4185850e Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sat, 20 Dec 2025 13:33:54 +0700 Subject: [PATCH 4/4] fix: repeated reset grinder roles --- .../jobs/handlers/reset-grinder-roles.ts | 4 +- .../jobs/validators/reset-grinder-roles.ts | 44 ++++++++++++++----- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/bot/events/client-ready/jobs/handlers/reset-grinder-roles.ts b/src/bot/events/client-ready/jobs/handlers/reset-grinder-roles.ts index 5d7e831..e8a6940 100644 --- a/src/bot/events/client-ready/jobs/handlers/reset-grinder-roles.ts +++ b/src/bot/events/client-ready/jobs/handlers/reset-grinder-roles.ts @@ -27,9 +27,9 @@ export default { const guild = await client.guilds.fetch(process.env.GUILD_ID!) const channel = await getChannel(guild, GRIND_ASHES_CHANNEL) ResetGrinderRoles.assertChannel(channel) - const users = await ResetGrinderRoles.getUsersWithLatestCheckin(client.prisma) + const users = await ResetGrinderRoles.getUsersWithLatestStreak(client.prisma) - await ResetGrinderRoles.validateUsers(guild, channel, users) + await ResetGrinderRoles.validateUsers(client.prisma, guild, channel, users) log.success(ResetGrinderRoles.MSG.JobSuccess) }) diff --git a/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts b/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts index b462bf2..287406c 100644 --- a/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts +++ b/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts @@ -1,4 +1,6 @@ import type { PrismaClient } from '@generatedDB/client' +import type { CheckinStreak } from '@type/checkin-streak' +import type { User } from '@type/user' import type { Guild, GuildMember, TextChannel } from 'discord.js' import { getGrindRoles, GRINDER_ROLE } from '@config/discord' import { isDateToday, isDateYesterday } from '@utils/date' @@ -6,14 +8,6 @@ import { sendAsBot } from '@utils/discord' import { log } from '@utils/logger' import { ResetGrinderRolesMessage } from '../messages/reset-grinder-roles' -interface UserWithLatestCheckin { - discord_id: string - checkins: { - status: string - created_at: Date - }[] -} - export class ResetGrinderRoles extends ResetGrinderRolesMessage { static hasValidCheckin(checkin?: { created_at: Date, status: string }): boolean { if (!checkin) @@ -39,14 +33,19 @@ export class ResetGrinderRoles extends ResetGrinderRolesMessage { } } - static async validateUsers(guild: Guild, channel: TextChannel, users: UserWithLatestCheckin[]) { + static async validateUsers(prisma: PrismaClient, guild: Guild, channel: TextChannel, users: User[]) { for (const user of users) { const lastCheckin = user.checkins?.[0] if (this.hasValidCheckin(lastCheckin)) continue + const checkinStreak = user.checkin_streaks?.[0] + if (!checkinStreak) + continue + const member = await guild.members.fetch(user.discord_id) await this.removeGrinderRoles(member) + await this.breakCheckinStreakAt(prisma, checkinStreak) await sendAsBot( null, @@ -58,7 +57,7 @@ export class ResetGrinderRoles extends ResetGrinderRolesMessage { } } - static async getUsersWithLatestCheckin(prisma: PrismaClient): Promise { + static async getUsersWithLatestStreak(prisma: PrismaClient): Promise { const users = await prisma.user.findMany({ select: { discord_id: true, @@ -70,9 +69,32 @@ export class ResetGrinderRoles extends ResetGrinderRolesMessage { orderBy: { created_at: 'desc' }, take: 1, }, + checkin_streaks: { + orderBy: { first_date: 'desc' }, + take: 1, + where: { + streak_broken_at: null, + }, + include: { + checkins: { + orderBy: { created_at: 'desc' }, + take: 1, + }, + }, + }, }, - }) as UserWithLatestCheckin[] + }) as User[] return users } + + static async breakCheckinStreakAt(prisma: PrismaClient, checkinStreak: CheckinStreak) { + await prisma.checkinStreak.update({ + where: { id: checkinStreak.id }, + data: { + streak_broken_at: new Date(), + updated_at: new Date(), + }, + }) + } }