diff --git a/backend/controller/controller.ts b/backend/controller/controller.ts index 1e7bc93..43cdb7e 100644 --- a/backend/controller/controller.ts +++ b/backend/controller/controller.ts @@ -2,10 +2,11 @@ import type { ServerWebSocket } from "bun"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import type { z, ZodType } from "zod"; +import * as schema from "../schema"; interface RequestContext { user?: string; - db: BunSQLiteDatabase; + db: BunSQLiteDatabase; ws: ServerWebSocket; } diff --git a/backend/controller/userController.ts b/backend/controller/userController.ts index 16b858d..78e22c4 100644 --- a/backend/controller/userController.ts +++ b/backend/controller/userController.ts @@ -19,6 +19,9 @@ import { import { getWeight, lootboxes } from "../../shared/lootboxes"; import { weightedPickRandom } from "../../shared/utils"; import { emit } from "../events"; +import { Game } from "../schema"; +import { and, eq, gt } from "drizzle-orm"; +import dayjs from "dayjs"; const secret = process.env.SECRET!; @@ -176,4 +179,28 @@ export const userController = createController({ }); }, ), + getHeatmap: createEndpoint( + z.object({ + id: z.string(), + }), + async ({ id }, { db }) => { + const now = dayjs(); + const firstOfYear = now + .set("day", 0) + .set("month", 0) + .set("hour", 0) + .set("minute", 0); + const gamesOfUser = await db.query.Game.findMany({ + where: and(eq(Game.user, id), gt(Game.finished, firstOfYear.valueOf())), + }); + const heat = Array.from({ + length: now.diff(firstOfYear, "days") + 1, + }).fill(0); + gamesOfUser.forEach((game) => { + const day = dayjs(game.finished).diff(firstOfYear, "days"); + heat[day] += 1; + }); + return heat; + }, + ), }); diff --git a/backend/database/getDb.ts b/backend/database/getDb.ts index f86fcf1..9057e41 100644 --- a/backend/database/getDb.ts +++ b/backend/database/getDb.ts @@ -1,8 +1,9 @@ import { drizzle } from "drizzle-orm/bun-sqlite"; +import * as schema from "../schema"; import { Database } from "bun:sqlite"; export const getDb = (filename: string = "sqlite.db") => { const sqlite = new Database(filename); - const db = drizzle(sqlite); + const db = drizzle(sqlite, { schema }); return db; }; diff --git a/backend/repositories/collectionRepository.ts b/backend/repositories/collectionRepository.ts index bc3bd5c..5137748 100644 --- a/backend/repositories/collectionRepository.ts +++ b/backend/repositories/collectionRepository.ts @@ -3,9 +3,10 @@ import { Collection, type CollectionType } from "../schema"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { decode, encode } from "@msgpack/msgpack"; import type { UserCollection } from "../../shared/gameType"; +import * as schema from "../schema"; export const getCollection = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, ): Promise => { const res = ( @@ -24,7 +25,7 @@ export const getCollection = async ( }; export const upsertCollection = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, collection: UserCollection, ) => { diff --git a/backend/repositories/gameRepository.ts b/backend/repositories/gameRepository.ts index f626410..9c16b4f 100644 --- a/backend/repositories/gameRepository.ts +++ b/backend/repositories/gameRepository.ts @@ -3,12 +3,19 @@ import { Game, type GameType } from "../schema"; import { eq, sql, desc, and, not } from "drizzle-orm"; import type { ServerGame } from "../../shared/game"; import { decode, encode } from "@msgpack/msgpack"; +import * as schema from "../schema"; -export const getGame = async (db: BunSQLiteDatabase, uuid: string) => { +export const getGame = async ( + db: BunSQLiteDatabase, + uuid: string, +) => { return (await db.select().from(Game).where(eq(Game.uuid, uuid)))[0]; }; -export const getGames = async (db: BunSQLiteDatabase, user: string) => { +export const getGames = async ( + db: BunSQLiteDatabase, + user: string, +) => { return await db .select() .from(Game) @@ -16,7 +23,10 @@ export const getGames = async (db: BunSQLiteDatabase, user: string) => { .orderBy(desc(Game.started)); }; -export const getCurrentGame = async (db: BunSQLiteDatabase, user: string) => { +export const getCurrentGame = async ( + db: BunSQLiteDatabase, + user: string, +) => { return ( await db .select() @@ -27,7 +37,10 @@ export const getCurrentGame = async (db: BunSQLiteDatabase, user: string) => { )[0]; }; -export const getGamesCount = async (db: BunSQLiteDatabase, user: string) => { +export const getGamesCount = async ( + db: BunSQLiteDatabase, + user: string, +) => { return ( await db .select({ count: sql`count(*)` }) @@ -36,7 +49,10 @@ export const getGamesCount = async (db: BunSQLiteDatabase, user: string) => { )[0].count; }; -export const upsertGame = async (db: BunSQLiteDatabase, game: GameType) => { +export const upsertGame = async ( + db: BunSQLiteDatabase, + game: GameType, +) => { const { uuid, user, stage, gameState, finished, started } = game; const games = await db.select().from(Game).where(eq(Game.uuid, uuid)); if (games.length > 0) { @@ -62,7 +78,7 @@ export const upsertGame = async (db: BunSQLiteDatabase, game: GameType) => { }; export const upsertGameState = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, game: ServerGame, ) => { const { uuid, user, stage, finished, started } = game; @@ -77,7 +93,7 @@ export const upsertGameState = async ( }; export const getTotalGamesPlayed = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user?: string, ) => { if (user) diff --git a/backend/repositories/gemsRepository.ts b/backend/repositories/gemsRepository.ts index 9d94fb2..43515a9 100644 --- a/backend/repositories/gemsRepository.ts +++ b/backend/repositories/gemsRepository.ts @@ -1,8 +1,12 @@ import { eq } from "drizzle-orm"; import { Gems } from "../schema"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; +import * as schema from "../schema"; -export const getGems = async (db: BunSQLiteDatabase, user: string) => { +export const getGems = async ( + db: BunSQLiteDatabase, + user: string, +) => { const res = (await db.select().from(Gems).where(eq(Gems.user, user)))[0]; const count = res?.count ?? 0; const totalCount = res?.totalCount ?? 0; @@ -10,7 +14,7 @@ export const getGems = async (db: BunSQLiteDatabase, user: string) => { }; export const addGems = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, gems: number, ) => { @@ -28,7 +32,7 @@ export const addGems = async ( }; export const removeGems = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, gems: number, ) => { diff --git a/backend/repositories/scoreRepository.ts b/backend/repositories/scoreRepository.ts index 04d4961..e597ce3 100644 --- a/backend/repositories/scoreRepository.ts +++ b/backend/repositories/scoreRepository.ts @@ -1,8 +1,9 @@ import { eq, sql, not } from "drizzle-orm"; import { Game } from "../schema"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; +import * as schema from "../schema"; -export const getScoreBoard = async (db: BunSQLiteDatabase) => { +export const getScoreBoard = async (db: BunSQLiteDatabase) => { return ( await db .select({ stage: sql`max(${Game.stage})`, user: Game.user }) diff --git a/backend/repositories/userRepository.ts b/backend/repositories/userRepository.ts index cf4ed27..be9e829 100644 --- a/backend/repositories/userRepository.ts +++ b/backend/repositories/userRepository.ts @@ -5,9 +5,10 @@ import { userSettings as userSettingsSchema, type UserSettings as UserSettingsType, } from "../../shared/user-settings"; +import * as schema from "../schema"; export const registerUser = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, name: string, password: string, ) => { @@ -23,7 +24,7 @@ export const registerUser = async ( }; export const loginUser = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, name: string, password: string, ) => { @@ -41,7 +42,7 @@ export const loginUser = async ( }; export const getUser = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, name: string, ): Promise => { const user = await db @@ -52,7 +53,7 @@ export const getUser = async ( }; export const getUserSettings = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, ): Promise => { const userSettings = await db @@ -64,7 +65,7 @@ export const getUserSettings = async ( }; export const upsertUserSettings = async ( - db: BunSQLiteDatabase, + db: BunSQLiteDatabase, user: string, settings: UserSettingsType, ) => { @@ -87,7 +88,7 @@ export const upsertUserSettings = async ( } }; -export const getUserCount = async (db: BunSQLiteDatabase) => { +export const getUserCount = async (db: BunSQLiteDatabase) => { return (await db.select({ count: sql`count(*)` }).from(User))[0] .count; }; diff --git a/bun.lockb b/bun.lockb index 8069714..99f2628 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 70e190b..72cf3f4 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@uidotdev/usehooks": "^2.4.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "dayjs": "^1.11.13", "drizzle-orm": "0.33.0", "framer-motion": "^11.11.8", "jotai": "^2.10.0", diff --git a/shared/lootboxes.ts b/shared/lootboxes.ts index 27f0279..4ea67cb 100644 --- a/shared/lootboxes.ts +++ b/shared/lootboxes.ts @@ -246,4 +246,4 @@ export const halloween: Lootbox = { ], }; -export const lootboxes = [series1, halloween]; +export const lootboxes = [series1]; diff --git a/src/main.tsx b/src/main.tsx index 6564ed3..f9b9815 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -14,6 +14,7 @@ import MatchHistory from "./views/match-history/MatchHistory.tsx"; import Collection from "./views/collection/Collection.tsx"; import { AnimatePresence } from "framer-motion"; import Store from "./views/store/Store.tsx"; +import Profile from "./views/profile/Profile.tsx"; const setup = async () => { const token = localStorage.getItem("loginToken"); @@ -44,6 +45,7 @@ setup().then(() => { + diff --git a/src/views/profile/Profile.tsx b/src/views/profile/Profile.tsx new file mode 100644 index 0000000..85beb85 --- /dev/null +++ b/src/views/profile/Profile.tsx @@ -0,0 +1,39 @@ +import { useMemo } from "react"; +import { useWSQuery } from "../../hooks"; +import dayjs from "dayjs"; + +const Profile: React.FC = () => { + const { data: heatmap } = useWSQuery("user.getHeatmap", { id: "Gordon" }); + const now = useMemo(() => dayjs(), []); + const firstOfYear = useMemo( + () => now.set("day", 0).set("month", 0).set("hour", 0).set("minute", 0), + [now], + ); + const weeks = now.diff(firstOfYear, "weeks") + 1; + const maxHeat = heatmap ? Math.max(...heatmap) : 0; + + return ( +
+ {heatmap && ( +
+ {Array.from({ length: weeks }).map((_, w) => ( +
+ {Array.from({ length: 7 }).map((_, d) => ( +
+
+
+ ))} +
+ ))} +
+ )} +
+ ); +}; + +export default Profile;