Skip to content

Commit

Permalink
Merge pull request #13 from La-404-Devinci/feat/adjustments
Browse files Browse the repository at this point in the history
feat: added multi-club and dumb support
  • Loading branch information
Kan-A-Pesh authored Nov 6, 2024
2 parents 53163e1 + 7b1fda7 commit 57115b8
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 212 deletions.
3 changes: 2 additions & 1 deletion controllers/challenges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export default abstract class ChallengesController {
.select({
id: challenges.id,
name: challenges.name,
score: challenges.score
score: challenges.score,
clubId: challenges.clubId
})
.from(challenges)
.innerJoin(clubs, eq(challenges.clubId, clubs.id))
Expand Down
7 changes: 3 additions & 4 deletions controllers/clubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,17 @@ export default abstract class ClubController {
return club.length ? club[0] : null;
}

public static async getDailyClub() {
public static async getDailyClubs() {
const club = await DB.instance
.select({
avatarUrl: clubs.avatarUrl,
name: clubs.name,
description: clubs.description
})
.from(clubs)
.where(eq(clubs.dailyDate, new Date()))
.limit(1);
.where(eq(clubs.dailyDate, new Date()));

return club.length ? club[0] : null;
return club;
}

public static async createClub(name: string, avatarUrl: string, description?: string, dailyDate?: Date) {
Expand Down
208 changes: 104 additions & 104 deletions controllers/leaderboard.ts
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;
}
}
108 changes: 54 additions & 54 deletions docker/compose.coolify.yml
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:
Loading

0 comments on commit 57115b8

Please sign in to comment.