Feat: 워크스페이스 설정페이지 api연동 - #53
Conversation
📝 WalkthroughWalkthrough워크스페이스 설정을 실제 Supabase 서버 액션으로 전환하고, 소유자 전용 수정과 본인 닉네임 저장을 구현했습니다. 초대 링크 프리뷰·참여·활성화 및 Resend 이메일 발송 흐름도 추가했습니다. Changes워크스페이스 설정 및 초대 백엔드
설정 및 초대 UI
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant InvitePage
participant SupabaseRPC
participant InviteAcceptView
Browser->>InvitePage: 초대 코드 접근
InvitePage->>SupabaseRPC: get_invite_preview(code)
SupabaseRPC-->>InvitePage: 프리뷰 반환
InvitePage->>InviteAcceptView: 초대 정보 전달
InviteAcceptView->>SupabaseRPC: join_workspace_by_invite_code(code)
SupabaseRPC-->>InviteAcceptView: workspaceId 반환
InviteAcceptView-->>Browser: 워크스페이스 이동
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/features/manage-member-profile/ui/MemberProfileForm.tsx (1)
59-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win닉네임 입력에 서버와 동일한 글자 수 제한을 클라이언트에도 반영 권장.
서버 스키마(
update-my-nickname.ts)는 닉네임을 20자로 제한하는데, input에는 대응하는maxLength가 없어 초과 입력 시 서버 왕복 후에야 오류를 알게 됩니다.♻️ 제안
<input value={nickname} onChange={(event) => { setNickname(event.target.value); setIsSaved(false); }} + maxLength={20} placeholder="닉네임을 입력하세요."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-member-profile/ui/MemberProfileForm.tsx` around lines 59 - 67, Update the nickname input in MemberProfileForm to enforce the server’s 20-character limit by adding the corresponding maxLength constraint, while preserving the existing value, change handler, placeholder, and styling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/invite/`[code]/page.tsx:
- Around line 16-36: Extract the invalid or expired invitation UI from the
page-level conditional in the invite page into a new InviteInvalidView component
under the invite views UI layer. Export InviteInvalidView through the invite
view barrel alongside InviteAcceptView, then import and render it from the
!preview branch so the page only composes views.
In `@src/entities/workspace-member/index.ts`:
- Around line 12-14: Update the comment above the `updateMyNickname` barrel
export so it accurately states that this Server Action can be safely exposed
through the barrel, removing the contradictory claim that
`next/headers`-dependent functions are excluded. Keep the `updateMyNickname`
export unchanged.
In `@src/entities/workspace/api/set-workspace-invite-enabled.ts`:
- Around line 25-33: Update the Supabase update chain in
setWorkspaceInviteEnabled to select the updated workspace id with maybeSingle(),
destructure the returned data alongside error, and treat !data as failure by
logging and throwing the existing user-facing error, preserving the current
error handling.
In `@src/entities/workspace/api/update-workspace-info.ts`:
- Around line 22-29: Update both workspace update
sites—src/entities/workspace/api/update-workspace-info.ts lines 22-29 and
src/entities/workspace-member/api/update-my-nickname.ts lines 28-33—by chaining
select('id') after update and capturing the returned data. Treat an empty result
as a permission error and throw it instead of reporting success; preserve
existing handling for database errors.
In `@src/features/manage-workspace-members/model/use-member-management.ts`:
- Line 82: Update the canInvite calculation in the member-management hook to
require isInviteEnabled alongside the existing email, duplicate, and
sending-state checks, so the invite action remains disabled when workspace
invitations are disabled.
- Line 35: Remove the unused useState wrapper around members in the
member-management hook and use initialMembers directly so updates from the
caller are reflected in the latest member list. Clean up the now-unneeded
state-related import if applicable, while preserving all existing
member-management behavior.
In `@src/shared/api/email/env.ts`:
- Line 5: Update the from initialization in the email environment configuration
to treat an empty INVITE_EMAIL_FROM value as unset and use the existing default
sender instead. Preserve the configured sender when it contains a non-empty
value.
In `@supabase/migrations/20260713010000_create_invite_rpcs.sql`:
- Around line 54-78: Update the insert exception handling in the invite RPC so a
unique_violation caused by an existing (workspace_id, user_id) membership is
rechecked and returns v_workspace_id as an idempotent success. Only apply the
nickname suffix retry for an actual workspace_nickname conflict, and ensure the
retry cannot treat a user-membership conflict as a nickname collision.
- Around line 30-38: Update the security-definer function
join_workspace_by_invite_code to derive the acting user exclusively from
auth.uid() instead of trusting p_user_id. Reject or otherwise prevent execution
when auth.uid() is null, and ensure all membership and invitation updates use
the authenticated ID so callers cannot act on behalf of another user.
- Line 63: Update the profile lookup in the invite RPC so a missing row is
caught and converted to a clear custom exception message before the error
reaches join-workspace-by-invite-code.ts. Preserve the existing nickname
assignment for found profiles and ensure the custom message is suitable for the
caller’s error.message fallback.
---
Outside diff comments:
In `@src/features/manage-member-profile/ui/MemberProfileForm.tsx`:
- Around line 59-67: Update the nickname input in MemberProfileForm to enforce
the server’s 20-character limit by adding the corresponding maxLength
constraint, while preserving the existing value, change handler, placeholder,
and styling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 4876e497-9f0c-416d-b577-255496fbc92c
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json,!package-lock.json
📒 Files selected for processing (24)
.env.examplepackage.jsonsrc/app/invite/[code]/page.tsxsrc/app/workspaces/[workspaceId]/settings/page.tsxsrc/entities/workspace-member/api/update-my-nickname.tssrc/entities/workspace-member/index.tssrc/entities/workspace/api/get-invite-preview.tssrc/entities/workspace/api/get-workspace-by-id.tssrc/entities/workspace/api/join-workspace-by-invite-code.tssrc/entities/workspace/api/send-invite-email.tssrc/entities/workspace/api/set-workspace-invite-enabled.tssrc/entities/workspace/api/update-workspace-info.tssrc/entities/workspace/index.tssrc/entities/workspace/model/workspace.types.tssrc/features/manage-member-profile/ui/MemberProfileForm.tsxsrc/features/manage-workspace-info/ui/WorkspaceInfoForm.tsxsrc/features/manage-workspace-members/model/use-member-management.tssrc/features/manage-workspace-members/ui/MemberInviteSection.tsxsrc/features/manage-workspace-members/ui/MemberManagementPanel.tsxsrc/shared/api/email/env.tssrc/views/invite/index.tssrc/views/invite/ui/InviteAcceptView.tsxsrc/views/settings/ui/SettingsView.tsxsupabase/migrations/20260713010000_create_invite_rpcs.sql
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/shared/model/database.types.ts (1)
778-779: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
create_work_shift_type_and_ensure_weekly_entriesRPC를 타입에 복구하세요src/shared/model/database.types.ts:778-779이 RPC는 아직
supabase/migrations/20260714024702_make_work_shift_creation_atomic.sql에 정의돼 있고src/entities/work-schedule/api/work-schedule-actions.ts에서도 호출합니다.Database['public']['Functions']에서 빠지면 생성 타입이 실제 스키마와 어긋납니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/model/database.types.ts` around lines 778 - 779, Restore the create_work_shift_type_and_ensure_weekly_entries entry in the Database public Functions type alongside create_workspace. Match its argument and return types to the RPC definition and its usage in work-schedule-actions.ts so the generated type reflects the existing schema.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@supabase/migrations/20260714030000_harden_invite_rpcs.sql`:
- Line 5: Remove the explicit transaction boundaries from the migration by
deleting the begin statement at the top and the commit statement near the end.
Leave the migration statements themselves unchanged so the migration tool
controls atomicity and migration recording.
- Around line 41-65: Update the membership insertion logic in the invite RPC
around the existing workspace_members existence check and insert exception block
to handle concurrent requests for the same (workspace_id, user_id) idempotently.
Distinguish user-membership unique violations from nickname conflicts, recheck
or otherwise return the existing workspace membership for the same user, and
only apply the generated nickname suffix for genuine nickname collisions.
---
Outside diff comments:
In `@src/shared/model/database.types.ts`:
- Around line 778-779: Restore the
create_work_shift_type_and_ensure_weekly_entries entry in the Database public
Functions type alongside create_workspace. Match its argument and return types
to the RPC definition and its usage in work-schedule-actions.ts so the generated
type reflects the existing schema.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 62b22caf-6f41-4f03-8b0c-42c2888612aa
📒 Files selected for processing (3)
src/entities/workspace/api/join-workspace-by-invite-code.tssrc/shared/model/database.types.tssupabase/migrations/20260714030000_harden_invite_rpcs.sql
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@supabase/migrations/20260714030000_harden_invite_rpcs.sql`:
- Around line 41-42: Update the profile lookup in the invite RPC to handle both
unique_violation and no_data_found exceptions. When the strict profiles query
for v_nickname finds no row, route it through the same safe handling path as the
existing conflict case so accounts without profiles do not abort the RPC.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5ed6aea0-2c8a-4d65-9173-f7dd0739a851
📒 Files selected for processing (2)
src/shared/api/email/env.tssupabase/migrations/20260714030000_harden_invite_rpcs.sql
| -- 이미 참여한 멤버면 재참여 없이 그대로 입장한다(멱등 — 빠른 경로). | ||
| -- 정합성은 아래 on conflict가 보장하므로 이 체크는 프로필 조회를 아끼는 최적화일 뿐이다. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 마이그레이션 파일에서 exists 체크와 strict select 부분(43-52라인)을 확인
fd 'harden_invite_rpcs' | xargs cat -n | sed -n '30,60p'
# 프로필 생성이 트리거 등으로 보장되는지 확인
rg -n 'insert into (public\.)?profiles' supabase/migrationsRepository: TeampleRun/syncly
Length of output: 1613
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== migrations mentioning profiles table or inserts =="
rg -n -i 'create table .*profiles|profiles\s*\(|insert into (public\.)?profiles|trigger.*profiles|profiles.*trigger|handle_new_user|on auth\.users' supabase/migrations
echo
echo "== files related to profiles =="
fd -a 'profiles' supabase/migrations supabase || trueRepository: TeampleRun/syncly
Length of output: 2157
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== auth.users 관련 트리거/함수 =="
rg -n -i 'auth\.users|new_user|handle_new_user|create.*profile|profiles.*from auth|insert.*profiles' supabase/migrations supabase --glob '*.sql'
echo
echo "== profiles 테이블 정의 =="
rg -n -i 'create table .*profiles|alter table .*profiles|primary key.*profiles|references .*profiles' supabase/migrations --glob '*.sql'Repository: TeampleRun/syncly
Length of output: 4974
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== any trigger/function mentioning profiles or auth.users anywhere =="
rg -n -i 'create\s+trigger|create\s+function|returns\s+trigger|auth\.users|public\.profiles|insert into (public\.)?profiles|new_user|handle_new_user' . --glob '*.sql' --glob '*.ts' --glob '*.js' --glob '*.md'
echo
echo "== core profiles table / policies excerpts =="
sed -n '1,60p' supabase/migrations/20260709025327_create_enums_and_core_tables.sql
echo
sed -n '1,80p' supabase/migrations/20260709025820_add_auth_rls_policies.sqlRepository: TeampleRun/syncly
Length of output: 10418
strict 조회의 no_data_found도 처리하세요. profiles 자동 생성이 보장되지 않아서, select real_name into strict v_nickname from profiles where id = v_user_id; 는 프로필이 없는 계정에서 RPC를 중단시킬 수 있습니다. unique_violation와 함께 no_data_found도 잡는 쪽이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/migrations/20260714030000_harden_invite_rpcs.sql` around lines 41 -
42, Update the profile lookup in the invite RPC to handle both unique_violation
and no_data_found exceptions. When the strict profiles query for v_nickname
finds no row, route it through the same safe handling path as the existing
conflict case so accounts without profiles do not abort the RPC.
Pull Request
작업 내용
/invite/[code]라우트)auth.uid())작업 결과
/invite/[code])로 워크스페이스 참여 가능변경 사항
Added
updateWorkspaceInfo/updateMyNickname서버액션 (정보·닉네임 수정, RLS 기반)supabase/migrations/20260713010000_create_invite_rpcs.sqlget_invite_preview(p_code)— 활성·유효 코드일 때만 워크스페이스 요약 반환join_workspace_by_invite_code(p_user_id, p_code)—security definer로 RLS 우회, 멱등 참여/invite/[code]라우트 및InviteAcceptView(초대 미리보기·참여 UI)set-workspace-invite-enabled— 초대 링크 활성/비활성 토글send-invite-email서버액션 — Resend로 초대 이메일 발송 (멤버만 발송 가능).env.example에RESEND_API_KEY,INVITE_EMAIL_FROM추가,resend패키지 도입Changed
settings/page.tsx— RSC에서 워크스페이스·멤버·현재 사용자를 실 API로 조회해 주입SettingsView— 현재 사용자 역할(owner) 기준으로 정보 편집 권한 판단WorkspaceInfoForm/MemberProfileForm— 실 서버액션 연결 및 저장 상태 UI 반영manage-workspace-members/**— 멤버 관리·초대 UI 실 데이터 연동get-workspace-by-id— invite_code, invite_enabled 컬럼 추가Fixed
(workspace_id, workspace_nickname)유니크 제약 위반을 사용자 친화 메시지로 처리실행화면
테스트
리뷰 체크리스트
feature/*->develop, 배포 시develop또는release/*->main)Type/#issue-number/description형식을 따릅니다.console.log, 주석, 임시 코드를 제거했습니다.리뷰 요청사항
security definer/search_path설정과 멱등·닉네임 충돌 재시도 로직이 안전한지관련 이슈
Closes #47
Summary by CodeRabbit
Summary by CodeRabbit
새로운 기능
개선 사항