Skip to main content

I am getting crazy while trying to fetch gear-equipment from Strava to Supabase (I am building an app). 

connection works fine, but then it doesn’t get the bikes. It doesn’t show any errors either. I have tried everything, but still never find the way. 

Is there anyone that has faced something like this before and can give me some advice?

Thank you in advance,

Borja

Hi ​@borjagascon, check this out. Do you have the right scopes configured? You need the scope "profile:read_all" for the bike and shoe list to appear.


Hi Emily, thanks for your help. I am still having same issue after changin it. Let me copy paste my Strava-Auth code, in case you want to help by looking at it. Thank you very much in advance for proposing some changes if you spot the error: 

 

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.7';

import { serve } from "https://deno.land/std@0.177.0/http/server.ts";

const corsHeaders = {

  'Access-Control-Allow-Origin': '*',

  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'

};

const SUPABASE_URL = Deno.env.get('SUPABASE_URL') || "https://tyqmfhtfvrffkaqttbcf.supabase.co";

const SUPABASE_ANON_KEY = Deno.env.get('SUPABASE_ANON_KEY') || "ey************************n************************************";

// Función de registro con timestamp para mejor seguimiento

function logEvent(message, data = {}) {

  const timestamp = new Date().toISOString();

  console.log(`e${timestamp}] STRAVA-AUTH: ${message}`, JSON.stringify(data));

}

serve(async (req)=>{

  // Para registrar cada llamada a la función

  const requestId = crypto.randomUUID();

  logEvent(`Función invocada (ID: ${requestId})`);

  // Handle CORS preflight request

  if (req.method === 'OPTIONS') {

    logEvent(`Solicitud CORS OPTIONS recibida (ID: ${requestId})`);

    return new Response('ok', {

      headers: corsHeaders

    });

  }

  try {

    // Parse request body

    const requestData = await req.json();

    const { code, user_id, redirect_uri } = requestData;

    logEvent(`Procesando solicitud de autenticación`, {

      request_id: requestId,

      code_present: Boolean(code),

      user_id: user_id || "FALTANTE",

      redirect_uri: redirect_uri || "FALTANTE (usando default)",

      scope: requestData.scope || "FALTANTE"

    });

    // HARDCODED ID Y SECRET - para evitar problemas con la encriptación

    // En un entorno real usaríamos los secrets correctamente

    const STRAVA_CLIENT_ID = "1*****";

    const STRAVA_CLIENT_SECRET = "a****************";

    // Verificar que tenemos las credenciales

    logEvent(`Verificando credenciales de Strava`, {

      clientIdPresente: Boolean(STRAVA_CLIENT_ID),

      secretPresente: Boolean(STRAVA_CLIENT_SECRET)

    });

    // Verificar los secrets del entorno (sin mostrar el contenido completo)

    const envClientId = Deno.env.get('STRAVA_CLIENT_ID');

    const envClientSecret = Deno.env.get('STRAVA_CLIENT_SECRET');

    logEvent(`Variables de entorno de Strava`, {

      STRAVA_CLIENT_ID_env_presente: Boolean(envClientId),

      STRAVA_CLIENT_SECRET_env_presente: Boolean(envClientSecret)

    });

    if (!code || !user_id) {

      logEvent(`Error: Faltan parámetros requeridos`, {

        code: Boolean(code),

        user_id: Boolean(user_id)

      });

      return new Response(JSON.stringify({

        error: 'Code y user_id son requeridos'

      }), {

        status: 400,

        headers: {

          ...corsHeaders,

          'Content-Type': 'application/json'

        }

      });

    }

    if (!STRAVA_CLIENT_ID || !STRAVA_CLIENT_SECRET) {

      logEvent(`Error: Faltan credenciales de API de Strava`, {

        hasClientId: Boolean(STRAVA_CLIENT_ID),

        hasClientSecret: Boolean(STRAVA_CLIENT_SECRET)

      });

      return new Response(JSON.stringify({

        error: 'Credenciales de API de Strava no configuradas'

      }), {

        status: 500,

        headers: {

          ...corsHeaders,

          'Content-Type': 'application/json'

        }

      });

    }

    logEvent(`Intercambiando código por token con API de Strava (ID: ${requestId})`);

    // Preparar el cuerpo de la solicitud para el token

    const tokenRequestBody = {

      client_id: STRAVA_CLIENT_ID,

      client_secret: STRAVA_CLIENT_SECRET,

      code: code,

      grant_type: 'authorization_code'

    };

    // Si se proporcionó un redirect_uri, incluirlo en la solicitud

    if (redirect_uri) {

      logEvent(`Usando redirect_uri proporcionado: ${redirect_uri}`);

      tokenRequestBody.redirect_uri = redirect_uri;

    }

    // Exchange code for tokens

    const tokenResponse = await fetch('https://www.strava.com/oauth/token', {

      method: 'POST',

      headers: {

        'Content-Type': 'application/json'

      },

      body: JSON.stringify(tokenRequestBody)

    });

    const tokenResponseText = await tokenResponse.text();

    let tokenData;

    try {

      tokenData = JSON.parse(tokenResponseText);

      logEvent(`Respuesta del token recibida`, {

        status: tokenResponse.status,

        token_presente: Boolean(tokenData?.access_token),

        athlete_presente: Boolean(tokenData?.athlete),

        error: tokenData?.error

      });

      // Log additional information about token scopes

      if (tokenData.scope) {

        logEvent(`Scopes autorizados`, {

          scopes: tokenData.scope

        });

        const hasProfileReadAll = tokenData.scope.includes('profile:read_all');

        logEvent(`Permiso profile:read_all autorizado: ${hasProfileReadAll ? 'SÍ' : 'NO'}`);

      }

    } catch (e) {

      logEvent(`Error al analizar la respuesta del token: ${e.message}`, {

        raw_response: tokenResponseText

      });

      return new Response(JSON.stringify({

        error: 'Falló al analizar la respuesta del token',

        status: tokenResponse.status

      }), {

        status: 500,

        headers: {

          ...corsHeaders,

          'Content-Type': 'application/json'

        }

      });

    }

    if (!tokenResponse.ok) {

      logEvent(`Error intercambiando código por token`, {

        error: tokenData.error || tokenData.message || 'Error desconocido',

        status: tokenResponse.status

      });

      return new Response(JSON.stringify({

        error: 'Falló al intercambiar código por token',

        details: tokenData.error || tokenData.message || 'Error desconocido de API de Strava'

      }), {

        status: 400,

        headers: {

          ...corsHeaders,

          'Content-Type': 'application/json'

        }

      });

    }

    logEvent(`Token recibido correctamente de Strava (ID: ${requestId})`);

    // Initialize Supabase client

    const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

    logEvent(`Comprobando/creando perfil para usuario: ${user_id}`);

    // Check if profiles table exists and if not, create it

    const { error: checkError, data: checkData } = await supabase.from('profiles').select('id').eq('id', user_id).limit(1);

    if (checkError) {

      logEvent(`Error comprobando tabla de perfiles: ${checkError.message}`);

      // Attempt to create profiles table if needed

      try {

        const { error: createProfileError } = await supabase.rpc('create_profile_if_not_exists', {

          user_id: user_id,

          user_email: 'unknown@example.com' // We don't have the email here, will be updated later

        });

        if (createProfileError) {

          logEvent(`Error creando perfil: ${createProfileError.message}`);

          throw createProfileError;

        }

      } catch (createError) {

        logEvent(`Excepción creando perfil: ${createError.message}`);

      }

    }

    logEvent(`Actualizando perfil con tokens de Strava`);

    // Update user profile with Strava tokens

    const { error: updateError } = await supabase.from('profiles').update({

      strava_connected: true,

      strava_access_token: tokenData.access_token,

      strava_refresh_token: tokenData.refresh_token,

      strava_token_expires_at: tokenData.expires_at,

      strava_athlete_id: tokenData.athlete?.id || null

    }).eq('id', user_id);

    if (updateError) {

      logEvent(`Error actualizando perfil: ${updateError.message}`);

      return new Response(JSON.stringify({

        error: 'Falló al actualizar perfil',

        details: updateError.message

      }), {

        status: 500,

        headers: {

          ...corsHeaders,

          'Content-Type': 'application/json'

        }

      });

    }

    // Fetch user bikes from Strava

    logEvent(`Obteniendo bicis de API de Strava (ID: ${requestId})`);

    let importedBikes = 0;

    try {

      const bikesResponse = await fetch('https://www.strava.com/api/v3/athlete', {

        headers: {

          'Authorization': `Bearer ${tokenData.access_token}`

        }

      });

      const athleteData = await bikesResponse.json();

      if (!bikesResponse.ok) {

        logEvent(`Error obteniendo datos de atleta: ${JSON.stringify(athleteData)}`);

      } else if (athleteData.bikes && athleteData.bikes.length > 0) {

        logEvent(`Encontradas ${athleteData.bikes.length} bicis en Strava`, {

          bikes: athleteData.bikes.map((bike)=>({

              id: bike.id,

              name: bike.name

            }))

        });

        importedBikes = athleteData.bikes.length;

        // Insert each bike into our database

        for (const bike of athleteData.bikes){

          const { data: existingBike, error: checkBikeError } = await supabase.from('bikes').select('id').eq('strava_id', bike.id).eq('user_id', user_id).limit(1);

          if (checkBikeError) {

            logEvent(`Error comprobando bici existente: ${checkBikeError.message}`);

            continue;

          }

          if (existingBike && existingBike.length > 0) {

            // Update existing bike

            const { error: updateBikeError } = await supabase.from('bikes').update({

              name: bike.name,

              total_distance: bike.distance || 0,

              updated_at: new Date().toISOString()

            }).eq('strava_id', bike.id).eq('user_id', user_id);

            if (updateBikeError) {

              logEvent(`Error actualizando bici desde Strava: ${updateBikeError.message}`);

            } else {

              logEvent(`Bici actualizada desde Strava: ${bike.name}`);

            }

          } else {

            // Insert new bike

            const { error: insertBikeError } = await supabase.from('bikes').insert({

              name: bike.name,

              type: bike.type || 'Road',

              strava_id: bike.id,

              user_id: user_id,

              total_distance: bike.distance || 0,

              image: 'https://images.unsplash.com/photo-1571068316344-75bc76f77890?auto=format&fit=crop&w=900&q=60'

            });

            if (insertBikeError) {

              logEvent(`Error insertando bici desde Strava: ${insertBikeError.message}`);

            } else {

              logEvent(`Bici importada desde Strava: ${bike.name}`);

            }

          }

        }

      } else {

        logEvent(`No se encontraron bicis en cuenta de Strava. Scopes autorizados: ${tokenData.scope || "No informado"}`);

      }

    } catch (bikesError) {

      logEvent(`Excepción obteniendo bicis de Strava: ${bikesError.message}`);

    }

    logEvent(`Proceso de autenticación con Strava completado (ID: ${requestId})`);

    // Return successful response

    return new Response(JSON.stringify({

      success: true,

      message: 'Cuenta de Strava conectada correctamente y bicis importadas',

      importedBikes: importedBikes,

      requestId: requestId

    }), {

      headers: {

        ...corsHeaders,

        'Content-Type': 'application/json'

      }

    });

  } catch (error) {

    logEvent(`Error general procesando solicitud: ${error.message || "Error desconocido"}`);

    return new Response(JSON.stringify({

      error: 'Error interno del servidor',

      details: error.message

    }), {

      status: 500,

      headers: {

        ...corsHeaders,

        'Content-Type': 'application/json'

      }

    });

  }

});

 


Hi ​@borjagascon, please reach out to developers@strava.com.


Reply