feat: 화상회의 단계 2 — 권한 분기(생성자=진행자)

방을 처음 만든 사용자를 진행자로 보고, 역할별로 LiveKit 권한을 차등 부여한다.

백엔드
- meeting_rooms 엔티티/마이그레이션: 방 이름(고유)→진행자(host) 보관
- 토큰 발급 시 역할 결정: 방이 없으면 생성자=진행자, 있으면 생성자 일치로 판정
  (동시 생성 경쟁은 name unique 충돌 후 재조회로 처리)
- grant 차등: 진행자 roomAdmin+발행/구독, 일반 발행/구독
- 역할을 토큰 metadata({"role":...})에 실어 다른 참여자도 진행자 식별
- 응답에 role 추가

프론트
- useMeeting: role 반환
- ParticipantTile: metadata 파싱 → 진행자 타일에 "진행자" 배지
- MeetingPage: 본인이 진행자면 헤더에 역할 칩 표시

방 목록/예약/참여 제한 UI 는 단계 4 로 분리.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 17:51:21 +09:00
parent 85b6b65e24
commit f519723260
7 changed files with 173 additions and 10 deletions
@@ -10,6 +10,18 @@ const videoEl = ref<HTMLVideoElement | null>(null)
const audioEl = ref<HTMLAudioElement | null>(null)
const hasVideo = ref(false)
const micOn = ref(false)
// 진행자 여부 — 토큰 metadata({"role":"host"})에서 파생
const isHost = ref(false)
// 참여자 metadata 문자열에서 진행자 여부 파싱
function parseHost(metadata: string | undefined): boolean {
if (!metadata) return false
try {
return (JSON.parse(metadata) as { role?: string }).role === 'host'
} catch {
return false
}
}
// 표시 이름 — 토큰에 담은 name, 없으면 식별자
const displayName = ref(props.participant.name || props.participant.identity)
@@ -19,6 +31,7 @@ function sync() {
const p = props.participant
displayName.value = p.name || p.identity
micOn.value = p.isMicrophoneEnabled
isHost.value = parseHost(p.metadata)
const camPub = p.getTrackPublication(Track.Source.Camera)
if (camPub?.track && videoEl.value) {
@@ -88,6 +101,11 @@ onBeforeUnmount(() => {
:title="micOn ? '마이크 켜짐' : '마이크 꺼짐'"
>{{ micOn ? '🎙' : '🔇' }}</span>
<span class="name">{{ displayName }}{{ props.isLocal ? ' (나)' : '' }}</span>
<span
v-if="isHost"
class="host-badge"
title="진행자"
>진행자</span>
</div>
</div>
</template>
@@ -161,4 +179,14 @@ onBeforeUnmount(() => {
.meta .mic.off {
opacity: 0.6;
}
.host-badge {
flex-shrink: 0;
padding: 0.0625rem 0.375rem;
border-radius: 0.25rem;
background: var(--accent);
color: #fff;
font-size: 0.625rem;
font-weight: 700;
line-height: 1.4;
}
</style>