Last active
July 29, 2023 14:37
-
-
Save potados99/7006f5bd7a60822791cdb601ce520f6a to your computer and use it in GitHub Desktop.
임시 데이터 수집기(collect.potados.com) AWS Lambda 구현
This file contains 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
const fs = require('fs').promises; | |
const path = require('path'); | |
const { randomUUID } = require('crypto'); | |
async function getDataSource(channelName) { | |
const channelNameSanitized = channelName.replace(/\//g, '').replace(/\./g, '').trim(); | |
if (!channelNameSanitized) { | |
throw new Error(`Invalid channel name: [${channelName}]`); | |
} | |
const filePath = path.join('/mnt/storage/channels/', `${channelNameSanitized}.json`); | |
// 파일이 없으면 생성합니다. | |
try { | |
await fs.access(filePath); | |
} catch (e) { | |
await fs.writeFile(filePath, '[]'); | |
} | |
return filePath; | |
} | |
async function getBackupDataSource() { | |
const filePath = path.join('/mnt/storage/', `combined.json`); | |
// 파일이 없으면 생성합니다. | |
try { | |
await fs.access(filePath); | |
} catch (e) { | |
await fs.writeFile(filePath, '[]'); | |
} | |
return filePath; | |
} | |
async function getStorage(dataSource) { | |
const file = dataSource; | |
return { | |
getAllMessages: async function() { | |
const fileContents = await fs.readFile(file); | |
return JSON.parse(fileContents); | |
}, | |
getMessage: async function(messageId) { | |
const allMessages = await this.getAllMessages(); | |
if (['last', 'latest'].includes(messageId.toLowerCase())) { | |
// 가장 마지막(최근) 것을 줍니다. | |
return allMessages.pop(); | |
} | |
return allMessages.filter(message => message.id === messageId).pop(); | |
}, | |
addMessage: async function({channelName, timeEpoch, userAgent, sourceIp, content}) { | |
const allMessages = await this.getAllMessages(); | |
const newMessage = { | |
id: randomUUID(), | |
channel: channelName, | |
timestamp: timeEpoch, | |
date: new Date(timeEpoch).toLocaleString('ko-KR', {timeZone: 'Asia/Seoul'}), | |
userAgent, | |
sourceIp, | |
body: content | |
}; | |
allMessages.push(newMessage); | |
await fs.writeFile(file, JSON.stringify(allMessages, null, 4)); | |
}, | |
deleteAllMessages: async function() { | |
await fs.writeFile(file, '[]'); | |
} | |
}; | |
} | |
async function getChannelNameAndMessageId(pathParameters) { | |
if (pathParameters == null) { | |
// 최상위 경로(/)로 들어오면 pathParameters가 없습니다. | |
return { | |
channelName: 'default', | |
messageId: undefined | |
}; | |
} | |
const {channel, message} = pathParameters; | |
return { | |
channelName: channel, | |
messageId: message | |
}; | |
} | |
async function getRoute(event) { | |
const { | |
pathParameters, | |
queryStringParameters, | |
isBase64Encoded, | |
body, | |
requestContext: {timeEpoch, http: {method, userAgent, sourceIp}} | |
} = event; | |
const {channelName, messageId} = await getChannelNameAndMessageId(pathParameters); | |
const storage = await getStorage(await getDataSource(channelName)); | |
const backupStorage = await getStorage(await getBackupDataSource()); | |
const routes = { | |
get: async function() { | |
const isCurl = userAgent.includes('curl'); | |
const isApiCall = queryStringParameters?.response?.toLowerCase() === 'api'; | |
const messages = messageId == null ? await storage.getAllMessages() : await storage.getMessage(messageId); | |
if (messages == null) { | |
return { | |
statusCode: 404, | |
body: 'Message not found.' | |
}; | |
} | |
if (isCurl || isApiCall) { | |
// 커맨드라인에서 curl로 들어온 요청이거나, | |
// response 파라미터가 api인 요청인 경우, | |
// application/json으로 내려줍니다. | |
return { | |
statusCode: 200, | |
headders: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(messages, null, 4) | |
}; | |
} else { | |
// 아닌 경우, 웹 브라우저로 간주하고 text/html로 내려줍니다. | |
return { | |
statusCode: 200, | |
headers: { | |
'Content-Type': 'text/html' | |
}, | |
body: ` | |
<html lang='ko'> | |
<head> | |
<meta charset='utf-8'> | |
<meta name='viewport' content='width=device-width,initial-scale=1'> | |
<title>감자도스 수집기</title> | |
</head> | |
<body> | |
<h2>${channelName}</h2> | |
<pre style='white-space: pre-wrap;'>${JSON.stringify(messages, null, 4)}</pre> | |
<form onSubmit="fetch('/${channelName}', {method: 'POST', body: document.getElementById('form_content').value}).then(() => window.location.reload()); return false;"> | |
<input type='text' id='form_content'> | |
<input type='submit' value='등록'> | |
</form> | |
<form onSubmit="fetch('/${channelName}', {method: 'DELETE'}).then(() => window.location.reload()); return false;"> | |
<input type='submit' value='초기화' style='background: red;'> | |
</form> | |
</body> | |
</html>` | |
}; | |
} | |
}, | |
post: async function() { | |
const content = isBase64Encoded ? Buffer.from(body, 'base64').toString('utf8') : body; | |
await storage.addMessage({ | |
channelName, timeEpoch, userAgent, sourceIp, content | |
}); | |
await backupStorage.addMessage({ | |
channelName, timeEpoch, userAgent, sourceIp, content | |
}); | |
return { | |
statusCode: 200, | |
body: '굿\n' | |
}; | |
}, | |
delete: async function() { | |
await storage.deleteAllMessages(); | |
return { | |
statusCode: 200, | |
body: '굿\n' | |
}; | |
} | |
}; | |
const handler = routes[method.toLowerCase()]; | |
if (handler == null) { | |
throw new Error(`Unknown method: [${method}]`); | |
} | |
return handler; | |
} | |
exports.handler = async (event) => { | |
const route = await getRoute(event); | |
return route(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment