Files
hoerdle/lib/dailyPuzzle.ts

246 lines
7.8 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
import { getTodayISOString } from './dateUtils';
const prisma = new PrismaClient();
export async function getOrCreateDailyPuzzle(genreName: string | null = null) {
try {
const today = getTodayISOString();
let genreId: number | null = null;
if (genreName) {
const genre = await prisma.genre.findUnique({
where: { name: genreName }
});
if (genre) {
genreId = genre.id;
} else {
return null; // Genre not found
}
}
let dailyPuzzle = await prisma.dailyPuzzle.findFirst({
where: {
date: today,
genreId: genreId
},
include: { song: true },
});
if (!dailyPuzzle) {
// Get songs available for this genre
const whereClause = genreId
? { genres: { some: { id: genreId } } }
: {}; // Global puzzle picks from ALL songs
const allSongs = await prisma.song.findMany({
where: whereClause,
include: {
puzzles: {
where: { genreId: genreId }
},
},
});
if (allSongs.length === 0) {
console.log(`[Daily Puzzle] No songs available for genre: ${genreName || 'Global'}`);
return null;
}
// Calculate weights
const weightedSongs = allSongs.map(song => ({
song,
weight: 1.0 / (song.puzzles.length + 1),
}));
// Calculate total weight
const totalWeight = weightedSongs.reduce((sum, item) => sum + item.weight, 0);
// Pick a random song based on weights
let random = Math.random() * totalWeight;
let selectedSong = weightedSongs[0].song;
for (const item of weightedSongs) {
random -= item.weight;
if (random <= 0) {
selectedSong = item.song;
break;
}
}
// Create the daily puzzle
try {
dailyPuzzle = await prisma.dailyPuzzle.create({
data: {
date: today,
songId: selectedSong.id,
genreId: genreId
},
include: { song: true },
});
console.log(`[Daily Puzzle] Created new puzzle for ${today} (Genre: ${genreName || 'Global'}) with song: ${selectedSong.title}`);
} catch (e) {
// Handle race condition
console.log('[Daily Puzzle] Creation failed, trying to fetch again (likely race condition)');
dailyPuzzle = await prisma.dailyPuzzle.findFirst({
where: {
date: today,
genreId: genreId
},
include: { song: true },
});
}
}
if (!dailyPuzzle) return null;
// Calculate puzzle number (sequential day count)
const whereClause = genreId
? { genreId: genreId }
: { genreId: null, specialId: null };
const puzzleCount = await prisma.dailyPuzzle.count({
where: {
...whereClause,
date: {
lte: dailyPuzzle.date
}
}
});
return {
id: dailyPuzzle.id,
puzzleNumber: puzzleCount,
audioUrl: `/api/audio/${dailyPuzzle.song.filename}`,
songId: dailyPuzzle.songId,
title: dailyPuzzle.song.title,
artist: dailyPuzzle.song.artist,
coverImage: dailyPuzzle.song.coverImage ? `/uploads/covers/${dailyPuzzle.song.coverImage}` : null,
releaseYear: dailyPuzzle.song.releaseYear,
genre: genreName
};
} catch (error) {
console.error('Error in getOrCreateDailyPuzzle:', error);
return null;
}
}
export async function getOrCreateSpecialPuzzle(specialName: string) {
try {
const today = getTodayISOString();
const special = await prisma.special.findUnique({
where: { name: specialName }
});
if (!special) return null;
let dailyPuzzle = await prisma.dailyPuzzle.findFirst({
where: {
date: today,
specialId: special.id
},
include: { song: true },
});
if (!dailyPuzzle) {
// Get songs available for this special through SpecialSong
const specialSongs = await prisma.specialSong.findMany({
where: { specialId: special.id },
include: {
song: {
include: {
puzzles: {
where: { specialId: special.id }
}
}
}
}
});
if (specialSongs.length === 0) return null;
// Calculate weights
const weightedSongs = specialSongs.map(specialSong => ({
specialSong,
weight: 1.0 / (specialSong.song.puzzles.length + 1),
}));
const totalWeight = weightedSongs.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * totalWeight;
let selectedSpecialSong = weightedSongs[0].specialSong;
for (const item of weightedSongs) {
random -= item.weight;
if (random <= 0) {
selectedSpecialSong = item.specialSong;
break;
}
}
try {
dailyPuzzle = await prisma.dailyPuzzle.create({
data: {
date: today,
songId: selectedSpecialSong.songId,
specialId: special.id
},
include: { song: true },
});
} catch (e) {
dailyPuzzle = await prisma.dailyPuzzle.findFirst({
where: {
date: today,
specialId: special.id
},
include: { song: true },
});
}
}
if (!dailyPuzzle) return null;
// Fetch the startTime from SpecialSong
const specialSong = await prisma.specialSong.findUnique({
where: {
specialId_songId: {
specialId: special.id,
songId: dailyPuzzle.songId
}
}
});
// Calculate puzzle number
const puzzleCount = await prisma.dailyPuzzle.count({
where: {
specialId: special.id,
date: {
lte: dailyPuzzle.date
}
}
});
return {
id: dailyPuzzle.id,
puzzleNumber: puzzleCount,
audioUrl: `/api/audio/${dailyPuzzle.song.filename}`,
songId: dailyPuzzle.songId,
title: dailyPuzzle.song.title,
artist: dailyPuzzle.song.artist,
coverImage: dailyPuzzle.song.coverImage ? `/uploads/covers/${dailyPuzzle.song.coverImage}` : null,
releaseYear: dailyPuzzle.song.releaseYear,
special: specialName,
maxAttempts: special.maxAttempts,
unlockSteps: JSON.parse(special.unlockSteps),
startTime: specialSong?.startTime || 0
};
} catch (error) {
console.error('Error in getOrCreateSpecialPuzzle:', error);
return null;
}
}