401 에러와 권한 지옥을 뚫고 완성한 Supabase x Apps Script 연동기

소개

시도하고자 했던 것과 그 이유를 알려주세요.

빌라 관리 효율화를 위해 Airtable을 검토했으나, 가구수가 많아 금세 유료 용량에 도달하는 한계가 있었습니다. 대안으로 무료 티어가 혜자로운 Supabase를 선택했습니다.

처음에는 IDE(VS Code 등)에서 직접 데이터를 밀어 넣으려 했으나, 대량의 기초 데이터를 한 번에 처리할 때 발생하는 토큰 제한과 연결 끊김 문제로 실패했습니다. 결국 엑셀이 너무 편한 저에게 가장 익숙한 구글 스프레드시트를 '기본 입력 도구'로, Supabase를 '중앙 DB'로 삼아 Apps Script로 두 환경을 연결하는 시스템을 구축했습니다.

아쉽게도 클로드코드 + airtable가 아닌 어쩐지 잘못된 길로 혼자 가는 느낌이지만... 하루의 반 가까이를 클로드가 안되는 곳에서 끄적이다보니 어떻게든 샛길로라도 해보려고 노력하고 있습니다.


진행 방법

어떤 도구를 사용했고, 어떻게 활용하셨나요?

  • 최소한의 보안 설정: API URL과 Key를 코드에 직접 노출하지 않기 위해 Apps Script의 '스크립트 속성(Script Properties)' 기능을 활용했습니다. 이는 코드 유출 시에도 핵심 자산을 보호하는 최소한의 방어선이 되었습니다.

  • 권한 설정의 난관: 처음에는 보안을 위해 service_role 키를 고집했으나, Apps Script를 브라우저로 인식하는 Supabase의 철저한 보안망(401 Forbidden)에 가로막혔습니다. 결국 Publishable (anon)로 선회하되, 보안 구멍을 막기 위해 SQL 쿼리로 직접 권한 정책을 강제 지정했습니다.

번거로운 Security policy 설정

원래대로 하자면 아래와 같이 각각의 테이블마다 각 권한을 '하나씩' 선택하여 등록해 줘야 하는데, 자동화를 외치는 마당에 수십개를 하나씩 등록하는건 너무 귀찮은 작업이었습니다.

보안 정책 설정을 보여주는 웹 브라우저의 스크린샷

명색이 DB인데 그래서 일괄로 할 수 있는 명령어가 있을 것 같아 요청하니 쿼리를 짜줬어요.

Tip: 사용한 프롬프트 전문

Supabase에서 RLS를 하나씩 설정하기 너무 귀찮아. anon 키를 사용하되, users 테이블에 대해 SELECT, INSERT, UPDATE 권한을 한꺼번에 부여하는 명령어를 짜줘.

Tip: 코드 전문

SQL

-- 1. 모든 권한을 한 번에 부여하는 SQL 정책 (SQL Editor에서 실행)
-- 일일이 클릭해서 설정하기 귀찮을 때 유용한 '강제 권한' 쿼리입니다.
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Enable all for anon" ON "public"."users"
FOR ALL -- SELECT, INSERT, UPDATE, DELETE 모두 포함
TO anon
USING (true)
WITH CHECK (true);

Tip: 사용한 프롬프트 전문

입주자정보 시트에 있는 데이터를 Apps Script에서 supabase의 users 테이블로 연동하는 전체 코드를 작성해줘.

Tip: 코드 전문

JavaScript

// 2. Apps Script 전체 연동 코드
// 스크립트 배포시 KEY가 노출될 위험이 있기 때문에 스크립트 속성에 추가
const scriptProperties = PropertiesService.getScriptProperties();
const SB_URL = scriptProperties.getProperty('SUPABASE_URL');
const SB_KEY = scriptProperties.getProperty('SUPABASE_KEY'); // anon 키 사용

function syncToSupabase() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName("입주자명부");
  const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 18).getValues();
  
  let successCount = 0;
  values.forEach((row, index) => {
    if (!row[2]) return; // 호수(C열) 필수 체크

    const payload = {
      "user_id": row[0] || undefined, // A열 UUID가 있으면 Update
      "room_number": String(row[2]),
      // ... 추가 필드들
    };

    const options = {
      "method": "post",
      "headers": {
        "apikey": SB_KEY,
        "Authorization": "Bearer " + SB_KEY,
        "Content-Type": "application/json",
        "Prefer": "return=representation,resolution=merge-duplicates"
      },
      "payload": JSON.stringify(payload),
      "muteHttpExceptions": true
    };

    const response = UrlFetchApp.fetch(`${SB_URL}/rest/v1/users`, options);
    const resText = response.getContentText();
    
    // 신규 등록 시 생성된 ID를 시트에 역으로 기록 (데이터 무결성 확보)
    if (response.getResponseCode() === 201 && !row[0]) {
      const resData = JSON.parse(resText);
      sheet.getRange(index + 2, 1).setValue(resData[0].user_id);
    }
    successCount++;
  });
  Browser.msgBox(successCount + "건 동기화 성공!");
}

스크립트 속성

  • 스크립트 내에 API키를 작성해서 그대로 배포하려니 마음에 걸려서 찾아보니 Apps script의 설정에 '스크립트 속성'에 작성하면 노출될 위험이 없더라구요.

  • 아래와 같이 설정을 하고 PropertiesService.getScriptProperties();를 사용하여 스크립트 내에서 설정을 불러올 수가 있습니다.

const scriptProperties = PropertiesService.getScriptProperties();
const SB_URL = scriptProperties.getProperty('SUPABASE_URL');
const SB_KEY = scriptProperties.getProperty('SUPABASE_KEY'); // anon 키 사용


결과와 배운 점

배운 점과 나만의 꿀팁을 알려주세요.

  1. 보안과 편의성의 타협: service_role 키의 401 에러 지옥에서 탈출하려면 anon 키 + SQL Policy 조합이 정답입니다. 일일이 대시보드에서 클릭하는 것보다 SQL 한 줄이 훨씬 빠르고 정확합니다.

  2. 데이터 무결성(ID 역기록): 전송 성공 후 Supabase가 생성한 user_id를 다시 스프레드시트 A열에 적어주는 과정이 중요합니다. 그래야 다음번 동기화 때 중복 생성 없이 정확히 '수정'만 일어납니다.

  3. 유연한 예외 처리: 입주민 성명이 없는 '공실' 상태도 데이터이므로, 필수값 로직에서 성명을 제외하고 호수만으로도 등록되게 설계한 점이 실무에서 큰 신의 한 수였습니다.

과정 중에 어떤 시행착오를 겪었나요?

  • "실행 기록에 아무것도 남지 않을 때"의 막막함이 가장 컸습니다. 결국 Browser.msgBox로 단계별 체크를 하며 UrlFetchApp의 통신 오류를 잡아냈습니다. 또한, 날짜 데이터 형식이 맞지 않아 전송이 거부되던 문제는 Utilities.formatDate로 포맷을 통일하여 해결했습니다.

앞으로의 계획이 있다면 들려주세요.

  • DB가 완성되었으니 이제 Vercel과 Supabase를 직접 연결해, 관리자인 제가 외부에서도 폰으로 바로 민원이나 미납 현황을 확인할 수 있는 대시보드 웹사이트를 구축할 예정입니다.

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요