- Add device ID generation (unique per device, stored in localStorage)
- Extend player ID format to: {basePlayerId}:{deviceId}
- Enable cross-domain sync on same device while keeping devices isolated
- Update backend APIs to support new player ID format
- Maintain backward compatibility with legacy UUID format
This allows:
- Each device (Desktop, Android, iOS) to have separate game states
- Cross-domain sync still works on the same device (hoerdle.de ↔ hördle.de)
- Easier debugging with visible device ID in player identifier
115 lines
3.8 KiB
TypeScript
115 lines
3.8 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { PrismaClient } from '@prisma/client';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
/**
|
|
* POST /api/player-id/suggest
|
|
*
|
|
* Tries to find a base player ID based on recently updated states for a genre and device.
|
|
* This helps synchronize player IDs across different domains (hoerdle.de and hördle.de)
|
|
* on the same device.
|
|
*
|
|
* Request body:
|
|
* - genreKey: Genre key (e.g., "global", "Rock", "special:00725")
|
|
* - deviceId: Device identifier (UUID)
|
|
*
|
|
* Returns:
|
|
* - basePlayerId: Suggested base player ID (UUID) if found, null otherwise
|
|
*/
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const body = await request.json();
|
|
const { genreKey, deviceId } = body;
|
|
|
|
if (!genreKey || typeof genreKey !== 'string') {
|
|
return NextResponse.json(
|
|
{ error: 'Missing or invalid genreKey' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Find the most recently updated player state for this genre
|
|
// Look for states updated in the last 48 hours
|
|
const cutoffDate = new Date();
|
|
cutoffDate.setHours(cutoffDate.getHours() - 48);
|
|
|
|
// If deviceId is provided, search for states with matching device ID
|
|
// Format: {basePlayerId}:{deviceId}
|
|
if (deviceId && typeof deviceId === 'string') {
|
|
// Search for states with the same device ID
|
|
const recentStates = await prisma.playerState.findMany({
|
|
where: {
|
|
genreKey: genreKey,
|
|
lastPlayed: {
|
|
gte: cutoffDate,
|
|
},
|
|
identifier: {
|
|
endsWith: `:${deviceId}`,
|
|
},
|
|
},
|
|
orderBy: {
|
|
lastPlayed: 'desc',
|
|
},
|
|
take: 1,
|
|
});
|
|
|
|
if (recentStates.length > 0) {
|
|
const recentState = recentStates[0];
|
|
// Extract base player ID from full identifier
|
|
const colonIndex = recentState.identifier.indexOf(':');
|
|
if (colonIndex !== -1) {
|
|
const basePlayerId = recentState.identifier.substring(0, colonIndex);
|
|
return NextResponse.json({
|
|
basePlayerId: basePlayerId,
|
|
lastPlayed: recentState.lastPlayed,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: Find any recent state for this genre (legacy support)
|
|
const recentState = await prisma.playerState.findFirst({
|
|
where: {
|
|
genreKey: genreKey,
|
|
lastPlayed: {
|
|
gte: cutoffDate,
|
|
},
|
|
},
|
|
orderBy: {
|
|
lastPlayed: 'desc',
|
|
},
|
|
});
|
|
|
|
if (recentState) {
|
|
// Extract base player ID if format is basePlayerId:deviceId
|
|
const colonIndex = recentState.identifier.indexOf(':');
|
|
if (colonIndex !== -1) {
|
|
const basePlayerId = recentState.identifier.substring(0, colonIndex);
|
|
return NextResponse.json({
|
|
basePlayerId: basePlayerId,
|
|
lastPlayed: recentState.lastPlayed,
|
|
});
|
|
} else {
|
|
// Legacy format: return as-is
|
|
return NextResponse.json({
|
|
basePlayerId: recentState.identifier,
|
|
lastPlayed: recentState.lastPlayed,
|
|
});
|
|
}
|
|
}
|
|
|
|
// No recent state found
|
|
return NextResponse.json({
|
|
basePlayerId: null,
|
|
});
|
|
} catch (error) {
|
|
console.error('[player-id/suggest] Error finding player ID:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Internal Server Error' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|