[회고]

Creating the dots·2021년 11월 20일
2

project-2-느린우체통

목록 보기
8/10
post-thumbnail

느린우체통

프로젝트 일정

SR 10/25 ~ 11/7

1차 기획회의

  • 프로젝트 아이디어 선정

2차 기획회의

  • 느린우체통 컴포넌트 구성
  • 느린우체통 개발 역할 분배

3차 기획회의

  • 와이어프레임 시작
  • IA 시작
  • 필요한 라이브러리 모듈 정리

4차 기획회의

  • 와이어프레임 완성
  • IA 완성

목업 구현 11/8 ~ 11/13

기능 구현 11/14 ~ 11/17

발표준비 11/17 ~ 11/18

발표 11/19

소개

'언제 어디서든 누군가에게 지금의 감정과 기분을 기록해 미래의 누군가에게 마음을 전달할 수는 없을까?'라는 궁금증에서 시작해 예약메일을 전송하는 '느린 우체통'을 기획하게 되었습니다.

느린 우체통은 최대 1년 후까지 날짜를 선택해 텍스트와 이미지 등을 첨부해 예약메일을 전송할 수 있는 서비스입니다. 홈페이지에 가입해 예약메일을 전송하면, 즉시 수신자에게 알림메일이 전달되며, 예약된 날짜에도 알림메일을 전송합니다. 수신자는 홈페이지에 가입하여야 예약메일을 읽어볼 수 있습니다.

기능 및 구현

  • 홈화면

    • 느린우체통을 이용해 예약메일을 이용해 앞으로 도착예정인 편지 수가 홈화면에 나타납니다.
  • 회원가입

    • 메일 전송 서비스인만큼 이메일 인증(node-mailer) 과정을 거칩니다.
    • 같은 메일로는 중복가입이 불가능합니다.
    • 이름, 비밀번호(10자리~15자리 영문제한)를 작성해 가입합니다.
    • 회원가입이 완료되면 로그인 페이지로 이동합니다.
    • crypto 모듈을 사용해 유저가 입력한 비밀번호에 salt를 추가해 암호화된 비밀번호를 데이터베이스에 저장합니다. 유저가 입력한 비밀번호는 데이터베이스에 저장되지 않습니다.
  • 로그인 및 비밀번호 찾기

    • 로그인 시 jwt 토큰을 발급받아 쿠키에 저장합니다.
    • 이메일 인증과정을 거쳐 비밀번호를 변경할 수 있습니다.
  • 소셜로그인

    • 카카오 로그인 버튼 클릭시 회원가입이 되어있지 않다면, 닉네임 정보제공 동의(필수) 및 카카오톡 이메일, 비밀번호를 입력한 후 자동 로그인됩니다.
    • 카카오 로그인 버튼 클릭시 회원가입이 되어있다면, 자동 로그인됩니다.
    • 소셜로그인 유저는 비밀번호 변경이 불가능하며, 회원 탈퇴시 비밀번호가 아닌 카카오 이메일을 입력한 후 탈퇴합니다.
  • 메일작성

    • CKEditor5의 ClassicEditor를 사용해 Heading, 글자크기, 로컬 이미지 첨부 등의 기능이 포함되어있습니다.
    • connect-multiparty 모듈을 사용해 유저가 메일작성 페이지에서 업로드한 로컬 이미지를 서버에 저장하고, 에디터에서 이미지를 보여줍니다.
    • 미리보기 버튼 클릭시, html-react-parser로 메일 내용을 parse하여 보여줍니다.
    • 전송하기 버튼 클릭시, 수신이메일과 제목, 전송날짜, 내용 등의 입력정보를 확인한 후, 예약이 완료된 편지는 전송 취소가 불가합니다. 편지를 보내시겠습니까?라는 내용의 확인 모달창을 띄워줍니다.
    • 확인 모달창에서 보내기 버튼 클릭시, 편지 예약 모달창이 띄워집니다.
    • 예약 모달창에서 확인 버튼 클릭시, 받은 편지함으로 이동합니다.
  • 알림메일 발송

    • 메일 전송과 동시에 수신인에게 OO님께서 예약전송한 편지가 OOOO-OO-OO일에 도착할 예정입니다라는 내용의 알림메일이 전송됩니다.
    • 예약 날짜가 되면, 수신인에게 OO님께서 OOOO-OO-OO일에 예약한 메일이 도착했습니다라는 내용의 알림메일이 전송됩니다.
  • 예약메일 발송

    • node-schedule과 node-mailer를 이용해 매일 특정시간에 당일 예약된 편지가 있는지 확인하고, 당일로 예약된 편지에 대해서 수신자 이메일에 메일을 전송합니다.
  • 마이페이지

    • 활동로그 탭에서 유저가 주고받은, 예약한 모든 메일에 대한 정보를 확인할 수 있습니다.
    • 개인정보 수정탭에서 비밀번호를 변경할 수 있습니다.
    • 개인정보 수정탭에서 비밀번호 확인을 거친 후 회원탈퇴 할 수 있습니다.
  • 받은편지함

    • 받은 편지함에서는 발신자 정보와 전송날짜, 제목, 읽음여부를 확인할 수 있고, 각 편지 클릭 시 메일 내용을 확인할 수 있습니다.
    • 도착 예정함에서는 편지 도착까지 남은 날짜를 보여줍니다.
    • 받은편지가 있다면 빨간 알림 아이콘이 보이고, 편지함 클릭시 사라집니다.
  • 보낸편지함

    • 보낸 편지함도 받은 편지함과 마찬가지로 수신자 정보와 전송날짜, 제목, 읽음여부를 확인할 수 있고, 각 편지 클릭 시 메일 내용을 확인할 수 있습니다.
    • 전달 예정함에서는 편지 전달까지 남은 날짜를 보여줍니다.
    • 받은편지가 있다면 빨간 알림 아이콘이 보이고, 편지함 클릭시 사라집니다.

맡은 역할

이번 프로젝트에서는 모든 팀원이 풀스택으로 참여했고, 저는 메일작성 페이지와 마이페이지를 구현했습니다.

메일작성 페이지

  • 메일 작성 구현
    메일 작성페이지에서는 CKEditor5와 connect-multiparty를 사용해 메일작성과 이미지 업로드 기능을 구현했습니다. 유저가 로컬 이미지를 첨부한 경우, 서버의 uploads 폴더에 저장됩니다.

  • 안내메일 구현
    전송하기 버튼 클릭시 수신자 이메일에게 안내메일을 전송합니다. 메일전송은 nodemailer를 사용해 구현했으며, 메일 내용은 campain monitor에서 이메일 템플릿을 수정해 사용했습니다.

  • 예약메일 구현
    예약메일 서비스인만큼, 예약된 날짜가 되면 수신자에게 예약안내 메일을 전송합니다. 매일 같은 시간에 예약메일을 전송하는 기능은 node-schedule을 사용해 구현했습니다. 서버의 index.js에서 매일 0시 0분에 데이터베이스에서 오늘 날짜로 예약된 메일 데이터를 가져옵니다. 각각의 데이터의 이름, 수신자이메일, 전송날짜 정보를 가져와 arrivalAlert 함수에 인자로 전달합니다. arrivalAlert 함수에서는 nodemailer를 사용해 인자로 받아온 정보를 활용해 안내메일을 전송합니다.

// index.js
const rule = new schedule.RecurrenceRule();
rule.hour = 0;
rule.minute = 0;

schedule.scheduleJob(rule, async function sendAlertMail() {
  const realTime = getDateStr(new Date());
  try {
    console.log('it works');
    const sql = `SELECT users.name, mails.created_at, mails.receiverEmail FROM mails INNER JOIN users ON users.email=mails.writerEmail WHERE reserved_at=?`;
    const [rows, fields, error] = await db.query(sql, [realTime]);
    if (error) {
      console.log(error);
    } else {
      for (let i = 0; i < rows.length; i++) {
        arrivalAlert(
          rows[i]['name'],
          rows[i]['receiverEmail'],
          rows[i]['created_at']
        );
        console.log('sending alert mail');
      }
    }
  } catch (error) {
    throw error;
  }
});

마이페이지

  • 마이페이지의 활동로그 구현
    활동로그는 보낸편지와 받은편지로 구분됩니다. 각각은 탭으로 구분되어 있으며, 클라이언트에서 query로 각각 writerEmail과 receiverEmail을 보냅니다. 서버는 데이터베이스에 보낸편지 활동로그에 대해 작성자 이메일과 일치하는 데이터를 모두 가져오고, 받은편지 활동로그에 대해 수신자 이메일과 일치하는 데이터를 모두 가져와 리스트로 보여줍니다.

    다음은 receivedlogs.js의 코드입니다. sentlogs.js에서는 쿼리로 전달받은 이메일이 writerEmail이라는 점만 다릅니다.
module.exports = async (req, res) => {
  try {
    const receiverEmail = req.query.receiverEmail;
    const sql =
      'SELECT id, title, reserved_at FROM mails WHERE receiverEmail = ? ORDER BY reserved_at';
    const params = [receiverEmail];
    const [rows, fields, err] = await db.query(sql, params);

    if (err) {
      console.log(err);
      return res.status(404).send('실패');
    } else {
      return res.status(200).json({ data: rows });
    }
  } catch (err) {
    throw err;
  }
};
  • 개인정보 수정 구현
    자체가입 유저는 비밀번호를 변경할 수 있으며, 비밀번호 유효성 검사를 거쳐 10자리이상 15자리 이하이면서 새로운 비밀번호와 확인 비밀번호가 일치하는 경우에만 변경이 가능합니다. 두 조건 중 하나라도 만족하지 않는다면, 입력한 항목을 확인하라는 알림창이 뜹니다.

    비밀번호 변경시 crypto 모듈을 이용해 서버에서 새로운 salt를 생성해 유저가 입력한 비밀번호와 salt를 합해 암호화된 비밀번호를 생성하고, 데이터베이스의 salt와 암호화된 비밀번호를 업데이트합니다.

    소셜로그인 유저는 비밀번호 변경이 불가합니다. redux로 관리되는 oauth여부를 useSelector로 가져와, oauth가 true인 경우, 비밀번호 변경 컴포넌트가 보이지 않도록 구현했습니다.

  • 회원탈퇴
    자체가입 유저는 회원탈퇴 시 비밀번호를 입력합니다. 비밀번호를 입력해 확인 버튼 클릭 시, 서버에서 데이터베이스로부터 암호화된 salt와 암호화된 비밀번호를 가져옵니다. 이후, 유저가 입력한 비밀번호를 가져온 salt로 암호화해 데이터베이스에서 가져온 암호화된 비밀번호와 일치하는지 확인합니다.

    일치하지 않는다면, 비밀번호를 확인하라는 알림창이 뜹니다.
    일치한다면, 회원탈퇴되고, 홈페이지로 이동합니다.

    다음은 자체가입 유저 회원탈퇴 로직입니다.

const db = require('../../db');
const crypto = require('crypto');

module.exports = async (req, res) => {
  try {
    const { email, password } = req.body;
    //console.log(email, password);
    const sql1 = 'SELECT salt,password as decoded FROM users WHERE email=?';
    const params1 = [email];
    const [row1, field1, err1] = await db.query(sql1, params1);
    if (err1) {
      console.log(err1);
      return res.status(404).json({ message: 'error' });
    }
    //console.log(row); [{salt:'111', decoded:'helloworld'}]
    const hashPassword = crypto
      .createHash('sha512')
      .update(password + row1[0]['salt'])
      .digest('hex');

    if (row1[0]['decoded'] === hashPassword) {
      const sql2 = 'DELETE FROM users WHERE password=?';
      const params2 = [hashPassword];
      const [row2, field2, err2] = await db.query(sql2, params2);
      if (err2) {
        console.log(err2);
        return res.status(404).json({ message: 'error' });
      } else {
        console.log('탈퇴됨');
        return res.status(200).json({ message: 'success' });
      }
    } else {
      console.log('비밀번호 틀림');
      return res.json({ message: 'not authorized' });
    }
  } catch (err) {
    throw err;
  }
};

tech-stack

이번 프로젝트를 진행하며

체계적인 프로젝트 태스크 관리

지난 다가치 프로젝트 때와는 다르게, 팀규칙부터 commit, lint, pr 규칙 등을 세부적으로 정했습니다. 해야할 모든 태스크를 미리 태스크카드로 만들어두었고, 매일 어떤 태스크카드를 하고있는지, 끝냈는지 등을 칸반보드로 In-Progress, Done으로 구분해 다른 팀원들과 진행사항을 공유했습니다.

커밋 메시지와 pr 내용을 작성하는 것이 처음에는 익숙하지 않았지만, 함께 팀원들과 merge할때 어떤 변경내역이 있는지 한눈에 확인할 수 있어 유용하다고 느꼈습니다.

전원 풀스택

이번 프로젝트에서는 팀원 4명이 모두 풀스택을 맡았습니다. 각자 페이지를 맡아 목업과 기능을 구현했습니다. 팀원 모두가 프론트와 백을 경험해볼 수 있었습니다. 자신이 맡은 페이지에 대해 더욱 책임감을 갖고 마무리할 수 있어 좋았습니다. 하지만, api를 작성할때와 css에 있어서 통일성이 부족하다는 느낌을 받았습니다. 다음 프로젝트 때에는 프론트와 백을 구분해 프로젝트를 진행할 예정입니다.

SR단계의 중요성

이번 프로젝트는 코드스테이츠에서 주어진 시간보다 조금 일찍 시작했습니다. 이미 팀원이 정해진 상태였고, 지난 다가치 토이 프로젝트를 마친 후 먼저 아이디어 회의를 시작했기 때문에 코드스테이츠 스케줄보다 일주일 정도 먼저 시작했습니다.

그래서 코드스테이츠로부터 SR단계에 대한 공지를 받았을때, 와이어프레임과 IA 등 이미 완성된 부분이 있어 시간을 단축할 수 있었지만, 필수로 해야 하는 부분들에 대해 (ex. gitbook등을 활용한 api작성) 추가적으로 진행했습니다.

그래서 조금 일찍 목업구현을 시작했는데, SR 과정을 충분히 하지 못했다는 생각이 들었습니다. 특히 메일작성 페이지에서는 구현해야할 기능이 많고, 사용할 스택 등이 여러개가 필요했지만 이에 대해 충분히 고민하는 시간을 갖지 못한 것 같습니다.

그래서 기능구현을 하며, CKEditor4에서 CKEditor5로 변경하는 등 중간에 수정하는 일들이 발생했습니다.

그래서 다음 프로젝트 때에는 SR 단계 동안 사용할 스택에 대해서도 충분히 찾아보고 고민하는 시간을 갖고, 로직을 미리 생각해 둔 뒤부터 목업 구현을 진행하려고 합니다.

profile
어제보다 나은 오늘을 만드는 중

0개의 댓글