Supabase Edge Function 설정 가이드
1️⃣ Supabase CLI 설치
# macOS/Linux
brew install supabase/tap/supabase
# Windows (Scoop 사용)
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase
# 또는 npm으로 설치
npm install -g supabase
2️⃣ Supabase 프로젝트 초기화
# 프로젝트 루트에서 실행
supabase init
# 이 명령어가 supabase/ 디렉토리를 생성합니다
3️⃣ Edge Function 생성
# evaluate-session 함수 생성
supabase functions new evaluate-session
# 이 명령어가 다음 파일을 생성합니다:
# supabase/functions/evaluate-session/index.ts
4️⃣ Edge Function 코드 작성
supabase/functions/evaluate-session/index.ts 파일에 앞서 제공한 코드를 복사합니다.
5️⃣ 환경 변수 설정
# .env 파일 생성 (프로젝트 루트)
cat > .env << EOF
GROQ_API_KEY=your_groq_api_key_here
SUPABASE_URL=your_supabase_url
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
EOF
# Supabase 프로젝트에 환경 변수 설정
supabase secrets set GROQ_API_KEY=your_groq_api_key_here
6️⃣ 로컬에서 테스트
# Supabase 로컬 개발 환경 시작
supabase start
# Edge Function 로컬 실행
supabase functions serve evaluate-session
# 다른 터미널에서 테스트
curl -i --location --request POST 'http://localhost:54321/functions/v1/evaluate-session' \
--header 'Authorization: Bearer YOUR_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{"sessionId":"session_123"}'
7️⃣ 프로덕션 배포
# Supabase 프로젝트에 로그인
supabase login
# 프로젝트 연결 (처음 한 번만)
supabase link --project-ref your-project-ref
# Edge Function 배포
supabase functions deploy evaluate-session
# 배포 확인
supabase functions list
8️⃣ CORS 설정 (웹앱에서 호출할 경우)
supabase/functions/evaluate-session/index.ts에 CORS 헤더 추가:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};
serve(async (req) => {
// CORS preflight 처리
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
// ... 기존 코드 ...
return new Response(JSON.stringify({ success: true, evaluation }), {
status: 200,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});
9️⃣ Groq API 키 발급
- Groq Console 접속
- 회원가입/로그인
- API Keys 메뉴에서 새 키 생성
- 무료 티어 자동 적용됨
🔟 데이터베이스 마이그레이션
# SQL 파일 생성
supabase migration new create_tables
# supabase/migrations/에 생성된 파일에 테이블 스키마 작성
# (coding_sessions, session_evaluations 테이블)
# 로컬 DB에 적용
supabase db push
# 프로덕션에 적용
supabase db push --linked
📝 프로젝트 구조 예시
ai-collaboration-platform/
├── .env # 로컬 환경 변수
├── .gitignore # .env 반드시 포함!
├── supabase/
│ ├── functions/
│ │ └── evaluate-session/
│ │ └── index.ts # Edge Function
│ ├── migrations/
│ │ ├── 20240115000001_create_coding_sessions.sql
│ │ └── 20240115000002_create_session_evaluations.sql
│ └── config.toml
├── vscode-extension/
│ ├── src/
│ │ └── extension.ts
│ ├── package.json
│ └── tsconfig.json
└── README.md
⚠️ 주의사항
- 환경 변수 보안:
.env 파일은 절대 Git에 커밋하지 마세요
- API 키 관리: Groq API 키는 Supabase Secrets로 관리
- 로컬 테스트: 배포 전 반드시 로컬에서 테스트
- 에러 처리: Edge Function에 적절한 에러 핸들링 추가
🔍 유용한 명령어
# Edge Function 로그 확인
supabase functions inspect evaluate-session
# Edge Function 삭제
supabase functions delete evaluate-session
# 모든 시크릿 보기
supabase secrets list
# 특정 시크릿 삭제
supabase secrets unset GROQ_API_KEY
🎯 문제 생성 API 사용법 (generate-problem)
개요
generate-problem Edge Function은 Groq AI를 사용하여 실무 중심의 코딩 문제 또는 비즈니스 시나리오 기반 엑셀 문제를 자동으로 생성하고, Supabase의 challenges 테이블에 저장합니다.
🎯 주요 특징
코딩 문제:
- 실제 개발 현장에서 마주칠 법한 시나리오 (API 통합, 데이터 처리, 에러 핸들링 등)
- 상세한 starter_code (30-100줄) 제공:
- 타입 정의/인터페이스
- 헬퍼 함수 스켈레톤
- 한국어 상세 주석
- TODO 주석으로 구현 영역 표시
- 예제 테스트 케이스
엑셀 문제:
- 실제 비즈니스 상황 (매출 분석, 재고 관리, 재무 보고 등)
- 현실적인 샘플 데이터 (최소 10-20행)
- 실무에서 사용되는 Excel 기능 활용
API 엔드포인트
POST https://your-project-ref.supabase.co/functions/v1/generate-problem
요청 형식
헤더
Authorization: Bearer YOUR_ANON_KEY
Content-Type: application/json
요청 본문 (코딩 문제)
{
"topic": "이진 탐색",
"type": "coding",
"difficulty": "medium",
"language": "Python"
}
요청 본문 (엑셀 문제)
{
"topic": "VLOOKUP 함수",
"type": "excel",
"difficulty": "easy"
}
필수 파라미터
| 파라미터 |
타입 |
필수 여부 |
설명 |
예시 |
type |
string |
✅ 필수 |
문제 유형 (coding 또는 excel) |
"coding" |
difficulty |
string |
✅ 필수 |
난이도 (easy, medium, hard) |
"medium" |
category |
string |
⭕ 선택 |
개발 분야 (아래 참조) |
"frontend", "backend" |
topic |
string |
⭕ 선택 |
문제 주제 (생략 시 LLM이 자동 선정) |
"배열", "피벗 테이블" |
language |
string |
⭕ 선택 |
프로그래밍 언어 (coding 타입만 해당, 기본값: Python) |
"Python", "JavaScript" |
카테고리 (category) 옵션
| 카테고리 |
설명 |
예시 주제 |
frontend |
프론트엔드 개발 |
React 상태 관리, CSS 반응형, 폼 검증, 무한 스크롤 |
backend |
백엔드 개발 |
REST API, DB 쿼리 최적화, 인증/인가, 캐싱, 레이트 리미팅 |
fullstack |
풀스택 개발 |
실시간 알림, OAuth 인증, 파일 업로드, 검색 기능 |
devops |
DevOps |
CI/CD 파이프라인, 컨테이너, 모니터링, 로그 분석 |
data |
데이터 엔지니어링 |
ETL, 데이터 파이프라인, 분석 쿼리, 대용량 처리 |
mobile |
모바일 개발 |
오프라인 동기화, 푸시 알림, 네이티브 연동, 상태 관리 |
응답 형식
성공 응답 (200 OK)
{
"success": true,
"challenge": {
"id": "uuid-here",
"title": "API 응답 캐싱 시스템 구현",
"track": "coding",
"difficulty": "medium",
"description": "주제: API 성능 최적화",
"problem_statement": "외부 API 호출 비용을 절감하기 위해 LRU 캐시를 활용한 응답 캐싱 시스템을 구현하세요...",
"test_cases": {
"examples": [
{
"input": "{ url: 'https://api.example.com/users', params: { id: 1 } }",
"output": "{ data: {...}, cached: false }",
"explanation": "첫 요청은 실제 API를 호출하고 결과를 캐시에 저장합니다"
}
],
"constraints": [
"캐시 크기는 최대 100개 항목으로 제한",
"동일한 요청은 5분간 캐시된 결과 반환",
"..."
]
},
"starter_code": {
"TypeScript": "/**\n * API 응답 캐싱 시스템\n * LRU (Least Recently Used) 캐시 알고리즘 구현\n */\n\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n hitCount: number;\n}\n\ninterface ApiRequestConfig {\n url: string;\n params?: Record<string, any>;\n headers?: Record<string, string>;\n}\n\nclass ApiCache {\n private cache: Map<string, CacheEntry<any>>;\n private maxSize: number;\n private ttl: number; // Time to live in milliseconds\n\n constructor(maxSize: number = 100, ttl: number = 300000) {\n this.cache = new Map();\n this.maxSize = maxSize;\n this.ttl = ttl;\n }\n\n // TODO: 캐시 키 생성 로직 구현\n private generateCacheKey(config: ApiRequestConfig): string {\n // 구현 필요\n }\n\n // TODO: 캐시에서 데이터 조회\n async get<T>(config: ApiRequestConfig): Promise<T | null> {\n // 구현 필요\n }\n\n // TODO: 캐시에 데이터 저장\n set<T>(config: ApiRequestConfig, data: T): void {\n // LRU 정책에 따라 오래된 항목 제거\n // 구현 필요\n }\n\n // TODO: 만료된 항목 정리\n private evictExpired(): void {\n // 구현 필요\n }\n}\n\n// 사용 예시:\n// const cache = new ApiCache(100, 300000);\n// const result = await cache.get({ url: 'https://api.example.com/users' });\n"
},
"tech_stack": ["TypeScript"],
"is_published": true,
"created_at": "2026-01-15T09:07:00.000Z"
},
"generatedContent": {
"title": "API 응답 캐싱 시스템 구현",
"topic": "API 성능 최적화",
"description": "외부 API 호출 비용을 절감하기 위해...",
"examples": [...],
"constraints": [...],
"starterCode": "..."
}
}
에러 응답
{
"error": "Missing required fields: topic, type, difficulty"
}
사용 예시
1. cURL 사용
# 코딩 문제 생성 (주제 지정)
curl -i --location --request POST \
'https://your-project-ref.supabase.co/functions/v1/generate-problem' \
--header 'Authorization: Bearer YOUR_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{
"topic": "동적 프로그래밍",
"type": "coding",
"difficulty": "hard",
"language": "Python"
}'
# 코딩 문제 생성 (LLM이 주제 자동 선정)
curl -i --location --request POST \
'https://your-project-ref.supabase.co/functions/v1/generate-problem' \
--header 'Authorization: Bearer YOUR_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{
"type": "coding",
"difficulty": "medium",
"language": "TypeScript"
}'
# 프론트엔드 문제 생성
curl -i --location --request POST \
'https://your-project-ref.supabase.co/functions/v1/generate-problem' \
--header 'Authorization: Bearer YOUR_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{
"type": "coding",
"category": "frontend",
"difficulty": "medium",
"language": "TypeScript"
}'
# 백엔드 문제 생성
curl -i --location --request POST \
'https://your-project-ref.supabase.co/functions/v1/generate-problem' \
--header 'Authorization: Bearer YOUR_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{
"type": "coding",
"category": "backend",
"difficulty": "hard",
"language": "Python"
}'
# 엑셀 문제 생성
curl -i --location --request POST \
'https://your-project-ref.supabase.co/functions/v1/generate-problem' \
--header 'Authorization: Bearer YOUR_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{
"topic": "조건부 서식",
"type": "excel",
"difficulty": "easy"
}'
2. JavaScript/TypeScript 사용
const { data, error } = await fetch(
"https://your-project-ref.supabase.co/functions/v1/generate-problem",
{
method: "POST",
headers: {
Authorization: `Bearer ${SUPABASE_ANON_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
topic: "재귀 함수",
type: "coding",
difficulty: "medium",
language: "JavaScript",
}),
}
).then((res) => res.json());
if (data.success) {
console.log("생성된 문제 ID:", data.challenge.id);
console.log("문제 제목:", data.challenge.title);
}
3. Python 사용
import requests
url = "https://your-project-ref.supabase.co/functions/v1/generate-problem"
headers = {
"Authorization": f"Bearer {SUPABASE_ANON_KEY}",
"Content-Type": "application/json"
}
payload = {
"topic": "그래프 탐색",
"type": "coding",
"difficulty": "hard",
"language": "Python"
}
response = requests.post(url, json=payload, headers=headers)
result = response.json()
if result.get("success"):
print(f"문제 생성 완료: {result['challenge']['title']}")
print(f"문제 ID: {result['challenge']['id']}")
로컬 테스트
# 로컬 Supabase 시작
supabase start
# generate-problem 함수 실행
supabase functions serve generate-problem
# 다른 터미널에서 테스트
curl -i --location --request POST \
'http://localhost:54321/functions/v1/generate-problem' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
--header 'Content-Type: application/json' \
--data '{
"topic": "배열 정렬",
"type": "coding",
"difficulty": "easy",
"language": "Python"
}'
데이터베이스 스키마 (challenges 테이블)
CREATE TABLE challenges (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title TEXT NOT NULL,
type TEXT NOT NULL CHECK (type IN ('coding', 'excel')),
difficulty TEXT NOT NULL CHECK (difficulty IN ('easy', 'medium', 'hard')),
topic TEXT NOT NULL,
-- Coding 문제 필드
description TEXT,
examples JSONB,
constraints JSONB,
starter_code TEXT,
language TEXT,
-- Excel 문제 필드
scenario TEXT,
data JSONB,
question TEXT,
solution TEXT,
explanation TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
환경 변수 설정
# Groq API 키 설정 (필수)
supabase secrets set GROQ_API_KEY=your_groq_api_key_here
# Supabase URL과 ANON KEY는 자동으로 설정됨
에러 처리
| 에러 메시지 |
원인 |
해결 방법 |
GROQ_API_KEY is not set |
Groq API 키 미설정 |
supabase secrets set GROQ_API_KEY=... 실행 |
Missing required fields: type, difficulty |
필수 파라미터 누락 |
type, difficulty 전달 (topic은 선택) |
Invalid type. Must be "coding" or "excel" |
잘못된 type 값 |
type을 "coding" 또는 "excel"로 설정 |
Failed to save challenge to database |
DB 저장 실패 |
challenges 테이블 존재 여부 확인 |
Generated content was not valid JSON |
AI 응답 오류 |
다시 시도하거나 max_tokens 증가 |
Examples
curl -i --location --request POST 'https://soqscjpglqbbzxkdtien.supabase.co/functions/v1/generate-problem' --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNvcXNjanBnbHFiYnp4a2R0aWVuIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3Njg5NTQsImV4cCI6MjA4MzM0NDk1NH0.q_zBFkJWaCE1UTc-UwFGLduxUWrpOG-veVaZCR_kCFc' --header 'Content-Type: application/json' --data '{"type":"coding", "topic":"", "difficulty":"medium", "language":"typescript"}'
📦 챌린지 다운로드 기능 (VS Code Extension)
웹 사이트에서 문제를 선택하면 VS Code 플러그인이 자동으로 챌린지를 다운로드하고, 압축을 풀어 바로 코딩을 시작할 수 있습니다.
아키텍처
┌─────────────────────────────────────────────────────────────────────┐
│ 웹 사이트 │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 📋 문제 목록 │ │
│ │ ├── [Frontend] React 상태 관리 (medium) │ │
│ │ ├── [Backend] API 레이트 리미팅 (hard) │ │
│ │ └── [Fullstack] 실시간 채팅 (hard) │ │
│ │ │ │
│ │ [VS Code에서 열기] 버튼 클릭 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ vscode://ai-collaboration-tracker/challenge?id=xxx │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ VS Code Extension │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 1. URI Handler가 challenge ID 수신 │ │
│ │ 2. Supabase Storage에서 ZIP 파일 다운로드 │ │
│ │ 3. ~/AIPang-Challenges/{문제명}/ 폴더에 압축 해제 │ │
│ │ 4. 새 워크스페이스로 열기 │ │
│ │ 5. 세션 시작 (코딩 추적 시작) │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
사용 방법
1. 웹에서 "VS Code에서 열기" 버튼 클릭
웹 사이트에 다음과 같은 버튼을 추가합니다:
<a href="vscode://ai-collaboration-tracker/challenge?id=챌린지-UUID">
VS Code에서 열기
</a>
2. VS Code에서 직접 다운로드
Cmd/Ctrl + Shift + P 로 명령 팔레트 열기
- "AI Tracker: 챌린지 다운로드" 선택
- 챌린지 ID 입력
- 자동으로 다운로드 및 워크스페이스 열기
Supabase Storage 설정
챌린지 ZIP 파일은 Supabase Storage에 저장합니다.
1. Storage 버킷 생성
-- Supabase SQL Editor에서 실행
INSERT INTO storage.buckets (id, name, public)
VALUES ('challenges', 'challenges', true);
2. 폴더 구조
challenges/
├── {challenge-id-1}/
│ └── challenge.zip
├── {challenge-id-2}/
│ └── challenge.zip
└── ...
3. ZIP 파일 구조
challenge.zip
├── README.md # 문제 설명
├── src/
│ └── solution.ts # 시작 코드
├── tests/
│ └── test.ts # 테스트 케이스
├── package.json # 종속성
└── .challenge.json # 메타데이터 (자동 생성)
웹 사이트에 버튼 추가 예시
// React 예시
function ChallengeCard({ challenge }) {
const openInVSCode = () => {
window.location.href = `vscode://ai-collaboration-tracker/challenge?id=${challenge.id}`;
};
return (
<div className="challenge-card">
<h3>{challenge.title}</h3>
<p>{challenge.description}</p>
<span className="badge">{challenge.difficulty}</span>
<span className="badge">{challenge.track}</span>
<button onClick={openInVSCode}>
🚀 VS Code에서 열기
</button>
</div>
);
}
프로세스 상세
- URI 수신:
vscode://ai-collaboration-tracker/challenge?id=xxx
- 챌린지 정보 조회: challenges 테이블에서 메타데이터 조회
- ZIP 다운로드: Supabase Storage에서
{id}/challenge.zip 다운로드
- 압축 해제:
~/AIPang-Challenges/{문제명}/ 폴더에 압축 해제
- 메타데이터 생성:
.challenge.json 파일 생성 (챌린지 ID, 다운로드 시간)
- 워크스페이스 열기: VS Code에서 해당 폴더 열기
- 세션 시작 안내: "세션 시작" 버튼으로 코딩 추적 시작
관련 명령어
| 명령어 |
설명 |
AI Tracker: 챌린지 다운로드 |
ID 입력하여 챌린지 다운로드 |
AI Tracker: 챌린지 시작 |
다운로드된 챌린지로 세션 시작 |
AI Tracker: 세션 종료 |
코딩 세션 종료 및 평가 |