왼손잡이해방연대 아지트

사용자 확인중...

일지 294호의 개정판

본문 보기

아지트 개발일지 4 - 이름 변경

여러분이 기다리시던 기능이 찾아왔습니다. 바로 이름 변경입니다.

Add Settings Page and User Name Change

상단 메뉴에 "설정" 버튼이 생겼습니다. 들어가보시면 이름을 바꿀 수 있는 폼이 나옵니다. 한 번 변경하고 나면 일주일 뒤에나 바꿀 수 있으니 신중하세요!

마이그레이션 코드를 한번 보겠습니다.

exports.up = (pgm) => {
  // 기존 사용자들은 0으로 초기화
  pgm.addColumn("users", {
    name_updated_at: {
      type: "timestamp",
      notNull: true,
      default: pgm.func("TO_TIMESTAMP(0)"),
    },
  });
  // 새로운 사용자들은 생성 시간으로 초기화
  pgm.alterColumn("users", "name_updated_at", {
    default: pgm.func("CURRENT_TIMESTAMP"),
  });
};

가장 마지막으로 이름을 바꾼 시점을 기록하는 name_updated_at 컬럼을 추가하여 이름 변경 주기를 기록하도록 했습니다. 새로운 사용자는 처음 가입하고 일주일이 지나야 이름을 바꿀 수 있습니다. 이는 데이터베이스 컬럼에 DEFAULT 속성을 넣어서 구현할 수 있죠. 하지만 그렇게만 하면 기존 사용자들은 지금부터 일주일간 이름을 바꿀 수 없습니다. 여러분이 고대하던 기능이란 걸 알기에, 즉시 기능을 제공하고 싶었습니다.

여러가지 방법이 있겠지만, 여기서는 직접 쿼리문을 작성하지 않고 node-pg-migrate에서 제공하는 테이블 수정 함수만 쓰는 방법을 쓰기로 했습니다. 컬럼을 추가하는 순간에는 일주일 이상으로 오래된 날짜, 구체적으로는 1970년 1월 1일이라는 이른바 epoch 날짜를 대충 넣었습니다. 이 값은 기존 로우들의 name_updated_at 컬럼의 값으로 쓰입니다. 이제부터 생길 로우에 대해서는 생성 당시의 값이 자동으로 들어가도록 DEFAULT 속성을 CURRENT_TIMESTAMP로 변경하고 마이그레이션을 마칩니다.

개인적으로 마이그레이션을 직접 작성하는 것의 한 장점은 이와 같이 새로 추가되는 값들을 섬세하고 조작할 수 있다는 점이라고 생각합니다. 예전에 썼던 Prisma와 같은 도구에서는 테이블 스키마에서 변한 부분을 기계적으로 적용하는 것이 기본 동작이었습니다. 그런 도구를 쓰면서는 이렇게 데이터베이스를 유연하게 다룰 생각은 하지 못했었습니다.

웹 구현 파트는 그냥 상식적으로 짰으니 넘어가겠습니다.

ᅟAdd User Page

이름 변경은 반드시 사용자 페이지 개설과 동시에 구현해야겠다고 생각하고 있었습니다. 기존 아지트에는 이름 외에 사용자를 구별할만한 방법이 없었기 때문입니다. 글의 목록을 보고 대충 알아볼 수는 있지만 사용자에게 불시에 과거와 현재의 목록을 비교하는 기억력을 발휘하도록 압박하는 것은 부조리합니다. 적어도 구별할 수 있는 최소한의 값이 있어야 합니다.

최고의 방법은 아니지만, 이 아지트에서는 PK 값으로 구별하도록 하겠습니다. PK가 뭐냐하면 primary key의 머릿글자입니다. 데이터베이스에서 쓰는 용어로, 로우마다 서로 다른 유일한 값을 가지도록 하고 해당 테이블에서 특정 로우를 찾을 때 기본적으로 사용할 컬럼을 지정할 때 사용합니다. 대충 아이디 같은 겁니다. 생각해보니 이미 가입 페이지에서 아이디라고 써놓았네요. 이상한 말 만들지 말고 그냥 아이디로 쓰겠습니다.

사용자 페이지에는 이름, 아이디, 직책이 나옵니다. 그리고 그 밑으로 작성한 모든 일지와 댓글을 볼 수 있습니다. 댓글 목록에는 각 댓글이 달린 일지로 가는 링크가 있는데, 그걸 누르면 그 일지에서 댓글이 노랗게 표시됩니다.

이거 만들면서 코드 상에서 재밌는 건... 특별히 없었지만 그나마 댓글 목록을 컴포넌트로 만들었던 것 정도? 원래는 일지 읽기 페이지에 바로 들어가있었는데 사용자 페이지에서도 쓰게 됐으니까 컴포넌트로 뺐습니다. 이런 거 컴포넌트로 만들 때 예전에는 함수 하나로 짰었습니다. 이런 식이죠:

export default funciton CommentList(p: { comments: Comment[] }) {
  return (
    <ul>
      {p.comments.length === 0
        ? <p>empty</p>
        : p.comments.map(comment => <li>...</li>}
    </ul>
  );
}

그런데 요즘은 부분별로 다 떨어트려서 이렇게 조립식으로(compositionally) 짭니다:

export function Container(p: { children: React.ReactNode }) {
  return <ul>{p.children}</ul>;
}
export function Item(p: { item: Comment }) {
  return <li>...</li>;
}
export function Empty() {
  return <p>empty</p>;
}

복층적인 로직은 사용하는 부분에서 구현하는 식이죠:

<CommentList.Container>
  {comments.length === 0
    ? <CommentList.Empty />
    : comments.map(comment => <CommentList.Item item={comment} />}
</CommentList.Container>

이러면 나중에 비슷한데 약간 다른 페이지를 만들 때 이리저리 조립해서 쓰기가 좋습니다. 이미 일지 목록 컴포넌트를 비슷하게 짜서 소개 페이지의 인기 있는 일지 목록, 모든 일지 목록, 초안 목록, 일지 페이지 하단의 다음/이전 일지 링크를 모두 기본 컴포넌트 몇 개를 조립해서 만들었습니다. 시간이 늦었으니 나중에 기회가 되면 소개하지요.

마무리

이렇게 오늘은 중요한 기능 몇 가지를 추가했습니다. 앞으로 이런 커밋이 더 많아질 예정입니다~