Created
December 3, 2025 16:12
-
-
Save ds1dbx/a7c6379c66e04e7204f38e5c1583bc10 to your computer and use it in GitHub Desktop.
AI 카페 도우미 - 구글 앱 스크립트 코드
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| apps_script_questions_api.gs | |
| Google Apps Script single-file API server that reads a Google Sheet named | |
| '질문답변' and returns the sheet data as JSON. Designed to be called by a | |
| Vercel API proxy (or any HTTP client). | |
| 특징: | |
| 1) Vercel에서 실행되는 API 프록시 서버가 이 웹앱을 호출합니다. | |
| 2) 시트의 각 행을 { row, question, answer } 형태의 객체로 반환합니다. | |
| 3) 질문과 답변이 둘 다 비어있는(빈 행) 경우 무시합니다. | |
| 4) 에러 발생 시 에러 내용을 JSON으로 반환합니다. | |
| 5) 단일 파일로 작성되어 있으며, 아래 설명을 따라 배포하면 됩니다. | |
| 사용법 요약: | |
| - 스프레드시트 ID를 SPREADSHEET_ID 상수에 넣으세요. | |
| - 필요한 경우 SHEET_NAME을 변경하세요 (기본값: '질문답변'). | |
| - 배포: "새로 배포 -> 웹 앱"으로 배포하고 접근 권한은 "Anyone" (또는 적절히 설정) | |
| - 배포된 URL을 Vercel 프록시에 연결해서 사용하세요. | |
| 예시 응답 JSON: | |
| { | |
| "count": 3, | |
| "items": [ | |
| { "row": 2, "question": "질문1", "answer": "답변1" }, | |
| ... | |
| ] | |
| } | |
| 주의: | |
| - 이 스크립트는 서버사이드에서 호출하는 용도로 설계되어 있으므로 | |
| 브라우저의 CORS 제한은 Vercel 프록시(서버)가 호출할 때 문제되지 않습니다. | |
| */ | |
| /** | |
| * 설정: 여기에 스프레드시트 ID를 넣으세요. | |
| * - 스프레드시트 URL 예: https://docs.google.com/spreadsheets/d/<<<THIS_ID>>>/edit | |
| */ | |
| const SPREADSHEET_ID = 'PUT_YOUR_SPREADSHEET_ID_HERE'; // <<-- 여기에 ID 넣기 | |
| const SHEET_NAME = '질문답변'; // 시트 이름이 다르면 변경 | |
| /** | |
| * HTTP GET/POST 모두 처리합니다. (Vercel 프록시는 GET 또는 POST를 사용할 수 있으므로 둘 다 지원) | |
| */ | |
| function doGet(e) { | |
| return handleRequest(e); | |
| } | |
| function doPost(e) { | |
| return handleRequest(e); | |
| } | |
| /** | |
| * 실제 요청 처리기 | |
| */ | |
| function handleRequest(e) { | |
| try { | |
| // 스프레드시트 열기 | |
| if (!SPREADSHEET_ID || SPREADSHEET_ID.indexOf('PUT_YOUR') !== -1) { | |
| throw new Error('SPREADSHEET_ID가 설정되지 않았습니다. 코드를 수정하여 SPREADSHEET_ID를 입력하세요.'); | |
| } | |
| var ss = SpreadsheetApp.openById(SPREADSHEET_ID); | |
| var sheet = ss.getSheetByName(SHEET_NAME); | |
| if (!sheet) { | |
| throw new Error("시트 '" + SHEET_NAME + "'을(를) 찾을 수 없습니다. 시트 이름을 확인하세요."); | |
| } | |
| // 전체 범위 읽기 | |
| var values = sheet.getDataRange().getValues(); // 2차원 배열 | |
| if (!values || values.length === 0) { | |
| return jsonSuccess({ count: 0, items: [] }); | |
| } | |
| // 헤더 존재 여부 감지: 첫 행에 '질문' 또는 'question' 같은 텍스트가 있으면 헤더로 간주 | |
| var headerRowIndex = 0; | |
| var firstRowJoined = values[0].map(function(c){ return (c||'').toString().trim(); }).join('|'); | |
| if (/질문|question|답변|answer/i.test(firstRowJoined)) { | |
| headerRowIndex = 1; // 데이터는 1부터 시작 | |
| } | |
| var items = []; | |
| for (var i = headerRowIndex; i < values.length; i++) { | |
| var row = values[i]; | |
| // 첫 두 열을 질문/답변으로 간주. 필요하면 인덱스 조정 가능. | |
| var question = (row[0] || '').toString().trim(); | |
| var answer = (row[1] || '').toString().trim(); | |
| // 둘 다 비어있으면 빈 행으로 간주하고 무시 | |
| if (question === '' && answer === '') { | |
| continue; | |
| } | |
| items.push({ | |
| row: i + 1, // 실제 스프레드시트의 행 번호(1-indexed) | |
| question: question, | |
| answer: answer | |
| }); | |
| } | |
| return jsonSuccess({ count: items.length, items: items }); | |
| } catch (err) { | |
| // 에러 발생 시 에러 정보를 JSON으로 반환 | |
| var errObj = { | |
| error: true, | |
| message: err.message || String(err), | |
| // 스택은 배포 환경에서 민감할 수 있으니 디버깅 목적으로만 포함 | |
| stack: err.stack ? String(err.stack).split('\n').slice(0,10) : null | |
| }; | |
| return jsonError(errObj); | |
| } | |
| } | |
| /** | |
| * 성공 응답 helper | |
| */ | |
| function jsonSuccess(payload) { | |
| var out = ContentService.createTextOutput(JSON.stringify({ success: true, data: payload })); | |
| out.setMimeType(ContentService.MimeType.JSON); | |
| return out; | |
| } | |
| /** | |
| * 에러 응답 helper (HTTP status code를 직접 설정할 수는 없으므로 JSON body로 에러를 전달) | |
| */ | |
| function jsonError(errObj) { | |
| var out = ContentService.createTextOutput(JSON.stringify({ success: false, error: errObj })); | |
| out.setMimeType(ContentService.MimeType.JSON); | |
| return out; | |
| } | |
| /* | |
| 배포 가이드 (단계별) | |
| 1) 스프레드시트 ID 설정 | |
| - 스프레드시트 URL에서 ID 부분을 복사하여 SPREADSHEET_ID 상수에 붙여넣으세요. | |
| 2) 앱 스크립트 프로젝트 생성 | |
| - https://script.google.com 에서 새 프로젝트 생성 | |
| - 이 파일의 내용을 붙여넣고 저장 | |
| 3) 권한 설정 | |
| - 실행 시 처음으로 SpreadsheetApp.openById를 호출하면 권한 요청이 발생합니다. | |
| - 배포 전에 스크립트를 직접 실행하여 권한을 승인하세요(예: handleRequest를 수동으로 실행). | |
| 4) 웹 앱으로 배포 | |
| - 상단 메뉴: Deploy -> New deployment | |
| - "Select type"에서 "Web app" 선택 | |
| - Description 입력 | |
| - "Execute as"는 보통 "Me (your-email)"로 설정 | |
| - "Who has access"는 "Anyone" 또는 프로젝트 요구사항에 맞게 설정 (Vercel에서 호출하려면 공개 접근이 더 간편) | |
| - 배포 후 생성되는 URL(예: https://script.google.com/macros/s/XXXXX/exec)을 복사 | |
| 5) Vercel 프록시 연동 예시 (Node.js) | |
| - Vercel API 라우트에서 아래처럼 fetch로 Apps Script URL 호출 | |
| // pages/api/qa-proxy.js (Next.js API route 예시) | |
| export default async function handler(req, res) { | |
| try { | |
| const appsScriptUrl = 'YOUR_DEPLOYED_WEBAPP_URL_HERE'; | |
| const r = await fetch(appsScriptUrl, { method: 'GET' }); | |
| const data = await r.json(); | |
| res.status(200).json(data); | |
| } catch (err) { | |
| res.status(500).json({ error: String(err) }); | |
| } | |
| } | |
| 6) 테스트 | |
| - 브라우저나 curl로 웹앱 URL에 접속하면 JSON 응답을 확인할 수 있습니다. | |
| - Vercel 프록시를 통해 호출하면 동일한 JSON이 반환됩니다. | |
| 추가 팁: | |
| - 질문/답변 열의 위치가 달라지면 코드 내 row[0], row[1] 인덱스를 조정하세요. | |
| - 입력 데이터에 메타(카테고리, 작성자 등)가 있으면 items 객체에 해당 칼럼을 추가하도록 확장하면 됩니다. | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment