feat: add daily fruit feature and related components
Build Docker Image / build (push) Successful in 2m8s
Build Docker Image / build (push) Successful in 2m8s
- Introduced a new daily fruit selection mechanism with associated database schema. - Created routes and components for displaying and interacting with daily fruit. - Implemented logic for tracking wins related to daily fruit and character guesses. - Added localization support for new daily fruit titles in English and French. - Enhanced the user experience with history tracking for guesses and wins. - Refactored existing character-related logic to accommodate new fruit functionality.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { character, devilFruit, devilFruitHistory, type DevilFruit, type Character } from '$lib/server/db/schema';
|
||||
import { desc, eq, and } from 'drizzle-orm';
|
||||
|
||||
// Generate or get random seed for daily fruit selection
|
||||
const RANDOM_SEED = Math.random();
|
||||
|
||||
const characterWithFruitSelect = {
|
||||
id: character.id,
|
||||
name: character.name,
|
||||
devilFruitId: character.devilFruitId,
|
||||
devilFruitName: devilFruit.name,
|
||||
devilFruitType: devilFruit.type,
|
||||
// include url if available from devilFruit join
|
||||
url: devilFruit.url
|
||||
};
|
||||
|
||||
export type CharacterWithFruit = Character & {
|
||||
devilFruitName: string | null;
|
||||
devilFruitType: string | null;
|
||||
};
|
||||
|
||||
export function getDateKey(date: Date): number {
|
||||
return normalizeDay(date).getTime();
|
||||
}
|
||||
|
||||
export function normalizeDay(date: Date = new Date()): Date {
|
||||
const normalized = new Date(date);
|
||||
normalized.setHours(1, 0, 0, 0);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function pickDailyFruit(fruits: DevilFruit[], date: Date): DevilFruit {
|
||||
const timestamp = getDateKey(date);
|
||||
const daysSinceEpoch = Math.floor(timestamp / 1000 / 60 / 60 / 24);
|
||||
const combinedSeed = (daysSinceEpoch + Math.floor(RANDOM_SEED * 1000000)) % fruits.length;
|
||||
return fruits[combinedSeed];
|
||||
}
|
||||
|
||||
export async function getCharactersWithDevilFruit(): Promise<CharacterWithFruit[]> {
|
||||
const rows = (await db
|
||||
.select(characterWithFruitSelect)
|
||||
.from(character)
|
||||
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
||||
.all()) as any[];
|
||||
|
||||
return rows.filter((r) => r.devilFruitId) as CharacterWithFruit[];
|
||||
}
|
||||
|
||||
export async function getAllDevilFruitsFromCharacters(): Promise<DevilFruit[]> {
|
||||
const characters = (await db
|
||||
.select(characterWithFruitSelect)
|
||||
.from(character)
|
||||
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
||||
.all()) as any[];
|
||||
|
||||
const map = new Map<string, DevilFruit>();
|
||||
for (const row of characters) {
|
||||
if (row.devilFruitId && row.devilFruitName) {
|
||||
if (!map.has(row.devilFruitId)) {
|
||||
map.set(row.devilFruitId, {
|
||||
id: row.devilFruitId,
|
||||
name: row.devilFruitName,
|
||||
type: row.devilFruitType ?? 'Unknown',
|
||||
url: row.url ?? null
|
||||
} as DevilFruit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
export async function getFruitById(fruitId: string): Promise<DevilFruit | null> {
|
||||
const [found] = await db.select().from(devilFruit).where(eq(devilFruit.id, fruitId)).limit(1);
|
||||
return (found as DevilFruit) ?? null;
|
||||
}
|
||||
|
||||
export async function getOrCreateTodayFruit(date: Date = new Date()): Promise<DevilFruit | null> {
|
||||
const today = normalizeDay(date);
|
||||
const todayDate = getDateKey(today);
|
||||
|
||||
const [existingEntry] = await db
|
||||
.select()
|
||||
.from(devilFruitHistory)
|
||||
.where(eq(devilFruitHistory.date, todayDate))
|
||||
.limit(1);
|
||||
|
||||
if (existingEntry?.devilFruitId) {
|
||||
return getFruitById(existingEntry.devilFruitId);
|
||||
}
|
||||
|
||||
const allFruits = await getAllDevilFruitsFromCharacters();
|
||||
if (allFruits.length === 0) return null;
|
||||
|
||||
const chosen = pickDailyFruit(allFruits, today);
|
||||
|
||||
try {
|
||||
await db.insert(devilFruitHistory).values({
|
||||
devilFruitId: chosen.id,
|
||||
date: todayDate,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to record daily fruit:', error);
|
||||
}
|
||||
|
||||
return chosen;
|
||||
}
|
||||
|
||||
export async function getYesterdayFruit(date: Date = new Date()): Promise<DevilFruit | null> {
|
||||
const yesterday = new Date(date);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const yesterdayDate = getDateKey(yesterday);
|
||||
|
||||
const [yesterdayEntry] = await db
|
||||
.select()
|
||||
.from(devilFruitHistory)
|
||||
.where(eq(devilFruitHistory.date, yesterdayDate))
|
||||
.limit(1);
|
||||
|
||||
if (!yesterdayEntry?.devilFruitId) return null;
|
||||
|
||||
return getFruitById(yesterdayEntry.devilFruitId);
|
||||
}
|
||||
|
||||
export async function getTodayFruitWinsCount(fruitId: string, date: Date = new Date()): Promise<number> {
|
||||
const today = normalizeDay(date);
|
||||
const todayDate = getDateKey(today);
|
||||
|
||||
const [result] = await db
|
||||
.select({ won: devilFruitHistory.won })
|
||||
.from(devilFruitHistory)
|
||||
.where(and(eq(devilFruitHistory.devilFruitId, fruitId), eq(devilFruitHistory.date, todayDate)));
|
||||
|
||||
return result?.won ?? 0;
|
||||
}
|
||||
Reference in New Issue
Block a user