mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-03-11 17:47:47 -05:00
The used `serve` function from the `std/server` module is deprecated and must be replaced with `Deno.serve`. This was done within this commit.
197 lines
5.9 KiB
TypeScript
197 lines
5.9 KiB
TypeScript
import { createClient, SupabaseClient, User } from '@supabase/supabase-js';
|
|
|
|
import { corsHeaders } from '../_shared/utils/cors.ts';
|
|
import { log } from '../_shared/utils/log.ts';
|
|
import { IProfile } from '../_shared/models/profile.ts';
|
|
import {
|
|
FEEDDECK_SUPABASE_ANON_KEY,
|
|
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
|
FEEDDECK_SUPABASE_URL,
|
|
} from '../_shared/utils/constants.ts';
|
|
import { githubAddAccount, githubDeleteAccount } from './github.ts';
|
|
|
|
/**
|
|
* `getProfile` returns the users profile. The user profile contains information
|
|
* about the users subscription and the connected accounts.
|
|
*
|
|
* ATTENTION: We should never return the users account token. Instead we should
|
|
* return a boolean if the user has connected an account or not.
|
|
*/
|
|
const getProfile = async (
|
|
supabaseClient: SupabaseClient,
|
|
user: User,
|
|
): Promise<Response> => {
|
|
const { data: profile, error: profileError } = await supabaseClient
|
|
.from(
|
|
'profiles',
|
|
)
|
|
.select('*').eq(
|
|
'id',
|
|
user.id,
|
|
);
|
|
if (profileError || profile?.length !== 1) {
|
|
log('error', 'Failed to get user profile', {
|
|
'user': user,
|
|
'error': profileError,
|
|
});
|
|
return new Response(
|
|
JSON.stringify({ error: 'Failed to get delete user' }),
|
|
{
|
|
headers: {
|
|
...corsHeaders,
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
},
|
|
status: 500,
|
|
},
|
|
);
|
|
}
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
'id': (profile[0] as IProfile).id,
|
|
'tier': (profile[0] as IProfile).tier,
|
|
'subscriptionProvider': (profile[0] as IProfile).subscriptionProvider,
|
|
'accountGithub': (profile[0] as IProfile).accountGithub?.token
|
|
? true
|
|
: false,
|
|
'createdAt': (profile[0] as IProfile).createdAt,
|
|
'updatedAt': (profile[0] as IProfile).updatedAt,
|
|
}),
|
|
{
|
|
headers: {
|
|
...corsHeaders,
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
},
|
|
status: 200,
|
|
},
|
|
);
|
|
};
|
|
|
|
/**
|
|
* The `profile-v2` function is the entry point for all requests which are
|
|
* related to the users profile. This means that the function can be used to get
|
|
* the users profile and to handle the users connected accounts.
|
|
*/
|
|
Deno.serve(async (req) => {
|
|
const { url, method } = req;
|
|
|
|
/**
|
|
* We need to handle the preflight request for CORS as it is described in the
|
|
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
|
*/
|
|
if (method === 'OPTIONS') {
|
|
return new Response('ok', { headers: corsHeaders });
|
|
}
|
|
|
|
try {
|
|
/**
|
|
* Create a new Supabase client with the anonymous key and the authorization
|
|
* header from the request. This allows us to access the database as the
|
|
* user that is currently signed in.
|
|
*/
|
|
const userSupabaseClient = createClient(
|
|
FEEDDECK_SUPABASE_URL,
|
|
FEEDDECK_SUPABASE_ANON_KEY,
|
|
{
|
|
global: {
|
|
headers: { Authorization: req.headers.get('Authorization')! },
|
|
},
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
},
|
|
);
|
|
|
|
/**
|
|
* Get the user from the request. If there is no user, we return an error.
|
|
*/
|
|
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
|
if (!user) {
|
|
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
headers: {
|
|
...corsHeaders,
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
},
|
|
status: 401,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new admin client for Supabase, which is used in the following
|
|
* steps to access the database. This client is required because the user
|
|
* client does not have the permissions to get a users profile.
|
|
*/
|
|
const adminSupabaseClient = createClient(
|
|
FEEDDECK_SUPABASE_URL,
|
|
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
|
{
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
},
|
|
);
|
|
|
|
/**
|
|
* We use the `URLPattern` library to match the request url to the different
|
|
* endpoints of the function. This allows us to use a single function for
|
|
* multiple endpoints. If the request method is `POST` we also parse the
|
|
* request body.
|
|
*/
|
|
const urlPattern = new URLPattern({ pathname: '/profile-v2/:id' });
|
|
const matchingPath = urlPattern.exec(url);
|
|
const id = matchingPath ? matchingPath.pathname.groups.id : null;
|
|
|
|
let data = null;
|
|
if (method === 'POST') {
|
|
data = await req.json();
|
|
}
|
|
|
|
log('debug', 'Request data', {
|
|
user: user,
|
|
method: method,
|
|
id: id,
|
|
data: data ? true : false,
|
|
});
|
|
|
|
/**
|
|
* Now we can check the request method and the request id to determine which
|
|
* action we need to execute.
|
|
*/
|
|
switch (true) {
|
|
case method === 'GET' && id === 'getProfile':
|
|
return await getProfile(adminSupabaseClient, user);
|
|
case method === 'POST' && id === 'githubAddAccount':
|
|
return await githubAddAccount(adminSupabaseClient, user, data);
|
|
case method === 'DELETE' && id === 'githubDeleteAccount':
|
|
return await githubDeleteAccount(adminSupabaseClient, user);
|
|
default:
|
|
/**
|
|
* If the request doesn't match any of the above conditions, because it
|
|
* doesn't match the request method and id we return a `400 Bad Request`
|
|
* error.
|
|
*/
|
|
return new Response(JSON.stringify({ error: 'Bad Request' }), {
|
|
headers: {
|
|
...corsHeaders,
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
},
|
|
status: 400,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
|
return new Response(
|
|
JSON.stringify({ error: 'An unexpected error occured' }),
|
|
{
|
|
headers: {
|
|
...corsHeaders,
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
},
|
|
status: 400,
|
|
},
|
|
);
|
|
}
|
|
});
|