diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..1a1c0ac --- /dev/null +++ b/infra/README.md @@ -0,0 +1,62 @@ +# infra + +## description + +서버 계정을 이전할 때 OS에 대한 기본 설정하는 부분을 편하게 set up 하기 위해 만든 ansible script 입니다. + +ansible 은 기본적으로 ssh 연결을 통해 스크립트를 실행하는 노드에서 host 에 적힌 노드들에게 연결을 보내는 형태입니다. 즉, 컨트롤 노드에만 ansible 을 설치하여 host 연결만 해두고 스크립트를 +실행하면 됩니다. + +### ansible.cfg + +### inventory + +``` +[wishboard-dev-server] # 제어할 노드의 이름 (playbook 파일의 hosts 에 연결되는 값) +127.0.0.1 # ip 주소 +``` + +### playbook + +- hosts: 플레이의 작업을 실행할 제어 노드를 지정 +- vars: 작업 수행 시 사용할 변수를 정의 + - tasks 에서 변수 사용 시 `{{ 변수명 }}` 처럼 사용 +- tasks: 실행할 작업들을 지정 (github actions 에 job) + - tasks 는 별도의 파일로 step 만 묶어서 생성해둘 수 있으나 현재는 한 파일에 다 정의해둔 상태 + +## started + +### 1) setting + +- local pc 에 ansible 설치 + +```bash +# mac 의 경우 +brew install ansible +ansible --version +``` + +- local pc 에서 ansible 을 이용해서 ssh 접속하기 위해 ssh 설정 필요 + +```bash +# 1. local pc 의 is_rsa.pub 키 값 복사 +# 2. ec2 접속해서 ~/.ssh/authorized_keys 에 추가 +cd ~/.ssh +vim authorized_keys +``` + +[참고자료](https://my-studyroom.tistory.com/entry/%EB%91%90-%EA%B0%9C%EC%9D%98-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B0%84%EC%9D%98-SSH-%EC%84%A4%EC%A0%95%EC%9D%84-%ED%86%B5%ED%95%B4-Ansible%EB%A1%9C-%ED%86%B5%EC%8B%A0-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0) + +### 2) execute + +```bash +cd ./infra +vim inventory # ip 값 수정 +``` + +파일 실행 구문 `ansible-playbook 파일명(.yaml/.yml)` + +```bash +# -v 옵션은 실행 내역을 자세히 보기 위함 +ansible-playbook -v hous-server-ubuntu-setup.yaml +``` diff --git a/infra/ansible.cfg b/infra/ansible.cfg new file mode 100644 index 0000000..a160f26 --- /dev/null +++ b/infra/ansible.cfg @@ -0,0 +1,10 @@ +[defaults] +inventory = ./inventory +remote_user = ubuntu +ask_pass = false + +[privilege_escalation] +become = true +become_method = sudo +become_user = root +become_ask_pass = fal diff --git a/infra/inventory b/infra/inventory new file mode 100644 index 0000000..a5fe58e --- /dev/null +++ b/infra/inventory @@ -0,0 +1,2 @@ +[wishboard-dev-server] +# 세팅할 서버 ip 주소 입력 diff --git a/infra/server-ubuntu-setup.yaml b/infra/server-ubuntu-setup.yaml new file mode 100644 index 0000000..d66b7df --- /dev/null +++ b/infra/server-ubuntu-setup.yaml @@ -0,0 +1,169 @@ +- hosts: hous-dev-server + + vars: + hostname: hous-dev-server + homedir: /home/ubuntu + swap_memory: 2G + + tasks: + - name: apt update + shell: + cmd: apt update + become: yes + + - name: set up timezone KST + shell: + cmd: timedatectl set-timezone Asia/Seoul + become: yes + + - name: set up hostname + shell: + cmd: hostnamectl set-hostname {{ hostname }} + become: yes + + - name: set up vim + shell: + cmd: apt install -y vim + become: yes + + - name: set up net-tools + shell: + cmd: apt install -y net-tools + become: yes + + - name: set up mysql client + shell: + cmd: apt-get install -y mysql-client + become: yes + + - name: check installed mysql client + shell: + cmd: mysql --version + + - name: set up redis-cli + shell: + cmd: apt install -y redis-tools + become: yes + + - name: check installed redis-cli + shell: + cmd: redis-cli --version + + - name: set up nginx + shell: + cmd: apt-get install -y nginx + become: yes + + - name: check installed nginx + shell: + cmd: nginx -v + + - name: set up codedeploy - install ruby-full + shell: + cmd: apt install -y ruby-full + chdir: '{{ homedir }}' + become: yes + + - name: set up codedeploy - install wget + shell: + cmd: apt install -y wget + chdir: '{{ homedir }}' + become: yes + + - name: set up codedeploy - install codedeploy-agent + shell: + cmd: wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install + chdir: '{{ homedir }}' + become: yes + + - name: set up codedeploy - chmod +x ./install + shell: + cmd: chmod +x ./install + chdir: '{{ homedir }}' + become: yes + + - name: set up codedeploy - install auto + shell: + cmd: ./install auto + chdir: '{{ homedir }}' + become: yes + + - name: check install codedeploy + shell: + cmd: service codedeploy-agent status + chdir: '{{ homedir }}' + become: yes + + - name: create swap memory + shell: + cmd: sudo fallocate -l {{ swap_memory }} /swapfile + become: yes + + - name: create swap memory file + shell: + cmd: chmod 600 /swapfile + become: yes + + - name: active swap memory 1 + shell: + cmd: mkswap /swapfile + become: yes + + - name: active swap memory 2 + shell: + cmd: swapon /swapfile + become: yes + + - name: apply created swap memory even after reboot + shell: + cmd: echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab + become: yes + + - name: check swap memory file + shell: + cmd: cat /etc/fstab + become: yes + + - name: check installed nvm + shell: + cmd: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash + args: + creates: '{{ ansible_env.HOME }}/.nvm/nvm.sh' + become: yes + + - name: check installed node 16.13.2 + shell: + cmd: source + + - name: apply to nvm script terminal + lineinfile: + dest: '{{ ansible_env.HOME }}/.bashrc' + line: 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && [ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"' + create: yes + insertafter: EOF + + - name: cat ~/.bashrc + shell: + cmd: cat "{{ ansible_env.HOME }}/.bashrc" + + - name: setup .bashrc + shell: + cmd: source ~/.bashrc + + - name: nvm install 16.13.2 + shell: + cmd: source {{ user_home }}/.nvm/nvm.sh && nvm install 16.13.2 + become: yes + + - name: 16.19.1 global setting + shell: + cmd: source {{ user_home }}/.nvm/nvm.sh && nvm use 16.13.2 && nvm alias default 16.13.2 + become: yes + + - name: checked node & npm version + shell: + cmd: node -v && npm -v + + - name: set up installed pm2 + shell: + cmd: npm install pm2 -g diff --git a/src/controllers/itemController.js b/src/controllers/itemController.js index 8ac4b34..d431319 100644 --- a/src/controllers/itemController.js +++ b/src/controllers/itemController.js @@ -8,6 +8,7 @@ const { ErrorMessage, } = require('../utils/response'); const { Strings } = require('../utils/strings'); +const { isValidDateFormat } = require('../utils/util'); const existEmptyData = (obj) => { if (obj.constructor !== Object) { @@ -25,7 +26,6 @@ module.exports = { if (!req.body.item_name) { throw new BadRequest(ErrorMessage.itemNameMiss); } - await Items.insertItem(req).then((itemId) => { if ( req.body.item_notification_date && @@ -33,19 +33,22 @@ module.exports = { ) { // TODO request DTO 분리하기 const itemNotiDate = req.body.item_notification_date; + const date = itemNotiDate.slice(0, 10); const minute = Number( itemNotiDate.slice(-5, itemNotiDate.length - 3), ); - if (minute === 0 || minute === 30) { - Noti.insertNoti(req, itemId).then(() => { - return res.status(StatusCode.CREATED).json({ - success: true, - message: SuccessMessage.itemAndNotiInsert, - }); - }); - } else { + if (!isValidDateFormat(date)) { throw new BadRequest(ErrorMessage.notiDateBadRequest); } + if (!(minute === 0 || minute === 30)) { + throw new BadRequest(ErrorMessage.notiDateMinuteBadRequest); + } + Noti.insertNoti(req, itemId).then(() => { + return res.status(StatusCode.CREATED).json({ + success: true, + message: SuccessMessage.itemAndNotiInsert, + }); + }); } else { return res.status(StatusCode.CREATED).json({ success: true, @@ -97,29 +100,32 @@ module.exports = { ) { // TODO request DTO 분리하기 const itemNotiDate = req.body.item_notification_date; + const date = itemNotiDate.slice(0, 10); const minute = Number( itemNotiDate.slice(-5, itemNotiDate.length - 3), ); - if (minute === 0 || minute === 30) { - //* item 수정 후 item_noti_~에 따라 알림여부를 noti에 수정/추가 - Noti.upsertNoti(req).then((state) => { - if (state === Strings.INSERT) { - return res.status(StatusCode.CREATED).json({ - success: true, - message: SuccessMessage.itemUpdateAndNotiInsert, - }); - } - if (state === Strings.UPSERT) { - return res.status(StatusCode.OK).json({ - success: true, - message: SuccessMessage.itemAndNotiUpdate, - }); - } - }); - } else { + if (!isValidDateFormat(date)) { throw new BadRequest(ErrorMessage.notiDateBadRequest); } + if (!(minute === 0 || minute === 30)) { + throw new BadRequest(ErrorMessage.notiDateMinuteBadRequest); + } + Noti.upsertNoti(req).then((state) => { + if (state === Strings.INSERT) { + return res.status(StatusCode.CREATED).json({ + success: true, + message: SuccessMessage.itemUpdateAndNotiInsert, + }); + } + + if (state === Strings.UPSERT) { + return res.status(StatusCode.OK).json({ + success: true, + message: SuccessMessage.itemAndNotiUpdate, + }); + } + }); } else { //* item 수정 후 item_noti_~가 null인 경우, noti에 존재하면 삭제 Noti.deleteNoti(req).then((result) => { diff --git a/src/models/user.js b/src/models/user.js index 678bccc..3b56eec 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -17,7 +17,7 @@ module.exports = { 'SELECT user_id, fcm_token FROM users WHERE fcm_token = ?'; const [selectRows] = await db.query(sqlSelect, [fcmToken]); - if (selectRows.affectedRows > 1) { + if (selectRows.length > 1) { const sqlUpdate = 'UPDATE users SET fcm_token = null WHERE user_id = ?'; const [updateRows] = await db.queryWithTransaction(sqlUpdate, [ selectRows[0].user_id, @@ -54,6 +54,26 @@ module.exports = { const checkPassword = bcrypt.compareSync(password, selectRows[0].password); + // fcm 토큰이 다른 유저에 존재한다면, null 처리 + const sqlSelectByFcmToken = + 'SELECT user_id, fcm_token FROM users WHERE fcm_token = ?'; + const [selectFcmToken] = await db.query(sqlSelectByFcmToken, [fcmToken]); + + console.log(selectFcmToken); + console.log(selectFcmToken.length >= 1); + + if (selectFcmToken.length >= 1) { + const sqlUpdate = 'UPDATE users SET fcm_token = null WHERE user_id = ?'; + const [updateRows] = await db.queryWithTransaction(sqlUpdate, [ + selectFcmToken[0].user_id, + ]); + + if (updateRows.affectedRows < 1) { + throw new NotFound(ErrorMessage.failedUpdateFcmToken); + } + } + + // 현재 유저로 fcm 토큰 갱신 if (selectRows[0].fcm_token !== fcmToken) { const sqlUpdate = 'UPDATE users SET fcm_token = ? WHERE user_id = ?'; const params = [fcmToken, selectRows[0].user_id]; diff --git a/src/utils/response.js b/src/utils/response.js index 101344a..26b7074 100644 --- a/src/utils/response.js +++ b/src/utils/response.js @@ -93,7 +93,8 @@ const ErrorMessage = { notiReadStateUpdate: '수정된 알림 읽음 상태 없음', notiInsert: '추가된 알림 없음', notiUpsert: '추가되거나 수정된 알림 없음', - notiDateBadRequest: '알림 날짜의 분은 00 또는 30', + notiDateMinuteBadRequest: '알림 날짜의 분은 00 또는 30', + notiDateBadRequest: '알림 날짜는 현재 날짜보다 같거나 미래만 가능', /* 사용자*/ validateNickname: '이미 존재하는 닉네임', diff --git a/src/utils/util.js b/src/utils/util.js index d8b2f50..640206d 100644 --- a/src/utils/util.js +++ b/src/utils/util.js @@ -4,4 +4,26 @@ const trimToString = (str) => { } }; -module.exports = { trimToString }; +const isValidDateFormat = (str) => { + const datePattern = /^\d{4}-\d{2}-\d{2}$/; + if (datePattern.test(str)) { + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + const currentMonth = currentDate.getMonth() + 1; // 월은 0부터 시작 + const currentDay = currentDate.getDate(); + + const inputDate = new Date(str); + const inputYear = inputDate.getFullYear(); + const inputMonth = inputDate.getMonth() + 1; // 월은 0부터 시작 + const inputDay = inputDate.getDate(); + + return ( + inputYear >= currentYear && + inputMonth >= currentMonth && + inputDay >= currentDay + ); + } + return false; +}; + +module.exports = { trimToString, isValidDateFormat };