133 lines
4.5 KiB
TypeScript
133 lines
4.5 KiB
TypeScript
type TemplateParams = {
|
|
sheetId: string
|
|
allowedOrigins: string[]
|
|
minSubmitSeconds: number
|
|
honeypotFieldName: string
|
|
sheetName?: string
|
|
}
|
|
|
|
export function generateAppsScriptTemplate(params: TemplateParams) {
|
|
const { sheetId, allowedOrigins, minSubmitSeconds, honeypotFieldName, sheetName = 'Responses' } = params
|
|
|
|
const code = `/**
|
|
* Apps Script Web App for accepting HTML form submissions and writing to Google Sheets.
|
|
* Deployed as Web App with Anyone (even anonymous) or Anyone within domain access, as needed.
|
|
*/
|
|
const CONFIG = {
|
|
SHEET_ID: '${sheetId}',
|
|
SHEET_NAME: '${sheetName}',
|
|
ALLOWED_ORIGINS: ${JSON.stringify(allowedOrigins)},
|
|
minSubmitSeconds: ${minSubmitSeconds},
|
|
HONEYPOT: '${honeypotFieldName}',
|
|
};
|
|
|
|
function doOptions(e) {
|
|
return corsResponse('', 204);
|
|
}
|
|
|
|
function doPost(e) {
|
|
try {
|
|
const origin = (e?.parameter?.origin) || (e?.headers && e.headers['Origin']) || '';
|
|
const now = Date.now();
|
|
const params = parseBody(e);
|
|
|
|
// CORS check
|
|
enforceCors(origin);
|
|
|
|
// Spam protection: honeypot must be empty
|
|
if (params[CONFIG.HONEYPOT]) {
|
|
return corsResponse(JSON.stringify({ ok: true }), 200, origin); // pretend success
|
|
}
|
|
|
|
// Spam protection: minimum submit time
|
|
const ts = Number(params['_ts'] || 0);
|
|
if (!isFinite(ts) || now - ts < CONFIG.minSubmitSeconds * 1000) {
|
|
return corsResponse(JSON.stringify({ ok: true }), 200, origin); // pretend success
|
|
}
|
|
|
|
// Write to sheet
|
|
const ss = SpreadsheetApp.openById(CONFIG.SHEET_ID);
|
|
const sh = ss.getSheetByName(CONFIG.SHEET_NAME) || ss.getSheets()[0];
|
|
const entries = Object.keys(params).filter(k => k !== CONFIG.HONEYPOT);
|
|
const values = entries.map(k => String(params[k]));
|
|
sh.appendRow([new Date()].concat(values));
|
|
|
|
return corsResponse(JSON.stringify({ ok: true }), 200, origin);
|
|
} catch (err) {
|
|
return corsResponse(JSON.stringify({ ok: false, error: String(err) }), 500);
|
|
}
|
|
}
|
|
|
|
function parseBody(e) {
|
|
// Accept form-urlencoded or JSON
|
|
if (e?.postData?.type === 'application/json') {
|
|
try { return JSON.parse(e.postData.contents || '{}'); } catch (_) { return {}; }
|
|
}
|
|
// Parse form-urlencoded
|
|
const raw = (e?.postData?.contents || '');
|
|
const out = {};
|
|
raw.split('&').forEach(p => {
|
|
const [k, v] = p.split('=');
|
|
if (k) out[decodeURIComponent(k)] = decodeURIComponent((v || '').replace(/\+/g, ' '));
|
|
});
|
|
// Also include e.parameter for safety
|
|
if (e?.parameter) {
|
|
Object.keys(e.parameter).forEach(k => { if (!(k in out)) out[k] = e.parameter[k]; });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function enforceCors(origin) {
|
|
if (!CONFIG.ALLOWED_ORIGINS || CONFIG.ALLOWED_ORIGINS.length === 0) return;
|
|
if (CONFIG.ALLOWED_ORIGINS.indexOf(origin) === -1) {
|
|
throw new Error('CORS: Origin not allowed: ' + origin);
|
|
}
|
|
}
|
|
|
|
function corsResponse(body, status, origin) {
|
|
const output = ContentService.createTextOutput(body).setMimeType(ContentService.MimeType.JSON);
|
|
const resp = HtmlService.createHtmlOutput().getResponse();
|
|
resp.setContent(output.getContent());
|
|
resp.setResponseCode(status || 200);
|
|
resp.setHeader('Access-Control-Allow-Origin', origin || '*');
|
|
resp.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
resp.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
return resp;
|
|
}
|
|
`;
|
|
|
|
const readme = `# Google Apps Script Web App (Google Sheets 저장)
|
|
|
|
이 스크립트는 HTML 폼 제출을 받아 Google 스프레드시트에 행을 추가합니다.
|
|
|
|
## 준비물
|
|
- Google 스프레드시트 생성 후 시트 ID 확인(주소창의 /d/ 와 /edit 사이)
|
|
- Apps Script 에디터 열기(스프레드시트 -> 확장 프로그램 -> Apps Script)
|
|
|
|
## 사용 방법
|
|
1. 새 프로젝트 생성 후, Code.gs 에 아래 코드 붙여넣기
|
|
2. CONFIG 내 값을 편집
|
|
- SHEET_ID: ${sheetId}
|
|
- SHEET_NAME: ${sheetName}
|
|
- ALLOWED_ORIGINS: ${JSON.stringify(allowedOrigins)}
|
|
- MIN_SUBMIT_SECONDS: ${minSubmitSeconds}
|
|
- HONEYPOT: ${honeypotFieldName}
|
|
3. 배포
|
|
- 상단 메뉴: Deploy -> Deploy as Web app
|
|
- 새 배포 -> Entry point: doPost
|
|
- Access: Anyone (권장) 또는 필요 정책에 맞게 설정
|
|
- Deploy를 눌러 Web app URL 획득
|
|
4. 빌더에서 폼 action URL 에 위 Web app URL 입력
|
|
|
|
## CORS 및 스팸 방지
|
|
- Access-Control-Allow-Origin 헤더가 설정됩니다.
|
|
- 허용 오리진만 통과하도록 enforceCors를 사용합니다.
|
|
- honeypot 필드와 제출 타임스탬프(_ts)로 기본 스팸 방지 동작을 합니다.
|
|
|
|
## 테스트
|
|
- doPost, SpreadsheetApp 관련 호출은 Apps Script 런타임에서 동작합니다.
|
|
`
|
|
|
|
return { code, readme }
|
|
}
|