generated from Kan-A-Pesh/express-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ceeb41e
commit 7b1fda7
Showing
5 changed files
with
222 additions
and
222 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,104 @@ | ||
import DB from "@/database/config"; | ||
import Redis from "@/database/redis"; | ||
import { acquired } from "@/database/schema/acquired"; | ||
import { challenges } from "@/database/schema/challenges"; | ||
import { users } from "@/database/schema/users"; | ||
import Logger from "@/log/logger"; | ||
import { eq, sum } from "drizzle-orm"; | ||
import UserController from "./users"; | ||
import { clubs } from "@/database/schema/clubs"; | ||
import ClubController from "./clubs"; | ||
|
||
export default abstract class LeaderboardController { | ||
public static async getLeaderboardEtag() { | ||
return await Redis.get<string>("leaderboard:etag"); | ||
} | ||
|
||
public static async getUserLeaderboard() { | ||
const leaderboardUuids = await Redis.sortedAll<string>("leaderboard:users"); | ||
const allUsers = await DB.instance | ||
.select({ | ||
uuid: users.uuid, | ||
username: users.username, | ||
quote: users.quote, | ||
avatarUrl: users.avatarUrl | ||
}) | ||
.from(users); | ||
|
||
const usersMap = new Map(allUsers.map((user) => [user.uuid, user])); | ||
return leaderboardUuids.map((set) => ({ | ||
score: set.score, | ||
user: usersMap.get(set.value) | ||
})); | ||
} | ||
|
||
public static async getClubLeaderboard() { | ||
const leaderboardUuids = await Redis.sortedAll<number>(`leaderboard:clubs`); | ||
const allClubs = await ClubController.getAllClubs(); | ||
|
||
const clubsMap = new Map(allClubs.map((club) => [club.id, club])); | ||
return leaderboardUuids.map((set) => ({ | ||
score: set.score, | ||
club: clubsMap.get(set.value) | ||
})); | ||
} | ||
|
||
public static async grant(userUuid: string, challengeId: number) { | ||
// Check if the user exists | ||
const user = await UserController.getUser(userUuid); | ||
if (!user) return false; | ||
|
||
// Create a new acquired record | ||
try { | ||
await DB.instance.insert(acquired).values({ | ||
userUuid: userUuid, | ||
challengeId: challengeId | ||
}); | ||
} catch (error: unknown) { | ||
Logger.error("leaderboard.ts::grant", error); | ||
return false; | ||
} | ||
|
||
const userScoreRequest = await DB.instance | ||
.select({ | ||
score: sum(challenges.score).as("score") | ||
}) | ||
.from(acquired) | ||
.innerJoin(challenges, eq(acquired.challengeId, challenges.id)) | ||
.where(eq(acquired.userUuid, userUuid)); | ||
|
||
if (userScoreRequest.length !== 1) return false; | ||
const userScore = parseInt(userScoreRequest[0].score ?? "0"); | ||
|
||
// Add/update the user's score in the leaderboard | ||
await Redis.sortedSet("leaderboard:users", userScore, userUuid); | ||
|
||
if (user.clubId) { | ||
// Add/update the club's score in the leaderboard | ||
|
||
//! Not sure if this is the MORE efficient way but, | ||
// every granter is a physical person. so they can't | ||
// grant a lot of challenges per second and overload | ||
// the server. That said, it's not that bad. | ||
|
||
const clubScoreRequest = await DB.instance | ||
.select({ | ||
score: sum(challenges.score).as("score") | ||
}) | ||
.from(clubs) | ||
.innerJoin(users, eq(clubs.id, users.clubId)) | ||
.innerJoin(acquired, eq(users.uuid, acquired.userUuid)) | ||
.where(eq(clubs.id, user.clubId)); | ||
|
||
if (clubScoreRequest.length !== 1) return false; | ||
const clubScore = parseInt(clubScoreRequest[0].score ?? "0"); | ||
|
||
await Redis.sortedSet(`leaderboard:clubs`, clubScore, user.clubId); | ||
} | ||
|
||
// Add ETag to the user's leaderboard | ||
await Redis.set(`leaderboard:etag`, crypto.randomUUID()); | ||
|
||
return true; | ||
} | ||
} | ||
import DB from "@/database/config"; | ||
import Redis from "@/database/redis"; | ||
import { acquired } from "@/database/schema/acquired"; | ||
import { challenges } from "@/database/schema/challenges"; | ||
import { users } from "@/database/schema/users"; | ||
import Logger from "@/log/logger"; | ||
import { eq, sum } from "drizzle-orm"; | ||
import UserController from "./users"; | ||
import { clubs } from "@/database/schema/clubs"; | ||
import ClubController from "./clubs"; | ||
|
||
export default abstract class LeaderboardController { | ||
public static async getLeaderboardEtag() { | ||
return await Redis.get<string>("leaderboard:etag"); | ||
} | ||
|
||
public static async getUserLeaderboard() { | ||
const leaderboardUuids = await Redis.sortedAll<string>("leaderboard:users"); | ||
const allUsers = await DB.instance | ||
.select({ | ||
uuid: users.uuid, | ||
username: users.username, | ||
quote: users.quote, | ||
avatarUrl: users.avatarUrl | ||
}) | ||
.from(users); | ||
|
||
const usersMap = new Map(allUsers.map((user) => [user.uuid, user])); | ||
return leaderboardUuids.map((set) => ({ | ||
score: set.score, | ||
user: usersMap.get(set.value) | ||
})); | ||
} | ||
|
||
public static async getClubLeaderboard() { | ||
const leaderboardUuids = await Redis.sortedAll<number>(`leaderboard:clubs`); | ||
const allClubs = await ClubController.getAllClubs(); | ||
|
||
const clubsMap = new Map(allClubs.map((club) => [club.id, club])); | ||
return leaderboardUuids.map((set) => ({ | ||
score: set.score, | ||
club: clubsMap.get(set.value) | ||
})); | ||
} | ||
|
||
public static async grant(userUuid: string, challengeId: number) { | ||
// Check if the user exists | ||
const user = await UserController.getUser(userUuid); | ||
if (!user) return false; | ||
|
||
// Create a new acquired record | ||
try { | ||
await DB.instance.insert(acquired).values({ | ||
userUuid: userUuid, | ||
challengeId: challengeId | ||
}); | ||
} catch (error: unknown) { | ||
Logger.error("leaderboard.ts::grant", error); | ||
return false; | ||
} | ||
|
||
const userScoreRequest = await DB.instance | ||
.select({ | ||
score: sum(challenges.score).as("score") | ||
}) | ||
.from(acquired) | ||
.innerJoin(challenges, eq(acquired.challengeId, challenges.id)) | ||
.where(eq(acquired.userUuid, userUuid)); | ||
|
||
if (userScoreRequest.length !== 1) return false; | ||
const userScore = parseInt(userScoreRequest[0].score ?? "0"); | ||
|
||
// Add/update the user's score in the leaderboard | ||
await Redis.sortedSet("leaderboard:users", userScore, userUuid); | ||
|
||
if (user.clubId) { | ||
// Add/update the club's score in the leaderboard | ||
|
||
//! Not sure if this is the MORE efficient way but, | ||
// every granter is a physical person. so they can't | ||
// grant a lot of challenges per second and overload | ||
// the server. That said, it's not that bad. | ||
|
||
const clubScoreRequest = await DB.instance | ||
.select({ | ||
score: sum(challenges.score).as("score") | ||
}) | ||
.from(clubs) | ||
.innerJoin(users, eq(clubs.id, users.clubId)) | ||
.innerJoin(acquired, eq(users.uuid, acquired.userUuid)) | ||
.where(eq(clubs.id, user.clubId)); | ||
|
||
if (clubScoreRequest.length !== 1) return false; | ||
const clubScore = parseInt(clubScoreRequest[0].score ?? "0"); | ||
|
||
await Redis.sortedSet(`leaderboard:clubs`, clubScore, user.clubId); | ||
} | ||
|
||
// Add ETag to the user's leaderboard | ||
await Redis.set(`leaderboard:etag`, crypto.randomUUID()); | ||
|
||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,66 @@ | ||
services: | ||
web: | ||
build: | ||
context: .. | ||
target: production | ||
environment: | ||
- MAIL_SERVER=${MAIL_SERVER} | ||
- MAIL_PORT=${MAIL_PORT:-587} | ||
- MAIL_SECURE=${MAIL_SECURE:-true} | ||
- MAIL_USER=${MAIL_USER:-username} | ||
- MAIL_PASS=${MAIL_PASS} | ||
- MAIL_FROM=${MAIL_FROM:-username <email>} | ||
- MAIL_REDIRECT_URL=${MAIL_REDIRECT_URL:-/auth/confirm#{token}} | ||
- PROFILE_REDIRECT_URL=${PROFILE_REDIRECT_URL:-/profile/{uuid}} | ||
web: | ||
build: | ||
context: .. | ||
target: production | ||
environment: | ||
- MAIL_SERVER=${MAIL_SERVER} | ||
- MAIL_PORT=${MAIL_PORT:-587} | ||
- MAIL_SECURE=${MAIL_SECURE:-true} | ||
- MAIL_USER=${MAIL_USER:-username} | ||
- MAIL_PASS=${MAIL_PASS} | ||
- MAIL_FROM=${MAIL_FROM:-username <email>} | ||
- MAIL_REDIRECT_URL=${MAIL_REDIRECT_URL:-/auth/confirm#{token}} | ||
- PROFILE_REDIRECT_URL=${PROFILE_REDIRECT_URL:-/profile/{uuid}} | ||
|
||
- JWT_SECRET=${SERVICE_BASE64_64_JWT_SECRET} | ||
- ADMIN_TOKEN=${SERVICE_BASE64_64_ADMIN_TOKEN} | ||
- JWT_SECRET=${SERVICE_BASE64_64_JWT_SECRET} | ||
- ADMIN_TOKEN=${SERVICE_BASE64_64_ADMIN_TOKEN} | ||
|
||
- POSTGRES_DB=${SERVICE_BASE64_POSTGRES_DB} | ||
- POSTGRES_PASSWORD=${SERVICE_BASE64_POSTGRES_PASSWORD} | ||
- POSTGRES_USER=${SERVICE_BASE64_POSTGRES_USER} | ||
- POSTGRES_DB=${SERVICE_BASE64_POSTGRES_DB} | ||
- POSTGRES_PASSWORD=${SERVICE_BASE64_POSTGRES_PASSWORD} | ||
- POSTGRES_USER=${SERVICE_BASE64_POSTGRES_USER} | ||
|
||
- MINIO_ROOT_PASSWORD=${SERVICE_BASE64_MINIO_ROOT_PASSWORD} | ||
- MINIO_ROOT_USER=${SERVICE_BASE64_MINIO_ROOT_USER} | ||
- MINIO_DEFAULT_BUCKETS=${SERVICE_BASE64_MINIO_DEFAULT_BUCKETS} | ||
- MINIO_ROOT_PASSWORD=${SERVICE_BASE64_MINIO_ROOT_PASSWORD} | ||
- MINIO_ROOT_USER=${SERVICE_BASE64_MINIO_ROOT_USER} | ||
- MINIO_DEFAULT_BUCKETS=${SERVICE_BASE64_MINIO_DEFAULT_BUCKETS} | ||
|
||
- SERVICE_FQDN_ADVENT_3000 | ||
- SERVICE_FQDN_ADVENT_3000 | ||
|
||
- TRUST_PROXY=${TRUST_PROXY:-true} | ||
- LOG_FOLDER=${LOG_FOLDER:-/logs} | ||
- POSTGRES_HOST=${POSTGRES_HOST:-postgres} | ||
- REDIS_HOST=${REDIS_HOST:-redis} | ||
- MINIO_HOST=${MINIO_HOST:-minio} | ||
- TRUST_PROXY=${TRUST_PROXY:-true} | ||
- LOG_FOLDER=${LOG_FOLDER:-/logs} | ||
- POSTGRES_HOST=${POSTGRES_HOST:-postgres} | ||
- REDIS_HOST=${REDIS_HOST:-redis} | ||
- MINIO_HOST=${MINIO_HOST:-minio} | ||
|
||
volumes: | ||
- applogs:/logs | ||
depends_on: | ||
- postgres | ||
- redis | ||
- minio | ||
volumes: | ||
- applogs:/logs | ||
depends_on: | ||
- postgres | ||
- redis | ||
- minio | ||
|
||
postgres: | ||
image: postgres:17-alpine | ||
environment: | ||
- POSTGRES_DB=${SERVICE_BASE64_POSTGRES_DB} | ||
- POSTGRES_PASSWORD=${SERVICE_BASE64_POSTGRES_PASSWORD} | ||
- POSTGRES_USER=${SERVICE_BASE64_POSTGRES_USER} | ||
volumes: | ||
- postgres:/var/lib/postgresql/data | ||
postgres: | ||
image: postgres:17-alpine | ||
environment: | ||
- POSTGRES_DB=${SERVICE_BASE64_POSTGRES_DB} | ||
- POSTGRES_PASSWORD=${SERVICE_BASE64_POSTGRES_PASSWORD} | ||
- POSTGRES_USER=${SERVICE_BASE64_POSTGRES_USER} | ||
volumes: | ||
- postgres:/var/lib/postgresql/data | ||
|
||
redis: | ||
image: redis:7.4 | ||
redis: | ||
image: redis:7.4 | ||
|
||
minio: | ||
image: "bitnami/minio:latest" | ||
environment: | ||
- MINIO_ROOT_PASSWORD=${SERVICE_BASE64_MINIO_ROOT_PASSWORD} | ||
- MINIO_ROOT_USER=${SERVICE_BASE64_MINIO_ROOT_USER} | ||
- MINIO_DEFAULT_BUCKETS=${SERVICE_BASE64_MINIO_DEFAULT_BUCKETS} | ||
volumes: | ||
- minio:/bitnami/minio/data | ||
minio: | ||
image: "bitnami/minio:latest" | ||
environment: | ||
- MINIO_ROOT_PASSWORD=${SERVICE_BASE64_MINIO_ROOT_PASSWORD} | ||
- MINIO_ROOT_USER=${SERVICE_BASE64_MINIO_ROOT_USER} | ||
- MINIO_DEFAULT_BUCKETS=${SERVICE_BASE64_MINIO_DEFAULT_BUCKETS} | ||
volumes: | ||
- minio:/bitnami/minio/data | ||
|
||
volumes: | ||
postgres: | ||
minio: | ||
applogs: | ||
postgres: | ||
minio: | ||
applogs: |
Oops, something went wrong.