반응형

 

Supabase를 사용하다 보면 Row Level Security(RLS)를 켜놓은 상태에서 UPDATE 쿼리를 실행했는데도 다음과 같은 에러를 만날 수 있습니다:

 

ERROR:  permission denied for table user_profiles

 

“분명히 Policy도 설정했는데 왜 안 되지?” 싶은 분들을 위해, 이 글에서는 이 에러의 근본 원인과 해결 방법을 예제로 함께 정리합니다.

 

문제 상황

 

시도한 쿼리

update public.user_profiles
set name = '테스트'
where id = 'e0804eda-xxxxx-xxxxx-xxxxxx-xxxx';

 

 

발생한 에러 로그

ERROR:  permission denied for table user_profiles

 

이 에러는 단순히 RLS 정책이 없어서가 아닙니다. 오히려 RLS는 잘 설정되어 있었지만, PostgreSQL 권한(GRANT) 자체가 누락되어 있어서 발생하는 것입니다.

 


원인 분석: Supabase의 보안 구조

 

Supabase는 다음 두 단계를 거쳐 요청을 허용합니다:

 

1. PostgreSQL 권한 (GRANT)

역할(Role)이 테이블에 접근할 수 있는지

예: SELECT, INSERT, UPDATE, DELETE 권한을 가졌는가?

 

2. Row Level Security (RLS) 정책

해당 row에 접근 가능한 조건을 만족하는가?

예: 로그인한 사용자의 auth.uid()와 row의 id가 일치하는가?

 

즉, GRANT + RLS 둘 다 통과해야 UPDATE가 작동합니다.

 

 

 


 해결 방법

 

테이블 권한 부여

GRANT UPDATE ON public.user_profiles TO authenticated;

 

필요에 따라 SELECT도 함께 부여합니다:

GRANT SELECT ON public.user_profiles TO authenticated;

 

 

 


RLS 활성화

ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;

 

RLS 정책 작성

 

로그인한 사용자가 자신의 프로필만 수정 가능하게 만들기:

CREATE POLICY "Users can update their own profile"
ON public.user_profiles
FOR UPDATE
TO authenticated
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);

 

USING: 해당 row를 조회할 수 있는 조건

WITH CHECK: 해당 row를 수정할 수 있는 조건

 

 


최종 정리

 

설정항목 설명 예시
GRANT 테이블에 대한 접근 권한 부여 GRANT UPDATE ON ... TO authenticated
RLS ENABLE Row-Level Security 사용 설정 ALTER TABLE ... ENABLE ROW LEVEL SECURITY
POLICY 행(row) 단위 조건 설정 USING, WITH CHECK 조건 작성

 

 


결과: 정상 동작

 

위 설정이 완료되면, Supabase Auth를 통해 로그인한 사용자가 자신의 id를 가진 row에 대해 다음과 같은 쿼리를 정상적으로 실행할 수 있습니다.

update public.user_profiles
set name = '테스트'
where id = '로그인한_사용자의_id';

 

 

 

마무리

 

처음엔 "RLS 정책만 설정하면 된다"고 생각하기 쉽지만, Supabase는 PostgreSQL 위에서 작동하는 만큼 기본 권한 설정도 꼭 필요합니다. 앞으로는 permission denied 에러가 나면 다음 두 가지를 먼저 체크해보세요:

1. GRANT 권한이 있는가?

2. RLS Policy가 올바른가?

 

이 구조만 이해하면, Supabase의 보안 모델을 더 자신 있게 다룰 수 있을 거예요.

 

 

# 해피코딩!

반응형

 

RLS란?

Supabase 본격적으로 사용하다보면, RLS를 접하게 되는데요. 이 RLS 개념이 상당히 편리한 기능입니다. 

Row Level Security , 즉 DB 의 특정 필드의 값을 채크하여 query 시 Row 단위로 제외 시킬 수 있는 방식입니다.

 

id user_id created_at title content team_id
1 mika 11 안녕하세요. 개발 진행 합니다. 1
2 other 12 개발 의뢰 합니다 이런거 개발해 주세요. 2

말로 표현하려니 좀 어려웠는데요.

 

위와 같은 table이 있다고 한다면, user_id 가 mika. 인 데이터만 가져오려고 한다면,

query시 user_id 가 mika 인 조건을 추가해서 client에서 호출하면 됩니다.

그러나, 만약 클라이언트 개발자가 이 과정에서 특정 경우에 조건을 추가하지 않았다면, 다른 사람의 정보들까지 노출되버리겠죠.

이를 시스템 상으로 보완할수 있는 하나의 방법이 RLS 입니다.

RLS에 기본적인 예제로  acceess token에서 auth 정보를 가져와서 접근 권한을 설정할 수 있기 때문에, 로그인한 사용자에게 맞춰서 데이터를 필터링 할 수 있게 됩니다.

 

 

문제는 뷰 (View)

View 는 기본적으로 RLS가 적용 안됩니다.

VIEW로 쿼리할때 데이터가 필터링 안되는지 몇시간 고민하다 찾게 된 사항입니다.

 

만약 View를 만들어서 사용하려면, View를 만들때 RLS가 적용되도록 해야합니다.

WITH ( security_invoker = TRUE )

 

view 생성시 이렇게 security_invoker를 설정해주면 RLS가 적용됩니다.

 

create or replace view
[view이름] WITH ( security_invoker = TRUE ) as
select * form [talbe이름]

 

 

아래와 같은 형식이 되겠죠..

 
create or replace view
public.myview WITH ( security_invoker = TRUE ) as
select
mydata.*
user_profiles.name as user_name,
user_profiles.email as user_email,
org.name as org_name,
sites.name as site_name

from
mydata
left join user_profiles on mydata.user_id = user_profiles.id
left join org on mydata.org_id = org.id
...

 

 

 


ALTER를 이용하여 기존의 View에 적용하는 방법.

View는 RLS 를 설정 할 수 없습니다.  이걸로 좌절하고 주말을 보냈는데, 찾다보니 방법이 있더군요.

ALTER VIEW my_view SET (security_invoker = on);

 

이렇게 설정을 하면 my_view 에서 query하는 table에 RLS가 적용 됩니다.

다만 이렇게 했을때 문제는 my_view 가 접근하는 모든 table에 RLS가 잘 적용 되어있어야 동작 한다는 것입니다.

 

a table은 접근 권한이 있고 b table은 접근 권한이 없으면 퍼미션 에러가 발생합니다.

View 뿐만 아니라, Table 에서도 마찬가지 입니다.

(이건 시스템을 어떻게 설계하느냐의 문제이기 때문에 이런 부분 고려해서 설계하면 좋겠네요)

 

 

RLS를 활용하여 내 서비스의 기능을 발전 시켜 나가보세요~

 

 

#해피코딩!!

반응형

 

서비스를 개발할때, 필요한 기능 중에 사용자별로 접근가능한 데이터를 제한 하거나, 기능을 제한 하는 것이 필요할 경우가 있습니다.

 

이럴 경우 보통, 사용자가 로그인 하고 나서 사용자의 권한 정보를 서버에 쿼리하고 사용자의 권한에 따라 기능을 제한 하는 형태로 진행하도록 할 수 있죠.

 

보통 사용자 정보에 롤(역할, role, permission) 을 두고 처리하는 경우가 많습니다. 

어떻게 그럼 서비스에서는 사용자의 role을 확인할 까요?

 

대략적으로 생각해볼 수 있는 방법이 2가지 정도 인것 같네요.

1. 사용자가 db나 api를 이용할때 해당 api, 또는 db query에서 사용자 정보 table을 참조하여 권한이 있는 사용자인지 확인하는 방법

2. 사용자의 access token에 권한 정보를 추가하여,  db, api에서 token 정보를 decoding 하여 권한 정보를 얻는 방법

 

1번은 매번 DB access 마다 확인을 해야 하는 사항이라 서버에 부하와 성능에 영향을 미치게 됩니다.

2번은 성능에 대한 오버해드가 거의 없습니다. 하지만 사용자의 정보가 변경되었을때 token에 바로 반영되는 것이 아니기 때문에 이를 위한 처리가 필요할 것입니다. (아니면 다음 로그인 까지 무시 하거나.)

 

오늘 정리할 내용은 성능 이슈도 이슈지이지만, access tokend 다루는 것에 중심을 두고 싶어서 2번에 대한 내용을 정리했습니다.

 

Supabase 에서 Auth Hooking 을 통해서 token 에 추가정보를 입력하기

 

Supabase 에 Beta로 현재 Auth Hooking을 지원하고 있고, postgres 에서는 RLS (Row Level Security)를 지원합니다.

 

이를 활용하여 서버쪽에서 auth정보를 이용해서 접근 권한을 설정할 수 있는 방법이 생겼습니다.

 

Access Token flow

 

이런 처리를 통해 기대한 Access Token의 전체적인 모습은 다음과 같습니다.

 

 

Access Token

 

 

Token을 디코딩 결과

{
  "aal": "aal1",
  "access": "user",
  "amr": [
    {
      "method": "password",
      "timestamp": 1738558038
    }
  ],
  "app_metadata": {
    "provider": "email",
    "providers": [
      "email"
    ]
  },
  "aud": "authenticated",
  "email": "xxxxx@xxxx.xxx",
  "exp": 1738633992,
  "iat": 1738630392,
  "is_anonymous": false,
  "iss": "http://127.0.0.1:54321/auth/v1",
  "org_depart_id": null,
  "org_id": "1",
  "phone": "",
  "role": "authenticated",
  "session_id": "b9313b3b-bd86-44ea-b3d7-98c7ca6b9447",
  "sub": "e0804eda-a117-4c51-9f50-66804c889b41",
  "user_metadata": {
    "email": "xxxxx@xxxx.xxx",
    "email_verified": false,
    "phone_verified": false,
    "sub": "e0804eda-a117-4c51-9f50-66804c889b41",
    "username": "name...."
  }
}

 

supabase의 token에  "access", "org_depart_id",  "org_id" 를 추가한 형태입니다.

 

 

그럼 절차를 확인 해보도록 하시죠.

 

DB의 policies 를 살펴보면, 사용자에 따라 접근권한을 설정할때, auth.uid() 같은 함수를 이용해서 현 사용자만 접근 가능하도록 할 수 있죠.

 

그런데 만약 같은 팀원만 볼수 있게 하고 싶다면, 음.. 일단 RLS에서 team에 대한 table join 해서 팀에 소속되어있는지 확인해야 합니다.

(예제에도 그렇게 되어있죠)

그런데 table join이 들어간 코드와 아닌코드의 성능 차이기 약 99% 차이가 난다고 예제에 나와있어요.

table join이 빠지면 99% 성능 향상 ..

 

그런데  만약 auth token에 추가정보를 담고, RLS에서 auth token에 있는 정보를 활용해서 필터링 할 수 있다고 한다면, (지금 시도해보고 있음), ㅅa

table join없이 처리가 가능합니다. 즉 향상된 성능으로 처리할 수 있다는 것이죠.

 

0. custom_jwt_token_hook 추가하기

  • supabase 에서 auth hook을 추가합니다.

Authentication ->  Hooks 를 선택  -> Add Hook

 

이런 화면이 나오죠.

- DB는 postgres 선택하고, schema를 선택해야 하는데 이때 public으로 해도 되지만, 저는 따로 private schema를 만들어두어서 그걸 이용했습니다. (public은 expose되어있어서 불안한 부분이 있거든요)

- 그리고 function을 선택해야 합니다. (지금 구현된 것이 없기 때문에 만들어야 겠죠).

 

  • custom_jwt_token_hook 구현 
  • private schema 를 만들어서 추가 (public schema는 노출 되어있기 때문에 피하는 것이 좋다. )
  • javascript 가 plpgsql보다 익숙하여 plv8 import하여 javacript로  작성하였습니다.
//함수 명: custom_jwt_token_hook

var org_id, access, org_depart_id;

//  -- Fetch the current user's level from the profiles table
  var result = plv8.execute("select id, org_id, access, org_depart_id from public.user_profiles where id = $1", [event.user_id]);
  if (result.length > 0) {
    org_id = result[0].org_id;
    org_depart_id = result[0].org_depart_id;
    access = result[0].access;

  } else {
    org_id = 0;
    org_depart_id = 0;
    access = '';
  }

//  -- Check if 'claims' exists in the event object; if not, initialize it
  if (!event.claims) {
    event.claims = {};
  }
//  -- Update the level in the claims
  event.claims.org_id = org_id;
  event.claims.org_depart_id = org_depart_id;
  event.claims.access = access;


  return event;

 

- user_profiles table을 만들고 이 테이블에 org_id, org_depart_id를 담고 있다.

 

SQL Editor에서 작성하는것이 더 편리합니다. 

create or replace function custom_jwt_token_hook(event jsonb)
returns jsonb
language plv8
as $$

  var org_id, role, org_depart_id;

//  -- Fetch the current user's level from the profiles table
  var result = plv8.execute("select org_id, role, org_depart_id from public.user_profiles where user_id = $1", [event.user_id]);
  if (result.length > 0) {
    org_id = result[0].org_id;
    org_depart_id = result[0].org_depart_id;
    role = result[0].role;

  } else {
    org_id = 0;
    org_depart_id = 0;
    role = '';
  }

//  -- Check if 'claims' exists in the event object; if not, initialize it
  if (!event.claims) {
    event.claims = {};
  }

//  -- Update the level in the claims
  event.claims.org_id = org_id;
  event.claims.org_depart_id = org_depart_id;
  event.claims.role = role;

  return event;
$$;

grant all
  on table public.user_profiles
  to supabase_auth_admin;

revoke all
  on table public.user_profiles
  from authenticated, anon, public;

 

 

1. custom jwt를 통해서 token에 추가정보가 들어간것 확인했습니다.

=> 정상적으로 설정이 완료 되면 token에 정보가 들어간 것을 볼 수 있다.

주의 점은 user_profiles에 접근 권한이 설정 되어있어야 한다는 것입니다.

supabase 의 auth 로직은 supabase_auth_admin role로 동작하기 때문에, user_profiles 의 select에  authenticated, supabase_auth_admin role이 필요합니다.

 

2. 서버에서 RLS 로직에서 추가정보를 꺼내올 수 있는지 확인 필요.

=> 확인 중인 사항 (확인 완료!)

 

아래처럼 org_id 값을 비교하고 싶을때, org_id가 int8 (숫자)인경우라면, 숫자를 문자로 변경해서 비교 해야함.

ALTER POLICY "Enable read access for same org"
ON "public"."devices"
TO authenticated
USING (
  (auth.jwt() ->> 'org_id')::text = org_id::text
);

 

 

 

3.테스트를 위해서 local pc에서 custom jwt 를 테스트 해 볼 수 있는 환경 구축..

가이드에 이렇게 적혀있음

# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
# [auth.hook.custom_access_token]
# enabled = true
# uri = "pg-functions://<database>/<schema>/<hook_name>"

 

 

 

config.toml 에 다음 과 같이 내용 추가.

[auth.hook.custom_access_token]
enabled = true
uri = "pg-functions://postgres/private/custom_jwt_token_hook"

 

=> 정상 동작 하는것 확인

 

 

이것을 시도 해보겠습니다. !!

 

 

 


!해피 코딩

 

 

[참고사항]

코드를 작성하고 돌려볼때 user_profiles의 권한 관련된 문제가 발생할 수도 있습니다.

저는 migration하다 문제가 생겨서 골치 아팠었는데요.

1. private/custom_jwt_token_hook을 못찾는 문제가 있었습니다.

환경 문제일 수도 있고, 제가 이것 저것 건드려서 생긴 문제일 수도 있습니다.

ALTER FUNCTION private.custom_jwt_token_hook SECURITY DEFINER;
// 원래 invoker로 설정 되어있어도 동작 했던것 같은데, 
// SECURITY DEFINER 로 설정을 바꿨습니다.

GRANT EXECUTE ON FUNCTION private.custom_jwt_token_hook TO authenticator;
//권한 설정 auth logic이 authenticator로 동작하는데 함수에 대해서 도 추가로 설정
// authenticator 역할이 실행 권한을 설정

 

2. user_profiles 접근 권한 문제 발생 했습니다.

RLS 설정을 하여 문제가 없어보이는 상황(제 짤은 생각으로는.. 그렇습니다.^^;;)이었으나 이상하게 접근 권한에 문제가 계속 생겨서 SQL Editor에서 다음과 같이 권한을 줬습니다. 그 이후 기대했던대로 동작 했습니다.

GRANT SELECT ON TABLE public.user_profiles TO authenticated;

 


3. View 문제

View는 RLS 를 설정 할 수 없습니다.  이걸로 좌절하고 주말을 보냈는데, 찾다보니 방법이 있더군요.

ALTER VIEW my_view SET (security_invoker = on);

 

이렇게 설정을 하면 my_view 에서 query하는 table에 RLS가 적용 됩니다.

다만 이렇게 했을때 문제는 my_view 가 접근하는 모든 table에 RLS가 잘 적용 되어있어야 동작 한다는 것입니다.

a table은 접근 권한이 있고 b table은 접근 권한이 없으면 퍼미션 에러가 발생합니다.

(이건 시스템을 어떻게 설계하느냐의 문제이기 때문에 이런 부분 고려해서 설계하면 좋겠네요)

 

 

[참고] Supabase Custom Access Token Hook

https://supabase.com/docs/guides/auth/auth-hooks/custom-access-token-hook?queryGroups=language&language=sql

 

Custom Access Token Hook | Supabase Docs

Customize the access token issued by Supabase Auth

supabase.com

 

 

반응형

 

Supabase Authentication을 hooking 해서 사용해야 하는 경우가 있습니다.

이때 sql query 함수를 개발하고 테스트를 보통은 cli를 통해서 진행할텐데, cli에 extension이 활성화 안되어있는 경우 검토가 힘들어지겠죠.

 

 

저도 역시 plv8이 활성화 안되어있어서 , 문제였습니다.

 

해결책은 다음 과 같이 찾아서 정리해봤습니다.

 

1. supabase에 기본적으로 extension으로 plv8이 있는 거 같음.

2. 그래서 이를 활성화 시키는 방법을 찾아서 실행.

3. 활성화 된것 확인

 

extension 활성화

CREATE EXTENSION plv8;

 

 

설치 확인

SELECT * FROM pg_available_extensions WHERE name = 'plv8';
 

 

코드 작성...

create or replace function custom_jwt_token_hook(event jsonb)
returns jsonb
language plv8
as $$

  var org_id, role, org_depart_id;

  -- Fetch the current user's level from the profiles table
  var result = plv8.execute("select org_id, role, org_depart_id from public.user_profiles where user_id = $1", [event.user_id]);
  if (result.length > 0) {
    org_id = result[0].org_id;
    org_depart_id = result[0].org_depart_id;
    role = result[0].role;

  } else {
    org_id = 0;
    org_depart_id = 0;
    role = '';
  }

  -- Check if 'claims' exists in the event object; if not, initialize it
  if (!event.claims) {
    event.claims = {};
  }

  -- Update the level in the claims
  event.claims.org_id = org_id;
  event.claims.org_depart_id = org_depart_id;
  event.claims.role = role;

  return event;
$$;

grant all
  on table public.user_profiles
  to supabase_auth_admin;

revoke all
  on table public.user_profiles
  from authenticated, anon, public;
반응형

 

Edge Function 은 Supabase 기반의 serverless api  솔루션입니다.

AWS Lambda 나 Google Cloud Function 과 같은 서비스라고 보시면 됩니다.

 

 

1. 사용 환경 만들기

EdgeFunction 을 어떻게 개발하지? 코드를 작성해서 클라우드에 올리나? 그럼 코드는 어떻게 확인하지.. 와 같은 의문이 들건데요.

간단히 결론부터 말하면 supabase 에서 cli(command line interface) 기반으로 Edge Function 을 개발할 수 있는 환경을 제공합니다.

이를 이용해서 로컬에서 개발하고 테스트 해볼 수 있습니다.

 

EdgeFunction을 작성하기 위해서는 PC에 개발 환경을 먼저 구축 해야 겠죠?

아래 가이드를 살펴보면서 작성하시면 좋겠습니다.

 

https://supabase.com/docs/guides/local-development?queryGroups=package-manager&package-manager=npm

 

Local Development & CLI | Supabase Docs

Learn how to develop locally and use the Supabase CLI

supabase.com

 

CLI install 하고 실행하기

1. install
npm install supabase --save-dev
2. init
npx supabase init
3. start
npx supabase start

//brew 에서는 
1. install
brew install supabase/tap/supabase
2. init
supabase init
3. start
supabase start

 

[결과 화면]
         API URL: http://127.0.0.1:54321
     GraphQL URL: http://127.0.0.1:54321/graphql/v1
  S3 Storage URL: http://127.0.0.1:54321/storage/v1/s3
          DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
      Studio URL: http://127.0.0.1:54323
    Inbucket URL: http://127.0.0.1:54324
      JWT secret: super-secret-jwt-token-with-at-least-32-characters-long
        anon key: ----dfasdfasdf----------
service_role key: -----asdfasdxx---------
   S3 Access Key: ----asdf90as8d0f98asdf--------
   S3 Secret Key: -----234hjk35h2k34j5h--------
       S3 Region: local

 

 

 

 

 

 

2. CLI와 project 연결하기

npx supabase link --project-ref [project name]

 

1. npx supabase login

 첫 로그인을 시도하려고 하면 아래와 같은 에러가 발생합니다.

~/supabase$ npx supabse login
npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/supabse - Not found
npm ERR! 404 
npm ERR! 404  'supabse@*' is not in this registry.
npm ERR! 404 
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/netplant/.npm/_logs/2024-12-16T07_31_12_169Z-debug-0.log
netplant@netplant01:~/supabase$ npx supabase login
Hello from Supabase! Press Enter to open browser and login automatically.

 

엔터를 치면, 

브라우저가 뜨고  supabse 에 로그인 화면이 나타납니다.

로그인을 합니다.

 

2. 로그인 하고나면, 

 

supabase verify 코드가 보이게 됩니다.

 

3.이 코드를 가지고 terminal에 입력합니다.

- Enter your verification code: f16d4f38 

 

 

3.. 함수 개발해보기

함수를 하나 만들어보자.

 

Edge function을 개발하기 위해서는 Cli를 이용해서 Edge Function을 만들어 줍니다.

supabase functions new my-function --no-verify-jwt
또는
npx supabase functions new my-function --no-verify-jwt

테스트를 위한 것이니 --no-verify-jwt 를 옵션으로 입력했습니다.

그러면 간단한 샘플코드와 함께 함수 폴더가 만들어 집니다.

supabase 밑에 functions 라는 폴더가 생기고, 그 아래 my-function이라는 폴더가 만들어 지죠.

 

폴더 위치 : supabase/functions/my-function

생성된 예제 코드

import "jsr:@supabase/functions-js/edge-runtime.d.ts"

console.log("Hello from Functions!")

Deno.serve(async (req) => {
  const { name } = await req.json()
  const data = {
    message: `Hello ${name}!`,
  }

  return new Response(
    JSON.stringify(data),
    { headers: { "Content-Type": "application/json" } },
  )
})



/* To invoke locally:

  1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start)
  2. Make an HTTP request:

  curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/my-function' \
    --header 'Authorization: Bearer -sdjfklsdfjskldf-sdlfkasm23324l234dsdfk' \
    --header 'Content-Type: application/json' \
    --data '{"name":"Functions"}'

*/

 

함수에 대한 설정은 config.toml 파일에서 설정 할 수도 있습니다.

 

 

이렇게 코드가 만들어지고 나면 그다음은 함수를 테스트 해봐야 하는데요.

테스트를 위해서 함수를 제공하는 서비스를 실행합니다.

 

supabase functions serve my-function --no-verify-jwt

또는 

npx supabase functions serve my-function --no-verify-jwt

 

그리고 나서 함수를 테스트 하기 위해서 샘플 코드에 주석으로 적혀있는 curl 명령을 실행해봅니다.

  curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/my-function' \
    --header 'Authorization: Bearer -sdjfklsdfjskldf-sdlfkasm23324l234dsdfk' \
    --header 'Content-Type: application/json' \
    --data '{"name":"Functions"}'

 

이렇게 해서 결과가 나오면 api 를 개발할 수 있는 환경이 완성 된것입니다.

 

5. Edge Function 을 서버에 올리기

함수 개발을 끝내고 나면, 서버에 올리는 일이 남았습니다.

//전체 함수를 서버에 올리기
supabase functions deploy

// 한 함수만 올리기
supabase functions deploy my-function

//JWT 토큰 없이 동작할수 있도록 올리기
supabase functions deploy hello-world --no-verify-jwt

 

이렇게 하면 새로 만든 함수가 supabase 에 등록이 됩니다.

 

 

이제 부터는 실제로 개발할때 유용한 몇가지 팁들을 같이 적어볼까 합니다.

 

5. 팁

 

1. 환경 변수 또는 시크릿(Secret) 다루기 

[환경 변수 파일 .env]

일명 .env 또는 dotenv 라고 부르는 환경 변수 설정을 하는 방법입니다.

api key 또는 token 등 서비스를 구동할때 필요한 인증 키나 값들은 보통 소스코드에서 분리해서 사용하죠.

이럴때 일반적으로 .env를 활용하게 됩니다.

//env 사용하기
Deno.env.get('MY_SECRET_NAME')

그럼 어떻게 확인해 볼 수 있느냐?

supabase functions 서비스를 실행할때 env 파일을 설정해서 테스트 해볼 수 있습니다.

supabase functions serve --env-file ./supabase/.env

 

[시크릿 키 Secret]

Supabase 서버에서는 .env 파일을 그대로 사용할수 없고 ,supabase 에는 secret key를 관리하는 page에서 관리되는 키를 사용해야 합니다.

supabase site -> Project Settings -> Edge Functions -> Edge Function Secrets Management

가 바로 키를 관리하는 곳입니다.

 

CLI에 .env에 등록된 키들을 올리는 기능이 있으니 이를 활용하면 쉽게 올리고 테스트 가능하겠죠.

#.env에 있는 모든 키를 올릴때
supabase secrets set --env-file ./supabase/.env

#특정 키만 올일때
supabase secrets set MY_NAME=Chewbacca

 

 

2. 서버의 db를 로컬에 구축 하기(옵션 - 건너 뛰어도 됨.)

되도록이면 로컬의 환경이 서버의 환경과 동일(또는 유사)한것이 개발 및 테스트시 예외상황을 줄일 수 있습니다.

그래서 서버의 내용을 로컬에 구축하는 작업을 진행해보겠습니다.

1. 서버의 데이터를 모두 로컬에 복사해오면 좋겠지만  오버해드(서버의 데이터 내용이 많으면 많을 수록 문제가 되겠죠)가 큽니다.

그래서 서버의 구조, 즉 db schema 를 로컬에 동일하게 구축하는 것입니다.

2. 로컬에는 기본적인 테스트 가능한 데이터만 입력합니다.

서버의 일부 데이터만 입력하는 방법입니다.

 

 

 

#해피코딩

반응형

 

#Firebase 를 대처할만한 편리하고 저렴하게 사용할 수 있는 backend solution인 supabase 소개는 따로.!

 

Supabase에서 Database를 생성하고 나서, database 를 사용하는 방법은 다음과 같습니다.

 

supabase guide 를 보면 쉽게 따라하실수 있습니다.

https://supabase.com/docs/reference/dart/upgrade-guide?queryGroups=version&version=2.0x

 

Flutter: Upgrade guide | Supabase Docs

Initialization does not await for session refresh In v1, Supabase.initialize() would await for the session to be refreshed before returning. This caused delays in the app's launch time, especially when the app is opened in a poor network environment. In v2

supabase.com

 

 

 

지금 하고 싶은 이야기는 이미 supabase를 사용하고 계시는 분들에게 유용한 기능인데요.

 

# database 컬럼으로 json 활용

database를 만들어 사용하다 보면, 데이터중에  json 으로 데이터를 넣어야 하는 경우가 있습니다.

 

 

그런데, json 으로 데이터 컬럼을 추가 할경우, 쿼리 할때 데이터를 가져온다음에 name 이나 type을 비교해야 하는 상당히 불편한 부분이 생깁니다.

다행히도 supabase의 eq() 함수는 json data 내부의 필드값을 접근 할 수 있는 기능을 제공합니다.

https://supabase.com/docs/reference/dart/using-filters

final data = await supabase
  .from('test')
  .select()
  .eq('data->type', 'hello');

 

 

 

# eq 의 -> 가 에러가 발생

그런데 안타깝게도 아래와 같은 에러가 발생 할 수 있습니다.

 

PostgrestException(message: invalid input syntax for type json, code: 22P02, details: The input string ended unexpectedly., hint: null)

 

 

아 !,  -> 기능으로 json 내부 접근해서 쿼리 할수 있을 줄 알았는데 동작을 안하네??? 이러고 좌절 하실수 있는데요.

걱정하지 마세요 !

 

제가 찾아본 자료에서는 json 내부에 접근하는 접근자  몇가지 있는데요.

가이드에 나와있는 접근자로는 오류가 발생하고 다른 접근자를 사용하면 됩니다.

가이드에 적힌 '->' 느ㄴ son형태의 값을 넘기는 것이고, '->>'를 사용하면 text 값을 정상적으로 처리할 수 있습니다.

 

final data = await supabase
  .from('test')
  .select()
  .eq('data->>type', 'hello');

 

이렇게 사용하면 정상적으로 eq 가 동작합니다.

 

 

#json, jsonb 의 차이 및 접근자에 대해서 더 알고 싶다면

 

OperatorRight Operand TypeDescriptionExampleExample Result

-> int Get JSON array element (indexed from zero, negative integers count from the end) '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2 {"c":"baz"}
-> text Get JSON object field by key '{"a": {"b":"foo"}}'::json->'a' {"b":"foo"}
->> int Get JSON array element as text '[1,2,3]'::json->>2 3
->> text Get JSON object field as text '{"a":1,"b":2}'::json->>'b' 2
#> text[] Get JSON object at specified path '{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}' {"c": "foo"}
#>> text[] Get JSON object at specified path as text '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' 3

https://www.postgresql.org/docs/9.5/functions-json.html

 

 

[참고]

https://yeongunheo.tistory.com/entry/PostgreSQL-json-jsonb-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%97%B0%EC%82%B0%EC%9E%90

 

[PostgreSQL] json, jsonb 타입과 연산자

데이터베이스 테이블 내 하나의 컬럼에 JSON 데이터를 저장하는 경우가 있습니다. 보통 외부에서 제공된 데이터를 별도의 처리 없이 그대로 저장할 때 JSON 타입으로 저장하게 됩니다. PostgreSQL은 J

yeongunheo.tistory.com

 

 

#해피코딩

+ Recent posts