diff --git a/prisma/migrations/20241004154110_add_new_achievement/migration.sql b/prisma/migrations/20241004154110_add_new_achievement/migration.sql new file mode 100644 index 0000000..302b1e9 --- /dev/null +++ b/prisma/migrations/20241004154110_add_new_achievement/migration.sql @@ -0,0 +1,11 @@ +-- AlterEnum +-- This migration adds more than one value to an enum. +-- With PostgreSQL versions 11 and earlier, this is not possible +-- in a single migration. This can be worked around by creating +-- multiple migrations, each migration adding only one value to +-- the enum. + + +ALTER TYPE "AchievementType" ADD VALUE 'FiveCopyPastaADay'; +ALTER TYPE "AchievementType" ADD VALUE 'TagCollector'; +ALTER TYPE "AchievementType" ADD VALUE 'CollectionCurator'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 980ac34..4f3d6ef 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -201,6 +201,9 @@ enum AchievementType { OneMonthStreak ThreeMonthStreak SixMonthStreak + FiveCopyPastaADay + TagCollector + CollectionCurator } enum EngagementAction { diff --git a/src/lib/constant.tsx b/src/lib/constant.tsx index f907600..eb3b486 100644 --- a/src/lib/constant.tsx +++ b/src/lib/constant.tsx @@ -161,3 +161,11 @@ export const ENGAGEMENT_SCORE = { }; export const TIMEZONE = "Asia/Jakarta"; + +export const ACHIEVEMENT_MINIMUM = { + TAG_COLLECTOR_MINIMUM: 15, + COLLECTION_CURATOR: { + COPY_PASTA_PER_COLLECTION: 5, + COLLECTION_MINIMUM: 3, + }, +}; diff --git a/src/server/api/routers/collection.ts b/src/server/api/routers/collection.ts index c92a660..7a7db50 100644 --- a/src/server/api/routers/collection.ts +++ b/src/server/api/routers/collection.ts @@ -12,6 +12,7 @@ import { editCollectionForm, } from "~/server/form/collection"; import { updateUserEngagementScore } from "~/server/util/db"; +import { checkAndGrantCollectionCuratorAchievement } from "~/server/util/achievement"; export const collectionRouter = createTRPCRouter({ list: publicProcedure @@ -97,6 +98,10 @@ export const collectionRouter = createTRPCRouter({ }); const payload = handleEngagementAction("CreateCollection", collection.id); await updateUserEngagementScore(ctx.db, ctx.session.user.id, payload); + await checkAndGrantCollectionCuratorAchievement( + ctx.db, + ctx.session.user.id, + ); return collection.id; }), @@ -235,6 +240,11 @@ export const collectionRouter = createTRPCRouter({ }, }, }); + + await checkAndGrantCollectionCuratorAchievement( + ctx.db, + ctx.session.user.id, + ); }), delete: protectedProcedure diff --git a/src/server/api/routers/copyPasta.ts b/src/server/api/routers/copyPasta.ts index 3fc9827..faf939d 100644 --- a/src/server/api/routers/copyPasta.ts +++ b/src/server/api/routers/copyPasta.ts @@ -19,6 +19,10 @@ import { type CopyPastaSearchResult, } from "~/lib/interface"; import { updateUserEngagementScore } from "~/server/util/db"; +import { + checkAndGrantFiveCopyPastaADayAchievement, + checkAndGrantTagCollectionAchievement, +} from "~/server/util/achievement"; function tokenize(content: string) { return content.toLowerCase().split(/\s+/); @@ -99,7 +103,12 @@ export const copyPastaRouter = createTRPCRouter({ }); const payload = handleEngagementAction("CreateCopyPasta", copyPasta.id); - await updateUserEngagementScore(ctx.db, ctx.session.user.id, payload); + await Promise.all([ + updateUserEngagementScore(ctx.db, ctx.session.user.id, payload), + checkAndGrantTagCollectionAchievement(ctx.db, ctx.session.user.id), + checkAndGrantFiveCopyPastaADayAchievement(ctx.db, ctx.session.user.id), + ]); + if (isSuperAdmin) { const payload = handleEngagementAction( "ApproveCopyPasta", diff --git a/src/server/util/achievement.ts b/src/server/util/achievement.ts new file mode 100644 index 0000000..acbbbd8 --- /dev/null +++ b/src/server/util/achievement.ts @@ -0,0 +1,126 @@ +import { AchievementType, type PrismaClient } from "@prisma/client"; +import { ACHIEVEMENT_MINIMUM } from "~/lib/constant"; +import { getJakartaDate } from "~/utils"; + +export async function checkAndGrantFiveCopyPastaADayAchievement( + db: PrismaClient, + userId: string, +) { + const today = getJakartaDate(); + today.setHours(0, 0, 0); + + const userCreatedCopyPastaToday = await db.copyPasta.count({ + where: { + createdAt: { + gte: today, + }, + createdById: userId, + }, + }); + + if (userCreatedCopyPastaToday >= 5) { + const existingAchievement = await db.achievement.findUnique({ + where: { + userId_type: { + userId, + type: AchievementType.FiveCopyPastaADay, + }, + }, + }); + + if (!existingAchievement) { + await db.achievement.create({ + data: { + userId, + type: AchievementType.FiveCopyPastaADay, + }, + }); + console.log( + `User ${userId} has been granted the FiveCopyPastaADay achievement!`, + ); + } + } +} + +export async function checkAndGrantTagCollectionAchievement( + db: PrismaClient, + userId: string, +) { + const distinctTags = await db.copyPastasOnTags.findMany({ + where: { + copyPastas: { + createdById: userId, + }, + }, + distinct: ["tagId"], + }); + + if (distinctTags.length >= ACHIEVEMENT_MINIMUM.TAG_COLLECTOR_MINIMUM) { + const existingAchievement = await db.achievement.findUnique({ + where: { + userId_type: { + userId, + type: AchievementType.TagCollector, + }, + }, + }); + + if (!existingAchievement) { + await db.achievement.create({ + data: { + userId, + type: AchievementType.TagCollector, + }, + }); + console.log(`User ${userId} has been granted the TagMaster achievement!`); + } + } +} + +export async function checkAndGrantCollectionCuratorAchievement( + db: PrismaClient, + userId: string, +) { + const collections = await db.collection.findMany({ + where: { + createdById: userId, + }, + include: { + _count: { + select: { copyPastas: true }, + }, + }, + }); + + const qualifiedCollections = collections.filter( + (collection) => + collection._count.copyPastas >= + ACHIEVEMENT_MINIMUM.COLLECTION_CURATOR.COPY_PASTA_PER_COLLECTION, + ); + + if ( + qualifiedCollections.length >= + ACHIEVEMENT_MINIMUM.COLLECTION_CURATOR.COLLECTION_MINIMUM + ) { + const existingAchievement = await db.achievement.findUnique({ + where: { + userId_type: { + userId, + type: AchievementType.CollectionCurator, + }, + }, + }); + + if (!existingAchievement) { + await db.achievement.create({ + data: { + userId, + type: AchievementType.CollectionCurator, + }, + }); + console.log( + `User ${userId} has been granted the CollectionCurator achievement!`, + ); + } + } +}