Fix: Spotify OAuth RLS integration, profile mapping, and robust AI playlist generation bypass

This commit is contained in:
RoadtripDJ Dev
2026-05-19 01:27:04 +01:00
parent a0f11f73e8
commit 9222d3a483
6 changed files with 286 additions and 83 deletions

View File

@@ -46,18 +46,39 @@ export default function NewTripScreen({ navigation }) {
let generatedPlaylistUrl = null;
try {
console.log("GENERATING_PLAYLIST_FOR_TRIP:", tripName);
console.log("PLAYLIST_CREATE_START");
// Helper for robust parsing
const safeParseJson = async (res: Response, label: string) => {
const rawText = await res.text();
console.log(`PLAYLIST_API_STATUS [${label}]:`, res.status);
console.log(`PLAYLIST_API_CONTENT_TYPE [${label}]:`, res.headers.get("content-type"));
console.log(`PLAYLIST_API_RAW_RESPONSE [${label}]:`, rawText.substring(0, 300) + (rawText.length > 300 ? "..." : ""));
try {
return JSON.parse(rawText);
} catch (e) {
throw new Error(`Playlist API returned non-JSON response [${label}]: ${rawText}`);
}
};
// A. Get provider token
const providerToken = await getSpotifyAccessToken();
console.log("SPOTIFY_ACCESS_TOKEN_EXISTS:", !!providerToken);
if (!providerToken) {
throw new Error("Spotify token missing. Please log in with Spotify again.");
}
if (providerToken) {
console.log("Spotify token missing, skipping playlist generation.");
Alert.alert('Aviso', 'Spotify token missing, please login again');
} else {
// B. Fetch Spotify User ID
const spotifyUserRes = await fetch('https://api.spotify.com/v1/me', {
headers: { 'Authorization': `Bearer ${providerToken}` }
headers: {
'Authorization': `Bearer ${providerToken}`,
'Content-Type': 'application/json'
}
});
const spotifyUserData = await spotifyUserRes.json();
const spotifyUserData = await safeParseJson(spotifyUserRes, 'SpotifyUser');
if (!spotifyUserData.id) throw new Error('Could not fetch Spotify User ID');
const spotifyUserId = spotifyUserData.id;
@@ -71,8 +92,25 @@ export default function NewTripScreen({ navigation }) {
stream: false
})
});
const ollamaData = await ollamaRes.json();
const seed_genres = ollamaData.message.content.trim().replace(/\s+/g, '').toLowerCase();
let seed_genres = "pop,road-trip,happy"; // Fallback genres
try {
const ollamaData = await safeParseJson(ollamaRes, 'Ollama');
let rawAiText = ollamaData?.message?.content || "";
// Clean AI text
rawAiText = rawAiText.replace(/```json/g, '').replace(/```/g, '').trim();
if (rawAiText.length > 0 && !rawAiText.toLowerCase().startsWith("a ")) {
seed_genres = rawAiText.replace(/\s+/g, '').toLowerCase();
// Spotify limits to 5 seed genres, let's keep it clean
seed_genres = seed_genres.split(',').slice(0, 5).join(',');
} else {
console.log("AI returned plain text/error, using fallback genres:", rawAiText);
}
} catch (aiError) {
console.log("AI parsing failed, using fallback genres.", aiError);
}
// D. Create empty playlist
const createPlaylistRes = await fetch(`https://api.spotify.com/v1/users/${spotifyUserId}/playlists`, {
@@ -87,24 +125,45 @@ export default function NewTripScreen({ navigation }) {
public: false
})
});
const playlistData = await createPlaylistRes.json();
const playlistData = await safeParseJson(createPlaylistRes, 'CreatePlaylist');
if (!playlistData.id) throw new Error('Could not create playlist');
const playlistId = playlistData.id;
generatedPlaylistUrl = playlistData.external_urls.spotify;
// E. Fetch Spotify track recommendations
// E. Fetch Spotify track recommendations via Search (does not require Premium)
let accumulatedDurationMs = 0;
let trackUris: string[] = [];
let attempts = 0;
const genresList = seed_genres.split(',');
let genreIndex = 0;
while (accumulatedDurationMs < tripDurationMs && attempts < 10) {
const recommendationsRes = await fetch(`https://api.spotify.com/v1/recommendations?seed_genres=${encodeURIComponent(seed_genres)}&limit=50`, {
headers: { 'Authorization': `Bearer ${providerToken}` }
const currentGenre = genresList[genreIndex % genresList.length] || 'pop';
const query = encodeURIComponent(`genre:${currentGenre}`);
const searchRes = await fetch(`https://api.spotify.com/v1/search?type=track&q=${query}&limit=50&offset=${attempts * 50}`, {
headers: {
'Authorization': `Bearer ${providerToken}`,
'Content-Type': 'application/json'
}
});
if (!recommendationsRes.ok) break;
const recommendationsData = await recommendationsRes.json();
if (!recommendationsData.tracks || recommendationsData.tracks.length === 0) break;
if (!searchRes.ok) {
const errText = await searchRes.text();
console.error("Search failed:", errText);
Alert.alert('Erro Spotify', `Aviso ao adicionar músicas: ${errText.substring(0, 100)}`);
break;
}
const searchData = await safeParseJson(searchRes, 'SearchTracks');
const tracks = searchData?.tracks?.items;
if (!tracks || tracks.length === 0) {
genreIndex++;
attempts++;
continue;
}
for (const track of recommendationsData.tracks) {
for (const track of tracks) {
if (!trackUris.includes(track.uri)) {
trackUris.push(track.uri);
accumulatedDurationMs += track.duration_ms;
@@ -112,6 +171,7 @@ export default function NewTripScreen({ navigation }) {
}
}
attempts++;
genreIndex++;
}
if (trackUris.length > 0) {
@@ -128,16 +188,14 @@ export default function NewTripScreen({ navigation }) {
body: JSON.stringify({ uris: chunk })
});
}
console.log("PLAYLIST_CREATE_SUCCESS:", generatedPlaylistUrl);
} else {
console.error("No tracks found for genres:", seed_genres);
}
} else {
console.error("Spotify token missing, skipping playlist generation.");
Alert.alert('Aviso', 'Sessão Spotify não encontrada. A viagem será guardada sem playlist.');
}
} catch (playlistError) {
console.error("Error generating playlist:", playlistError);
Alert.alert('Erro Playlist', 'A viagem foi calculada, mas ocorreu um erro a criar a playlist.');
} catch (playlistError: any) {
console.warn("Expected failure generating playlist:", playlistError.message || playlistError);
Alert.alert('Erro Playlist', `A viagem foi calculada, mas a playlist falhou: ${playlistError.message?.substring(0, 50) || 'Erro Desconhecido'}`);
}
// G. Save to Supabase unconditionally if route is valid