Gila - 채팅 권한 설정, 채팅 멤버 표시

박상준·2024년 9월 11일

Gila

목록 보기
23/25

2% 부족한 채팅

채팅 기능이 구현은 되었지만 아쉬운 부분이 분명히 존재한다.

  1. 채팅 참여 유저
  2. 채팅 초대 받지 못한 유저 처리

우선 채팅에 접속중인 유저는 확인이 가능하지만 누가 이 채팅에 참여하는지는 확인이 안된다. 심지어 활동을 만든 사람마저도 확인이 안된다. 그러다보니 누가 누구인지 채팅을 치지 않는 이상 확인이 안되는 문제가 있다.
두번째로 채팅 채널에 허용되지 않는 유저가 접근했을때 막을 방법이 없다. 나도 이 문제를 ably.io를 통해서 처리하는 방법이 없을까 고민해보고 공식문서도 찾아봤지만 결국 찾지 못했다. 그래서 이건 프론트에서 막아주는게 맞다고 판단했다.

채팅 참여 유저 표시

다행히 위의 두 문제 모두 저번 포스트와 비슷한 방식으로 처리해주면 된다.

model Activity {
  id           String   @id @default(cuid())
  title        String
  description  String
  startDate    DateTime
  endDate      DateTime
  maximumCount Int
  thumbnails   String[]
  tags         String[]
  location     String

  userId String
  user   User   @relation(fields: [userId], references: [id])

  reviews          Review[]
  activityRequests ActivityRequest[]

  views Int @default(0)

  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt
  favorites Favorite[]
}

활동 모델안에 있는 activityRequests를 활용해주도록 하겠다. 우선 채팅에 참여하는 유저는 신청을 보내고 생성한 유저가 approve해준 상태이다. 즉, 신청 리스트중에서 status가 approve인 유저만 확인해주면 된다.

...
activityRequests: {
  where: { status: 'APPROVE' },
  include: {
    requestUser: {
      select: {
        id: true,
        nickname: true,
        email: true,
        image: true,
        tags: true,
        createdAt: true,
      },
    },
    activity: true,
  },
},
...

이제 채팅 채널 데이터를 불러오는 로직에서 해당 부분을 추가해주면 된다. 그러면 status가 approve인 신청만 가져오게 되고 해당 데이터의 유저 데이터도 함께 가져온다.

export default async function Page({ params }: { params: { channel: string } }) {
  const user = await getCurrentUser();
  const activity = await getChannelById(params.channel);

  return (
    <ChatPage
      channel={params.channel}
      user={user}
      activityTitle={activity.title}
      member={activity.activityRequests} // 참여하는 유저 리스트
      owner={activity.user}
    />
  );
}

이제 채팅 페이지에 받은 데이터를 그대로 넘겨주면 된다. 그리고 추가적으로 해당 활동의 approve된 신청내역만 가져오면 활동을 생성한 유저는 확인이 안되기에 활동을 만든 유저 데이터도 추가적으로 넘겨줬다.

// ChatOnlineList.tsx
...
<ul className="flex flex-col mt-2 gap-2">
  <li>
    <div className="flex items-center gap-2">
      <Circle
        size={8}
        fill={ownerConnected ? '#01FE19' : '#848484'}
        color={ownerConnected ? '#01FE19' : '#848484'}
      />
      {owner.nickname}
      <Crown size={15} fill="#ffcf00" color="#ffcf00" />
    </div>
  </li>
  {member.map((item: RequestWithReqUser) => (
    <li key={item.id}>
      <ChatOnlineUser user={item} connectedUser={users} />
    </li>
  ))}
</ul>
...

해당 데이터를 이제 리스트로 렌더링해주면 된다. 최상단에는 생성한 유저와 유저 접속 상태를 표시해준다. 그 밑으로는 모든 참여 유저를 렌더링해주고 접속 여부를 props로 전달해주면 된다.

export default function ChatOnlineUser({ user, connectedUser }: Props) {
  const [isConnected, setIsConnected] = useState(false);

  useEffect(() => {
    for (let i = 0; connectedUser.length > i; i++) {
      if (user.requestUser.id === connectedUser[i].clientId) {
        setIsConnected(true);
        break;
      }
    }
  }, [connectedUser, user]);

  return (
    <div className="flex items-center">
      <Circle
        className="mr-2"
        size={8}
        fill={isConnected ? '#01FE19' : '#848484'}
        color={isConnected ? '#01FE19' : '#848484'}
      />
      {user.requestUser.nickname}
    </div>
  );
}

추가로 처리해줘야하는 부분은 approve된 리스트와 접속중인 유저 리스트의 순서가 같지 않다는 점이다. 그래서 해당 컴포넌트에서 참여 유저id와 접속 유저 id를 전부 비교해서 접속 여부를 세팅해준다.

  useEffect(() => {
    for (let i = 0; users.length > i; i++) {
      if (users[i].clientId === owner.id) {
        setOwnerConnected(true);
        break;
      }
    }
  }, [owner, users]);

이건 채팅 생성 유저도 같다. 다행인점은 접속 유저 데이터의 양이 많지가 않아서 반복문으로 인한 속도 저하가 없다는 점이다. 물론 성능이 안좋은 하드웨어라면 어려울수도 있겠지만...지금 할수 있는 방법은 이게 최선이다.

이제 해당 액티비티에 참여중인 유저를 확인할 수 있고 접속 여부도 확인할 수 있다.

채팅 접속 제한

전의 코드는 /chat/[channelId]의 주소로 접속하면 제한없이 모두 접속이 가능했다. 물론 해당 id값을 우연히 입력해서 들어올수는 없을 것이다. 하지만 만약 누군가 해당 주소를 공유한다면 아무나 접속이 가능하기 때문에 활동 신청의 의미가 없다고 생각했다. 그래서 신청을 수락한 유저가 아니라면 다른 페이지로 이동시켜주도록 해야한다.

  useEffect(() => {
    let acceptUser = false;
    for (let i = 0; member.length > i; i++) {
      if (member[i].requestUserId === user.id) {
        acceptUser = true;
        break;
      }
    }
    if (user.id === owner.id) {
      acceptUser = true;
    }
    if (!acceptUser) {
      alert('접근 권한이 없는 채팅입니다. 채팅리스트로 이동해주세요.');
      router.replace('/dashboard/my-chat');
    }
  }, [member, owner, router, user]);

그래서 간단하게 반복문을 활용해서 신청 상태가 approve인 유저와 활동 생성 유저는 접속가능하도록 해주고 만약 신청 리스트에 현재 유저가 없다면 alert를 보여주고 본인 채팅 페이지로 이동시켜줬다.

이제 신청 수락된 유저만 접근이 가능하도록 만들어줬다.

신청 수락 버그

이 기능을 만져보다보니 약간의 버그가 존재했다. 활동에는 maximumCount값이 있는데 해당 값이 아무런 효과가 없었다. 그냥 말그대로 숫자만 들어간 상태이다. 원인은 해당 Count값과 활동에 참여하는 유저간의 관계성이 없기 때문이다. 그래서 maximumCount가 3일때 본인 제외 3명이상 수락을 해도 계속 수락이 가능했다. 내가 이제와서 prisma 관계형 모델을 다 건들지는 못하기 때문에 우선적으로 프론트에서 방지해보고자 한다.

...
const approvedRequest = await db.activity.findUnique({
  where: { id: activityId },
  include: {
    activityRequests: { where: { status: 'APPROVE' } },
  },
});

if (
  approvedRequest &&
  approvedRequest.activityRequests.length === approvedRequest.maximumCount
) {
  return { success: false, message: '최대 인원 이상 요청을 수락할 수 없습니다.' };
}
...

해당 로직은 activityRequest를 approve하는 로직이다. 이제 여기에서 maximumCount값을 활용해서 제한을 두면 된다. 이젠 무제한으로 approve를 할수 없도록 수정해줬다.

마무리

내가 할수 있는 최대한을 했다고 생각한다. 양방향 실시간 소통도 되고 현재 접속 유저, 참여 유저, 접근 제한까지 왠만한 채팅과 비슷한 기능이 구현되었다. 나름 뿌듯하다. 그리고 이 기능을 구현하면서 발견한 버그 해결까지 마음에 든다.

이제 서비스가 나름의 구실을 갖춰간다. 작은 디테일을 수정해주고 전체적으로 리팩토링을 해주고 싶다. 코드가 아무래도 막무가내로 작성된 부분들이 분명 존재하기에 아직 멀었다... 조금만 더 힘내보자!

profile
개인 블로그 플렛폼도 운영중입니다(https://blog-park.vercel.app/)

0개의 댓글