diff --git a/src/commands/alltimekdleaderboard.ts b/src/commands/alltimekdleaderboard.ts index 90979de..5db5cd0 100644 --- a/src/commands/alltimekdleaderboard.ts +++ b/src/commands/alltimekdleaderboard.ts @@ -1,6 +1,5 @@ import { Command, sendMessage } from "commands"; -import { getAllUserRecords } from "db/dbUser"; -import { getTimeoutStats } from "lib/getStats"; +import { getKDLeaderboard } from "db/dbUser"; import User from "user"; type KD = { user: User; kd: number; }; @@ -10,27 +9,19 @@ export default new Command({ aliases: ['alltimeleaderboard', 'alltimekdleaderboard'], usertype: 'chatter', execution: async msg => { - const users = await getAllUserRecords(); - if (!users) return; - - const userKDs: KD[] = []; - await Promise.all(users.map(async userRecord => { - const user = await User.initUserId(userRecord.id.toString()); - if (!user) return; - const data = await getTimeoutStats(user, false); - if (!data) return; - if (data.hit.blaster < 5) return; - - let kd = data.shot.blaster / data.hit.blaster; - if (isNaN(kd)) kd = 0; - userKDs.push({ user, kd }); - })); - - if (userKDs.length === 0) { + const rawKD = await getKDLeaderboard(); + if (rawKD.length === 0) { await sendMessage(`No users on leaderboard yet!`, msg.messageId); return; }; + const userKDs: KD[] = []; + await Promise.all(rawKD.map(async userRecord => { + const user = await User.initUserId(userRecord.userId.toString()); + if (!user) return; + userKDs.push({ user, kd: userRecord.KD }) + })); + userKDs.sort((a, b) => b.kd - a.kd); const txt: string[] = []; diff --git a/src/commands/monthlykdleaderboard.ts b/src/commands/monthlykdleaderboard.ts index d398e95..04fd458 100644 --- a/src/commands/monthlykdleaderboard.ts +++ b/src/commands/monthlykdleaderboard.ts @@ -1,6 +1,5 @@ import { Command, sendMessage } from "commands"; -import { getAllUserRecords } from "db/dbUser"; -import { getTimeoutStats } from "lib/getStats"; +import { getKDLeaderboard } from "db/dbUser"; import User from "user"; type KD = { user: User; kd: number; }; @@ -10,27 +9,21 @@ export default new Command({ aliases: ['monthlyleaderboard', 'kdleaderboard', 'leaderboard'], usertype: 'chatter', execution: async msg => { - const users = await getAllUserRecords(); - if (!users) return; + const monthdata = new Date().toISOString().slice(0, 7); - const userKDs: KD[] = []; - await Promise.all(users.map(async userRecord => { - const user = await User.initUserId(userRecord.id.toString()); - if (!user) return; - const data = await getTimeoutStats(user, true); - if (!data) return; - if (data.hit.blaster < 5) return; - - let kd = data.shot.blaster / data.hit.blaster; - if (isNaN(kd)) kd = 0; - userKDs.push({ user, kd }); - })); - - if (userKDs.length === 0) { + const rawKD = await getKDLeaderboard(monthdata); + if (rawKD.length === 0) { await sendMessage(`No users on leaderboard yet!`, msg.messageId); return; }; + const userKDs: KD[] = []; + await Promise.all(rawKD.map(async userRecord => { + const user = await User.initUserId(userRecord.userId.toString()); + if (!user) return; + userKDs.push({ user, kd: userRecord.KD }) + })); + userKDs.sort((a, b) => b.kd - a.kd); const txt: string[] = []; diff --git a/src/db/dbGetLoot.ts b/src/db/dbGetLoot.ts index 498908e..07ff620 100644 --- a/src/db/dbGetLoot.ts +++ b/src/db/dbGetLoot.ts @@ -3,7 +3,6 @@ import { getLoots } from "db/schema"; import type { inventory } from "items"; import type User from "user"; - export async function createGetLootRecord(user: User, qbucks: number, inventory: inventory) { await db.insert(getLoots).values({ user: parseInt(user.id), diff --git a/src/db/dbUser.ts b/src/db/dbUser.ts index fc08734..9c079be 100644 --- a/src/db/dbUser.ts +++ b/src/db/dbUser.ts @@ -1,9 +1,8 @@ import db from "db/connection"; -import { users } from "db/schema"; +import { timeouts, users } from "db/schema"; import { itemarray, type inventory } from "items"; import type User from "user"; -import logger from "lib/logger"; -import { desc, eq } from "drizzle-orm"; +import { count, desc, eq, inArray, sql, ne, between, and, SQL } from "drizzle-orm"; /** Use this function to both ensure existance and to retreive data */ export async function getUserRecord(user: User) { @@ -45,3 +44,49 @@ export async function updateUserRecord(user: User, newData: updateUser) { export async function getBalanceLeaderboard() { return await db.select().from(users).orderBy(desc(users.balance)).limit(10); }; + +export async function getKDLeaderboard(monthData?: string) { + let condition: SQL | undefined = ne(timeouts.item, 'silverbullet'); + if (monthData) { + const begin = Date.parse(monthData); + const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); + condition = and(condition, between(timeouts.created, new Date(begin), new Date(end))); + }; + + const usersGotShot = await db.select({ + userId: users.id, + amount: count(timeouts.target), + }) + .from(users) + .innerJoin(timeouts, eq(users.id, timeouts.target)) + .groupBy(users.id) + .having(sql`count(${timeouts.id}) > 5`) + .where(condition); + + const usersThatShot = await db.select({ + userId: users.id, + amount: count(timeouts.user) + }) + .from(users) + .innerJoin(timeouts, eq(users.id, timeouts.user)) + .groupBy(users.id) + .where( + and( + condition, + inArray(users.id, usersGotShot.map(a => a.userId)) + ) + ); + + const lookup = new Map(usersThatShot.map(a => [a.userId, a.amount])); + const result = usersGotShot.map(user => ({ + userId: user.userId, + KD: lookup.get(user.userId)! / user.amount + })); + + result.map(user => { + if (isNaN(user.KD)) user.KD = 0; + return user + }); + + return result; +}; diff --git a/src/db/schema.ts b/src/db/schema.ts index b1b4c22..c9d5b61 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -1,8 +1,8 @@ import type { AccessToken } from "@twurple/auth"; import type { inventory, items } from "items"; - import { integer, jsonb, pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; import type { anivBots } from "lib/handleAnivMessage"; +import { relations } from "drizzle-orm"; export const auth = pgTable('auth', { id: integer().primaryKey(), @@ -17,6 +17,16 @@ export const users = pgTable('users', { lastlootbox: timestamp().default(new Date(0)).notNull() }); +export const usersRelations = relations(users, ({ many }) => ({ + timeouts_target: many(timeouts), + timeouts_shooter: many(timeouts), + usedItems: many(usedItems), + cheerEvents: many(cheerEvents), + cheers: many(cheers), + anivTimeouts: many(anivTimeouts), + getLoots: many(getLoots) +})); + export const timeouts = pgTable('timeouts', { id: uuid().defaultRandom().primaryKey(), user: integer().notNull().references(() => users.id), @@ -25,6 +35,19 @@ export const timeouts = pgTable('timeouts', { created: timestamp().defaultNow().notNull() }); +export const timeoutsRelations = relations(timeouts, ({ one }) => ({ + user: one(users, { + fields: [timeouts.user], + references: [users.id], + relationName: 'shooter' + }), + target: one(users, { + fields: [timeouts.target], + references: [users.id], + relationName: 'target' + }) +})) + export const usedItems = pgTable('usedItems', { id: uuid().defaultRandom().primaryKey(), user: integer().notNull().references(() => users.id), @@ -32,6 +55,13 @@ export const usedItems = pgTable('usedItems', { created: timestamp().defaultNow().notNull() }); +export const usedItemsRelations = relations(usedItems, ({ one }) => ({ + user: one(users, { + fields: [usedItems.user], + references: [users.id] + }) +})); + export const cheerEvents = pgTable('cheerEvents', { id: uuid().defaultRandom().primaryKey(), user: integer().notNull().references(() => users.id), @@ -39,6 +69,13 @@ export const cheerEvents = pgTable('cheerEvents', { created: timestamp().defaultNow().notNull() }); +export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({ + user: one(users, { + fields: [cheerEvents.user], + references: [users.id] + }) +})); + export const cheers = pgTable('cheers', { id: uuid().defaultRandom().primaryKey(), user: integer().notNull().references(() => users.id), @@ -46,6 +83,13 @@ export const cheers = pgTable('cheers', { created: timestamp().defaultNow().notNull() }); +export const cheersRelations = relations(cheers, ({ one }) => ({ + user: one(users, { + fields: [cheers.user], + references: [users.id] + }) +})); + export const anivTimeouts = pgTable('anivTimeouts', { id: uuid().defaultRandom().primaryKey(), user: integer().notNull().references(() => users.id), @@ -55,6 +99,13 @@ export const anivTimeouts = pgTable('anivTimeouts', { created: timestamp().defaultNow().notNull() }); +export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({ + user: one(users, { + fields: [anivTimeouts.user], + references: [users.id] + }) +})); + export const getLoots = pgTable('getLoots', { id: uuid().defaultRandom().primaryKey(), user: integer().notNull().references(() => users.id), @@ -62,3 +113,10 @@ export const getLoots = pgTable('getLoots', { items: jsonb().$type().notNull(), created: timestamp().defaultNow().notNull() }); + +export const getLootsRelations = relations(getLoots, ({ one }) => ({ + user: one(users, { + fields: [getLoots.user], + references: [users.id] + }) +}));