Skip to content

Instantly share code, notes, and snippets.

@potados99
Last active July 29, 2023 14:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save potados99/7006f5bd7a60822791cdb601ce520f6a to your computer and use it in GitHub Desktop.
Save potados99/7006f5bd7a60822791cdb601ce520f6a to your computer and use it in GitHub Desktop.
임시 데이터 수집기(collect.potados.com) AWS Lambda 구현
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