Skip to content

Commit

Permalink
➕ [ADD]: DB와의 연결고리
Browse files Browse the repository at this point in the history
  • Loading branch information
eunji8784 committed Dec 24, 2021
1 parent 263f91e commit 582152b
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 0 deletions.
81 changes: 81 additions & 0 deletions 5st-seminar/functions/db/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 필요한 모듈들
const functions = require('firebase-functions');
const { Pool, Query } = require('pg');
const dayjs = require('dayjs');
const dotenv = require('dotenv');
dotenv.config();

// DB Config (유저, 호스트, DB 이름, 패스워드)를 로딩해줍시다.
const dbConfig = require('../config/dbConfig');

// NODE_ENV라는 글로벌 환경변수를 사용해서, 현재 환경이 어떤 '모드'인지 판별해줍시다.
let devMode = process.env.NODE_ENV === 'development';

// SQL 쿼리문을 콘솔에 프린트할지 말지 결정해주는 변수를 선언합시다.
const sqlDebug = true;

// 기본 설정에서는 우리가 실행하게 되는 SQL 쿼리문이 콘솔에 찍히지 않기 때문에,
// pg 라이브러리 내부의 함수를 살짝 손봐서 SQL 쿼리문이 콘솔에 찍히게 만들어 줍시다.
const submit = Query.prototype.submit;
Query.prototype.submit = function () {
const text = this.text;
const values = this.values || [];
const query = text.replace(/\$([0-9]+)/g, (m, v) => JSON.stringify(values[parseInt(v) - 1]));
// devMode === true 이면서 sqlDebug === true일 때 SQL 쿼리문을 콘솔에 찍겠다는 분기입니다.
devMode && sqlDebug && console.log(`\n\n[👻 SQL STATEMENT]\n${query}\n_________\n`);
submit.apply(this, arguments);
};

// 서버가 실행되면 현재 환경이 개발 모드(로컬)인지 프로덕션 모드(배포)인지 콘솔에 찍어줍시다.
console.log(`[🔥DB] ${process.env.NODE_ENV}`);

// 커넥션 풀을 생성해줍니다.
const pool = new Pool({
...dbConfig,
connectionTimeoutMillis: 60 * 1000,
idleTimeoutMillis: 60 * 1000,
});

// 위에서 생성한 커넥션 풀에서 커넥션을 빌려오는 함수를 정의합니다.
// 기본적으로 제공되는 pool.connect()와 pool.connect().release() 함수에 디버깅용 메시지를 추가하는 작업입니다.
const connect = async (req) => {
const now = dayjs();
const string =
!!req && !!req.method
? `[${req.method}] ${!!req.user ? `${req.user.id}` : ``} ${req.originalUrl}\n ${!!req.query && `query: ${JSON.stringify(req.query)}`} ${!!req.body && `body: ${JSON.stringify(req.body)}`} ${
!!req.params && `params ${JSON.stringify(req.params)}`
}`
: `request 없음`;
const callStack = new Error().stack;
const client = await pool.connect();
const query = client.query;
const release = client.release;

const releaseChecker = setTimeout(() => {
devMode
? console.error('[ERROR] client connection이 15초 동안 릴리즈되지 않았습니다.', { callStack })
: functions.logger.error('[ERROR] client connection이 15초 동안 릴리즈되지 않았습니다.', { callStack });
devMode ? console.error(`마지막으로 실행된 쿼리문입니다. ${client.lastQuery}`) : functions.logger.error(`마지막으로 실행된 쿼리문입니다. ${client.lastQuery}`);
}, 15 * 1000);

client.query = (...args) => {
client.lastQuery = args;
return query.apply(client, args);
};
client.release = () => {
clearTimeout(releaseChecker);
const time = dayjs().diff(now, 'millisecond');
if (time > 4000) {
const message = `[RELEASE] in ${time} | ${string}`;
devMode && console.log(message);
}
client.query = query;
client.release = release;
return release.apply(client);
};
return client;
};

module.exports = {
connect,
};
4 changes: 4 additions & 0 deletions 5st-seminar/functions/db/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
userDB: require('./user'),
postDB: require('./post'),
};
104 changes: 104 additions & 0 deletions 5st-seminar/functions/db/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const _ = require('lodash');
const convertSnakeToCamel = require('../lib/convertSnakeToCamel');

const getAllPosts = async (client) => {
const { rows } = await client.query(
`
SELECT * FROM post p
WHERE is_deleted = FALSE
`,
);
return convertSnakeToCamel.keysToCamel(rows);
};

const getPostById = async (client, postId) => {
const { rows } = await client.query(
`
SELECT * FROM post p
WHERE id = $1
AND is_deleted = FALSE
`,
[postId],
);
return convertSnakeToCamel.keysToCamel(rows[0]);
};

const addPost = async (client, userId, title, content) => {
const { rows } = await client.query(
`
INSERT INTO post
(user_id, title, content)
VALUES
($1, $2, $3)
RETURNING *
`,
[userId, title, content],
);
return convertSnakeToCamel.keysToCamel(rows[0]);
};

const updatePost = async (client, title, content, postId) => {
const { rows: existingRows } = await client.query(
`
SELECT * FROM post p
WHERE id = $1
AND is_deleted = FALSE
`,
[postId],
);

if (existingRows.length === 0) return false;

const data = _.merge({}, convertSnakeToCamel.keysToCamel(existingRows[0]), { title, content });

const { rows } = await client.query(
`
UPDATE post p
SET title = $1, content = $2, updated_at = now()
WHERE id = $3
RETURNING *
`,
[data.title, data.content, postId],
);
return convertSnakeToCamel.keysToCamel(rows[0]);
};

const deletePost = async (client, postId) => {
const { rows } = await client.query(
`
UPDATE post p
SET is_deleted = TRUE, updated_at = now()
WHERE id = $1
RETURNING *
`,
[postId],
);

return convertSnakeToCamel.keysToCamel(rows[0]);
};

const getPostsByUserId = async (client, userId) => {
const { rows } = await client.query(
`
SELECT * FROM post
WHERE user_id = $1
AND is_deleted = FALSE
`,
[userId],
);
return convertSnakeToCamel.keysToCamel(rows);
};

const getPostsByUserIds = async (client, userIds) => {
if (userIds.length < 1) return [];
const { rows } = await client.query(
`
SELECT * FROM post
WHERE user_id IN (${userIds.join()})
AND is_deleted = FALSE
`,
);
return convertSnakeToCamel.keysToCamel(rows);
};

module.exports = { getAllPosts, getPostById, addPost, updatePost, deletePost, getPostsByUserId, getPostsByUserIds };
108 changes: 108 additions & 0 deletions 5st-seminar/functions/db/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const _ = require('lodash');
const convertSnakeToCamel = require('../lib/convertSnakeToCamel');

const getAllUsers = async (client) => {
const { rows } = await client.query(
`
SELECT * FROM "user" u
WHERE is_deleted = FALSE
`,
);
return convertSnakeToCamel.keysToCamel(rows);
};

const getUserById = async (client, userId) => {
const { rows } = await client.query(
`
SELECT * FROM "user" u
WHERE id = $1
AND is_deleted = FALSE
`,
// client.query()의 두 번째 파라미터에는, 쿼리문에 집어넣고 싶은 변수들의 배열을 적습니다.
// $1에는 배열의 첫번째 변수가, $2에는 배열의 두 번째 변수... 이런 식으로 쿼리문에 변수가 들어가게 됩니다!
[userId],
);
// 위의 getAllUsers와는 달리, 이번에는 유저 하나만 가져오고 싶기 때문에 rows[0]만 리턴해 줍니다.
return convertSnakeToCamel.keysToCamel(rows[0]);
};

const getUserByIdFirebase = async (client, idFirebase) => {
const { rows } = await client.query(
`
SELECT * FROM "user" u
WHERE id_firebase = $1
AND is_deleted = FALSE
`,
[idFirebase],
);
return convertSnakeToCamel.keysToCamel(rows[0]);
};

const getUserByEmail = async (client, email) => {
const { rows } = await client.query(
`
SELECT * FROM "user" u
WHERE email = $1
AND is_deleted = FALSE
`,
[email],
);
return convertSnakeToCamel.keysToCamel(rows[0]);
};

const updateUser = async (client, username, phone, userId) => {
const { rows: existingRows } = await client.query(
`
SELECT * FROM "user"
WHERE id = $1
AND is_deleted = FALSE
`,
[userId],
);

if (existingRows.length === 0) return false;

const data = _.merge({}, convertSnakeToCamel.keysToCamel(existingRows[0]), { username, phone });

const { rows } = await client.query(
`
UPDATE "user" u
SET username = $1, phone = $2, updated_at = now()
WHERE id = $3
RETURNING *
`,
[data.username, data.phone, userId],
);
return convertSnakeToCamel.keysToCamel(rows[0]);
};

const deleteUser = async (client, userId) => {
const { rows } = await client.query(
`
UPDATE "user" u
SET is_deleted = TRUE, updated_at = now()
WHERE id = $1
RETURNING *
`,
[userId],
);

return convertSnakeToCamel.keysToCamel(rows[0]);
};

const addUser = async (client, email, username, phone, idFirebase) => {
const { rows } = await client.query(
`
INSERT INTO "user" u
(email, username, phone, id_firebase)
VALUES
($1, $2, $3)
RETURNING *
`,

[email, username, phone, idFirebase],
);
return convertSnakeToCamel.keysToCamel(rows[0]);
};

module.exports = { getAllUsers, getUserById, getUserByIdFirebase, getUserByEmail, updateUser, deleteUser, addUser };

0 comments on commit 582152b

Please sign in to comment.