Skip to content

feat:소셜 로그인 기반 워크스페이스·공지 연동(#50) - #50

Merged
seongjinss555 merged 8 commits into
developfrom
feat/#50/auth-workspace-notices
Jul 14, 2026
Merged

feat:소셜 로그인 기반 워크스페이스·공지 연동(#50)#50
seongjinss555 merged 8 commits into
developfrom
feat/#50/auth-workspace-notices

Conversation

@seongjinss555

@seongjinss555 seongjinss555 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Pull Request

작업 내용

  • 소셜 로그인 세션을 기준으로 워크스페이스 목록 조회와 생성을 처리하도록 전환했습니다.
  • 워크스페이스 Shell의 헤더와 사이드바가 실제 로그인 사용자와 워크스페이스 역할을 표시하도록 연결했습니다.
  • 공지사항을 Supabase 데이터와 작성자·소유자 권한 기준으로 처리하도록 연동했습니다.

작업 결과

  • 로그인 사용자는 자신의 멤버십에 속한 워크스페이스만 조회합니다.
  • 새 워크스페이스의 owner와 owner 멤버십은 auth.uid()로 결정됩니다.
  • 매장 운영 워크스페이스 첫 진입은 대시보드로 이동합니다.
  • 공지 목록·작성·수정·삭제·고정 상태가 워크스페이스 단위 DB 데이터와 권한을 사용합니다.

변경 사항

Added

  • auth.uid() 기반 create_workspace RPC 마이그레이션
  • 현재 로그인 사용자의 프로필과 워크스페이스 역할 조회 API
  • 공지 RLS, 고정 상태 보호 트리거 및 조회 인덱스

Changed

  • 워크스페이스 목록 RPC에 테스트 사용자 대신 실제 세션 사용자 ID 전달
  • 생성 다이얼로그의 이름 placeholder를 목적별 예시로 분기
  • 헤더·사이드바 사용자 정보를 목업에서 실제 멤버십 데이터로 전환

Fixed

  • 다른 로그인 사용자에게 테스트 사용자 워크스페이스가 노출되던 문제
  • 워크스페이스 생성 시 호출자가 owner를 지정할 수 있던 문제
  • 매장 운영 워크스페이스가 업무 스케줄로 바로 진입하던 흐름

실행화면

  • 헤더 및 사이드바 실제 유저 연동
스크린샷 2026-07-14 오전 10 17 04 스크린샷 2026-07-14 오전 10 17 15
  • 공지 등록 (DB 연동 결과)
image image
  • 공지 삭제 (DB 연동 결과)
image 스크린샷 2026-07-14 오전 10 04 18
  • 대시보드 공지 연동
image
  • 사이드바 purpose 별 이미지 차별화
스크린샷 2026-07-14 오전 11 24 25 스크린샷 2026-07-14 오전 11 24 39 스크린샷 2026-07-14 오전 11 25 14

테스트

  • npm run lint 실행 (기존 SignupView dependency 경고 1건)
  • npm run typecheck
  • npm run build
  • Supabase 원격 마이그레이션 적용 후 신규 소셜 로그인 사용자 워크스페이스 생성 E2E 확인

리뷰 체크리스트

  • PR base branch가 올바릅니다. (feature/* -> develop, 배포 시 develop 또는 release/* -> main)
  • 브랜치명이 Type/#issue-number/description 형식을 따릅니다.
  • 커밋 메시지가 컨벤션을 따릅니다.
  • 불필요한 console.log, 주석, 임시 코드를 제거했습니다.
  • 타입 에러와 린트 에러를 확인했습니다.
  • CodeRabbit 1차 리뷰를 확인했습니다.
  • CodeRabbit 리뷰 반영 후 Discord에 공유했습니다.
  • 최소 1명 이상의 approve 후 merge합니다.

리뷰 요청사항

  • create_workspace 마이그레이션 적용 후 auth.uid() 기준 owner·멤버십 생성과 RLS 동작을 중점 확인 부탁드립니다.
  • 공지 작성자/소유자 권한 및 고정 상태 변경 제약을 함께 검토 부탁드립니다.

관련 이슈

Closes #50
Ref #2

Summary by CodeRabbit

  • 새로운 기능
    • 워크스페이스 공지사항을 서버에서 불러와 작성/수정/삭제/고정을 지원합니다(권한 정보 포함).
    • 대시보드 “최근 공지” 위젯이 워크스페이스별 실데이터로 표시됩니다.
    • 워크스페이스 헤더·사이드바에 현재 멤버 기반 표시가 반영됩니다.
    • 근무 유형 추가 시 이번 주 스케줄의 누락 항목을 자동으로 채웁니다.
  • 개선 사항
    • 공지 작업 중 버튼 상태를 잠그고, 권한에 따라 메뉴 노출/편집 가능 범위가 제한됩니다.
    • 일부 워크스페이스 이동/로딩 흐름이 더 안정화되었습니다.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

워크스페이스와 공지가 목데이터 대신 인증 세션, Supabase 데이터, 멤버십 역할을 사용하도록 변경되었습니다. 공지 CRUD·고정, 대시보드 조회, 워크스페이스 진입 및 스케줄 누락 엔트리 보완 흐름이 추가되었습니다.

Changes

워크스페이스 인증 사용자 연동

Layer / File(s) Summary
인증 사용자 기반 워크스페이스 데이터
src/entities/workspace/..., src/shared/model/database.types.ts, supabase/migrations/...create_workspace...sql
워크스페이스 목록과 생성이 인증 사용자 ID 및 auth.uid()를 사용하도록 변경되고 관련 RPC 타입과 권한이 갱신되었습니다.
현재 멤버 조회와 Shell 전달
src/entities/workspace-member/..., src/app/workspaces/[workspaceId]/layout.tsx, src/widgets/workspace-shell/...
현재 멤버십과 프로필을 조회해 WorkspaceShell, 헤더, 사이드바에 전달합니다.
워크스페이스 진입 및 생성 UI
src/app/workspaces/[workspaceId]/page.tsx, src/features/create-workspace/ui/CreateWorkspaceDialog.tsx
워크스페이스 진입 경로와 목적별 이름 placeholder가 변경되었습니다.

공지 보드 데이터와 권한 연동

Layer / File(s) Summary
공지 데이터 계약과 서버 조회
src/entities/notice/model/*, src/entities/notice/api/get-notice-board.ts
공지 작성자와 viewer 타입을 추가하고 공지·프로필·멤버십 정보를 조회합니다.
공지 화면과 대시보드 연결
src/app/workspaces/[workspaceId]/notices/page.tsx, src/views/store-operation/notices/..., src/widgets/store-operation/dashboard-recent-notices/...
공지 페이지와 대시보드 위젯이 workspaceId 기반 서버 데이터를 사용합니다.
공지 변경 액션과 서버 상태 훅
src/entities/notice/api/notice-actions.ts, src/features/manage-notices/model/use-notice-board-state.ts
공지 생성·수정·삭제·고정과 Query 기반 갱신 흐름을 구현했습니다.
공지 권한 UI와 제출 상태
src/features/manage-notices/ui/NoticeList.tsx, src/features/manage-notices/ui/NoticeComposer.tsx
viewer 권한에 따른 메뉴 표시와 비동기 저장 상태 처리를 추가했습니다.
공지 RLS와 고정 상태 보호
supabase/migrations/20260713010000_secure_announcements.sql, supabase/migrations/20260714022856_harden_announcements_mutations.sql
공지 CRUD 정책을 멤버십·작성자·소유자 기준으로 분리하고 고정 및 식별자 변경을 제한했습니다.

근무 스케줄 엔트리 보완

Layer / File(s) Summary
누락 스케줄 엔트리 생성과 저장 검증
src/entities/work-schedule/..., src/features/manage-work-schedule/..., supabase/migrations/20260714024702_make_work_shift_creation_atomic.sql
근무 유형 생성과 주간 엔트리 보장을 RPC로 묶고, 새 유형 추가 시 누락 엔트리를 보완하며 시간 유효성 검사를 적용합니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant NoticesView
  participant NoticeActions
  participant Supabase
  participant QueryCache
  User->>NoticesView: 공지 작성·수정·삭제·고정 요청
  NoticesView->>NoticeActions: 입력값과 workspaceId 전달
  NoticeActions->>Supabase: 멤버 역할 확인 및 announcements 변경
  Supabase-->>NoticeActions: 변경 결과 반환
  NoticeActions-->>NoticesView: 성공 또는 오류 반환
  NoticesView->>QueryCache: 공지 데이터 다시 조회
Loading

Possibly related PRs

  • TeampleRun/syncly#19: 목데이터 기반 공지 화면과 상태 관리가 서버 조회 및 권한 데이터 구조로 전환되는 동일 코드 흐름과 연결됩니다.
  • TeampleRun/syncly#12: 워크스페이스 레이아웃과 Shell 컴포넌트의 동일한 사용자 표시 경로를 변경합니다.
  • TeampleRun/syncly#29: 대시보드 위젯 렌더링 컨텍스트에 workspaceId를 전달하는 동일한 인터페이스 흐름과 연결됩니다.

Suggested reviewers: wjswlgh96, kwon812, jiwoonge, 0011810

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 소셜 로그인 기반 워크스페이스·공지 연동이라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed 필수 섹션들이 모두 포함되어 있고 작업 내용, 결과, 변경 사항, 테스트, 이슈 링크가 충분히 작성되었습니다.
Linked Issues check ✅ Passed 세션 기반 워크스페이스 조회/생성, 실제 멤버 정보 연동, 공지 권한/RLS, 대시보드 이동, 스케줄 보강이 모두 반영되었습니다.
Out of Scope Changes check ✅ Passed 요구사항과 무관한 변경은 보이지 않으며, 추가된 스케줄·DB 보강도 공지/워크스페이스 연동 범위에 포함됩니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#50/auth-workspace-notices

Comment @coderabbitai help to get the list of available commands.

@seongjinss555
seongjinss555 force-pushed the feat/#50/auth-workspace-notices branch from ef20bdb to 7611fad Compare July 14, 2026 01:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 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/workspaces/`[workspaceId]/notices/page.tsx:
- Around line 13-15: Update the notice-board data flow around getNoticeBoard and
NoticesView/useNoticeBoardState so server-fetched initialData is treated as
fresh on client mount. Configure the query’s staleTime and/or
initialDataUpdatedAt, or use the existing hydration strategy, to prevent an
immediate duplicate Supabase request while preserving normal refetch behavior
after the freshness window.

In `@src/entities/notice/api/notice-actions.ts`:
- Around line 139-164: Update setNoticePinned to load and validate the target
announcement with the existing getEditableAnnouncement helper before performing
the update, using both workspaceId and noticeId. Preserve the owner
authorization and update flow, but ensure missing or cross-workspace notices
throw the same not-found error behavior as the other notice actions instead of
revalidating successfully.
- Around line 35-41: Update the notice server action’s workspace-member
validation around the existing error and !data branches to return a serializable
ok/error result instead of throwing Error instances. Preserve the existing
Korean messages in the returned error values and keep the success response
compatible with useNoticeBoardState’s error.message toast handling.

In `@src/entities/workspace-member/api/get-current-workspace-member.ts`:
- Around line 22-45: Update the membership and profile queries in
getCurrentWorkspaceMember to execute concurrently with Promise.all, since both
only require workspaceId and user.id. Preserve the existing membershipError,
profileError, and no-membership handling behavior after both results resolve.

In `@src/entities/workspace/api/use-my-workspaces.ts`:
- Around line 5-8: 인증 사용자별로 쿼리 캐시가 분리되지 않아 계정 전환 시 이전 사용자의 워크스페이스가 노출될 수 있습니다.
myWorkspacesQueryKey를 현재 사용자 식별자를 포함하도록 구성하고, 인증 상태 변경 시 해당 쿼리 키가 올바르게 갱신되도록
getMyWorkspaces 호출부와 연동하세요. 로그아웃 처리에는 기존 queryClient 캐시 초기화 방식이 있는지 확인하고, 없다면
적용하세요.

In `@src/features/manage-notices/model/use-notice-board-state.ts`:
- Around line 22-28: Update useNoticeBoardState to subscribe to the useQuery
error state and surface background refetch failures through the existing
user-facing toast/error notification mechanism, including failures triggered by
refreshNoticeBoard after mutations. Preserve displaying initial or previously
cached data while reporting the failed refresh.

In `@supabase/migrations/20260713010000_secure_announcements.sql`:
- Around line 14-21: Update the announcement security policies and trigger
around announcements_insert_member, announcements_update_author_or_owner, and
prevent_non_owner_announcement_pin so non-owners cannot insert pinned
announcements, the pin-prevention trigger runs on both INSERT and UPDATE, and
UPDATE operations cannot change workspace_id while preserving the existing
author/owner permissions.

In
`@supabase/migrations/20260714003813_create_workspace_with_authenticated_user.sql`:
- Around line 27-28: `select ... into strict`를 사용하는 profiles 조회에서 발생하는 암묵적
`no_data_found` 예외를 제거하고, 조회 전에 `v_user_id`가 유효한지 명시적으로 검증하세요. 누락된 경우 `raise
exception`으로 기존 함수의 한국어 오류 메시지 형식에 맞는 명확한 메시지를 반환하고, 유효한 사용자에 대해서는
`profiles.real_name` 조회 동작을 유지하세요.
- Line 3: Remove the explicit begin transaction statement at the start of the
migration and the matching commit statement at the end, leaving the migration
statements otherwise unchanged so Supabase CLI manages the transaction.
🪄 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: 2e16ff12-546a-446b-b9ff-8139f468bb16

📥 Commits

Reviewing files that changed from the base of the PR and between c4a8d24 and 7611fad.

📒 Files selected for processing (26)
  • src/app/workspaces/[workspaceId]/layout.tsx
  • src/app/workspaces/[workspaceId]/notices/page.tsx
  • src/app/workspaces/[workspaceId]/page.tsx
  • src/entities/notice/api/get-notice-board.ts
  • src/entities/notice/api/notice-actions.ts
  • src/entities/notice/index.ts
  • src/entities/notice/model/mock-notices.ts
  • src/entities/notice/model/notice-query.ts
  • src/entities/notice/model/notice.types.ts
  • src/entities/workspace-member/api/get-current-workspace-member.ts
  • src/entities/workspace/api/create-workspace.ts
  • src/entities/workspace/api/get-my-workspaces.ts
  • src/entities/workspace/api/use-my-workspaces.ts
  • src/features/create-workspace/ui/CreateWorkspaceDialog.tsx
  • src/features/manage-notices/model/use-notice-board-state.ts
  • src/features/manage-notices/ui/NoticeComposer.tsx
  • src/features/manage-notices/ui/NoticeList.tsx
  • src/shared/model/database.types.ts
  • src/views/dashboard/config/widget-catalog.tsx
  • src/views/store-operation/notices/ui/NoticesView.tsx
  • src/widgets/store-operation/dashboard-recent-notices/ui/RecentNotices.tsx
  • src/widgets/workspace-shell/ui/WorkspaceHeader.tsx
  • src/widgets/workspace-shell/ui/WorkspaceShell.tsx
  • src/widgets/workspace-shell/ui/WorkspaceSidebar.tsx
  • supabase/migrations/20260713010000_secure_announcements.sql
  • supabase/migrations/20260714003813_create_workspace_with_authenticated_user.sql
💤 Files with no reviewable changes (2)
  • src/entities/notice/model/mock-notices.ts
  • src/entities/workspace/api/create-workspace.ts

Comment on lines +13 to +15
const initialData = await getNoticeBoard(workspaceId);

return <NoticesView workspaceId={workspaceId} />;
return <NoticesView workspaceId={workspaceId} initialData={initialData} />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

초기 조회 직후 동일한 공지 보드를 다시 요청하지 않도록 freshness 정책을 설정하세요.

useNoticeBoardStateuseQueryinitialData만 받고 기본 staleTime을 사용하므로, 서버에서 이미 조회한 데이터가 클라이언트 마운트 직후 stale로 처리되어 재조회됩니다. staleTime/initialDataUpdatedAt을 명시하거나 hydration 전략으로 중복 Supabase 조회를 막아주세요.

🤖 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/app/workspaces/`[workspaceId]/notices/page.tsx around lines 13 - 15,
Update the notice-board data flow around getNoticeBoard and
NoticesView/useNoticeBoardState so server-fetched initialData is treated as
fresh on client mount. Configure the query’s staleTime and/or
initialDataUpdatedAt, or use the existing hydration strategy, to prevent an
immediate duplicate Supabase request while preserving normal refetch behavior
after the freshness window.

Comment thread src/entities/notice/api/notice-actions.ts
Comment on lines +139 to +164
export async function setNoticePinned(input: {
workspaceId: string;
noticeId: string;
isPinned: boolean;
}): Promise<void> {
const value = z
.object({ workspaceId: uuidSchema, noticeId: uuidSchema, isPinned: z.boolean() })
.parse(input);
const { supabase, member } = await getCurrentWorkspaceMember(value.workspaceId);

if (member.role !== 'owner') {
throw new Error('워크스페이스 소유자만 공지를 고정할 수 있습니다.');
}

const { error } = await supabase
.from('announcements')
.update({ is_pinned: value.isPinned })
.eq('id', value.noticeId)
.eq('workspace_id', value.workspaceId);

if (error) {
throw new Error(`공지 고정 상태 변경에 실패했습니다: ${error.message}`);
}

revalidateNoticePages(value.workspaceId);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

setNoticePinned는 공지 존재 여부를 확인하지 않아 잘못된 noticeId에도 조용히 성공합니다.

createNotice/updateNotice/deleteNotice는 대상 공지를 먼저 조회(getEditableAnnouncement)해 없으면 에러를 던지지만, setNoticePinnedgetCurrentWorkspaceMember만 호출하고 곧바로 UPDATE합니다. noticeId가 존재하지 않거나 다른 워크스페이스 소속이면 UPDATE는 0 rows에 영향을 주고도 error가 없어 그대로 성공 응답(revalidateNoticePages + return)이 반환됩니다.

🐛 존재 확인 추가 제안
   const { supabase, member } = await getCurrentWorkspaceMember(value.workspaceId);

   if (member.role !== 'owner') {
     throw new Error('워크스페이스 소유자만 공지를 고정할 수 있습니다.');
   }

-  const { error } = await supabase
+  const { data, error } = await supabase
     .from('announcements')
     .update({ is_pinned: value.isPinned })
     .eq('id', value.noticeId)
-    .eq('workspace_id', value.workspaceId);
+    .eq('workspace_id', value.workspaceId)
+    .select('id')
+    .maybeSingle();

   if (error) {
     throw new Error(`공지 고정 상태 변경에 실패했습니다: ${error.message}`);
   }
+
+  if (!data) {
+    throw new Error('공지를 찾을 수 없습니다.');
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function setNoticePinned(input: {
workspaceId: string;
noticeId: string;
isPinned: boolean;
}): Promise<void> {
const value = z
.object({ workspaceId: uuidSchema, noticeId: uuidSchema, isPinned: z.boolean() })
.parse(input);
const { supabase, member } = await getCurrentWorkspaceMember(value.workspaceId);
if (member.role !== 'owner') {
throw new Error('워크스페이스 소유자만 공지를 고정할 수 있습니다.');
}
const { error } = await supabase
.from('announcements')
.update({ is_pinned: value.isPinned })
.eq('id', value.noticeId)
.eq('workspace_id', value.workspaceId);
if (error) {
throw new Error(`공지 고정 상태 변경에 실패했습니다: ${error.message}`);
}
revalidateNoticePages(value.workspaceId);
}
export async function setNoticePinned(input: {
workspaceId: string;
noticeId: string;
isPinned: boolean;
}): Promise<void> {
const value = z
.object({ workspaceId: uuidSchema, noticeId: uuidSchema, isPinned: z.boolean() })
.parse(input);
const { supabase, member } = await getCurrentWorkspaceMember(value.workspaceId);
if (member.role !== 'owner') {
throw new Error('워크스페이스 소유자만 공지를 고정할 수 있습니다.');
}
const { data, error } = await supabase
.from('announcements')
.update({ is_pinned: value.isPinned })
.eq('id', value.noticeId)
.eq('workspace_id', value.workspaceId)
.select('id')
.maybeSingle();
if (error) {
throw new Error(`공지 고정 상태 변경에 실패했습니다: ${error.message}`);
}
if (!data) {
throw new Error('공지를 찾을 수 없습니다.');
}
revalidateNoticePages(value.workspaceId);
}
🤖 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/entities/notice/api/notice-actions.ts` around lines 139 - 164, Update
setNoticePinned to load and validate the target announcement with the existing
getEditableAnnouncement helper before performing the update, using both
workspaceId and noticeId. Preserve the owner authorization and update flow, but
ensure missing or cross-workspace notices throw the same not-found error
behavior as the other notice actions instead of revalidating successfully.

Comment on lines +22 to +45
const { data: membership, error: membershipError } = await supabase
.from('workspace_members')
.select('workspace_id, user_id, workspace_nickname, role')
.eq('workspace_id', workspaceId)
.eq('user_id', user.id)
.maybeSingle();

if (membershipError) {
throw new Error(`현재 워크스페이스 멤버 조회에 실패했습니다: ${membershipError.message}`);
}

if (!membership) {
return null;
}

const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('email, real_name')
.eq('id', user.id)
.maybeSingle();

if (profileError) {
throw new Error(`현재 사용자 프로필 조회에 실패했습니다: ${profileError.message}`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

멤버십·프로필 조회를 병렬화할 수 있습니다.

membership 조회(Line 22-27)와 profile 조회(Line 37-41)는 서로의 결과에 의존하지 않고 각각 workspaceId/user.id, user.id만 필요합니다. 현재는 순차 await로 처리되어 워크스페이스 레이아웃이 렌더링될 때마다 불필요한 라운드트립이 하나 더 추가됩니다. Promise.all로 병렬화하면 지연 시간을 줄일 수 있습니다.

⚡ 병렬화 제안
-    const { data: membership, error: membershipError } = await supabase
-      .from('workspace_members')
-      .select('workspace_id, user_id, workspace_nickname, role')
-      .eq('workspace_id', workspaceId)
-      .eq('user_id', user.id)
-      .maybeSingle();
-
-    if (membershipError) {
-      throw new Error(`현재 워크스페이스 멤버 조회에 실패했습니다: ${membershipError.message}`);
-    }
-
-    if (!membership) {
-      return null;
-    }
-
-    const { data: profile, error: profileError } = await supabase
-      .from('profiles')
-      .select('email, real_name')
-      .eq('id', user.id)
-      .maybeSingle();
-
-    if (profileError) {
-      throw new Error(`현재 사용자 프로필 조회에 실패했습니다: ${profileError.message}`);
-    }
+    const [{ data: membership, error: membershipError }, { data: profile, error: profileError }] =
+      await Promise.all([
+        supabase
+          .from('workspace_members')
+          .select('workspace_id, user_id, workspace_nickname, role')
+          .eq('workspace_id', workspaceId)
+          .eq('user_id', user.id)
+          .maybeSingle(),
+        supabase.from('profiles').select('email, real_name').eq('id', user.id).maybeSingle(),
+      ]);
+
+    if (membershipError) {
+      throw new Error(`현재 워크스페이스 멤버 조회에 실패했습니다: ${membershipError.message}`);
+    }
+
+    if (!membership) {
+      return null;
+    }
+
+    if (profileError) {
+      throw new Error(`현재 사용자 프로필 조회에 실패했습니다: ${profileError.message}`);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: membership, error: membershipError } = await supabase
.from('workspace_members')
.select('workspace_id, user_id, workspace_nickname, role')
.eq('workspace_id', workspaceId)
.eq('user_id', user.id)
.maybeSingle();
if (membershipError) {
throw new Error(`현재 워크스페이스 멤버 조회에 실패했습니다: ${membershipError.message}`);
}
if (!membership) {
return null;
}
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('email, real_name')
.eq('id', user.id)
.maybeSingle();
if (profileError) {
throw new Error(`현재 사용자 프로필 조회에 실패했습니다: ${profileError.message}`);
}
const [{ data: membership, error: membershipError }, { data: profile, error: profileError }] =
await Promise.all([
supabase
.from('workspace_members')
.select('workspace_id, user_id, workspace_nickname, role')
.eq('workspace_id', workspaceId)
.eq('user_id', user.id)
.maybeSingle(),
supabase.from('profiles').select('email, real_name').eq('id', user.id).maybeSingle(),
]);
if (membershipError) {
throw new Error(`현재 워크스페이스 멤버 조회에 실패했습니다: ${membershipError.message}`);
}
if (!membership) {
return null;
}
if (profileError) {
throw new Error(`현재 사용자 프로필 조회에 실패했습니다: ${profileError.message}`);
}
🤖 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/entities/workspace-member/api/get-current-workspace-member.ts` around
lines 22 - 45, Update the membership and profile queries in
getCurrentWorkspaceMember to execute concurrently with Promise.all, since both
only require workspaceId and user.id. Preserve the existing membershipError,
profileError, and no-membership handling behavior after both results resolve.

Comment on lines 5 to +8
import { getMyWorkspaces } from './get-my-workspaces';

// 생성/수정 후 invalidateQueries({ queryKey: ['workspaces'] })로 무효화한다
export const myWorkspacesQueryKey = ['workspaces', 'my', DEV_USER_ID] as const;
export const myWorkspacesQueryKey = ['workspaces', 'my'] as const;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔵 Trivial

사용자별로 스코프되지 않은 쿼리 키.

myWorkspacesQueryKey가 더 이상 사용자 id를 포함하지 않습니다. 계정 전환(로그아웃→다른 계정 로그인)이 전체 새로고침 없이 이루어지는 흐름이 있다면, react-query 캐시가 무효화되기 전까지 이전 사용자의 워크스페이스 목록이 잠깐 노출될 수 있습니다. 이 파일만으로는 이슈로 확정하기 어렵지만, 인증 상태 변경(로그아웃) 시 queryClient.clear() 등으로 캐시를 초기화하는 처리가 있는지 확인해보시길 권장합니다.

🤖 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/entities/workspace/api/use-my-workspaces.ts` around lines 5 - 8, 인증 사용자별로
쿼리 캐시가 분리되지 않아 계정 전환 시 이전 사용자의 워크스페이스가 노출될 수 있습니다. myWorkspacesQueryKey를 현재 사용자
식별자를 포함하도록 구성하고, 인증 상태 변경 시 해당 쿼리 키가 올바르게 갱신되도록 getMyWorkspaces 호출부와 연동하세요. 로그아웃
처리에는 기존 queryClient 캐시 초기화 방식이 있는지 확인하고, 없다면 적용하세요.

Comment thread src/features/manage-notices/model/use-notice-board-state.ts
Comment thread supabase/migrations/20260713010000_secure_announcements.sql
@@ -0,0 +1,78 @@
-- 워크스페이스 생성자는 RPC 인자가 아닌 인증 세션(auth.uid())으로 결정한다.
-- 기존 p_user_id 인자를 제거해 호출자가 다른 사용자를 생성자로 지정할 수 없게 한다.
begin;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,140p' supabase/migrations/20260714003813_create_workspace_with_authenticated_user.sql && printf '\n---\n' && rg -n "transaction|begin;|commit;|supabase migration|squawk|disable transaction|single transaction" -S .

Repository: TeampleRun/syncly

Length of output: 3044


🌐 Web query:

Supabase CLI database migrations are each migration file wrapped in a transaction begin commit transaction semantics

💡 Result:

Yes, the Supabase CLI typically wraps each migration file in a single transaction (BEGIN/COMMIT) [1][2][3]. This ensures that if any part of the migration file fails, the entire file's changes can be rolled back, maintaining database consistency [1][2]. However, there are important exceptions and nuances to this behavior: 1. Pipeline-Incompatible Statements: Certain PostgreSQL commands—such as CREATE INDEX CONCURRENTLY, VACUUM, REINDEX CONCURRENTLY, ALTER SYSTEM, and CLUSTER—cannot be executed within a transaction block [1][2][3]. When the Supabase CLI detects these statements, it automatically handles them by executing them outside of the transaction block [1][2]. It temporarily pauses the transaction, executes the statement, and then resumes the transaction for the remainder of the file [1][2]. 2. Manual Disabling: Users can explicitly disable transactional wrapping for a specific migration file by including the comment -- supabase:disable-transaction at the top of the file [3]. This is often necessary when a migration contains one of the aforementioned pipeline-incompatible statements and you prefer to manage the transaction state manually [3]. 3. Risks of Manual Interference: Including explicit BEGIN or COMMIT statements within your migration files can cause conflicts with the CLI's own transaction management [4]. Doing so may lead to warnings or unexpected behavior, such as the migration failing to apply correctly or subsequent migration files not executing [4]. Always rely on the CLI's default automatic transaction handling unless you have a specific technical requirement to override it [3].

Citations:


begin; / commit; 제거 필요
Supabase CLI는 마이그레이션 파일을 기본적으로 단일 트랜잭션으로 감싸므로, 파일 내부의 명시적 트랜잭션은 충돌할 수 있습니다. 이 파일의 begin;(3행)과 commit;(78행)을 삭제하세요.

🧰 Tools
🪛 Squawk (2.59.0)

[warning] 3-3: There is an existing transaction already in progress, managed by your migration tool. Put migration statements in separate files to have them be in separate transactions or don't use the assume-in-transaction setting.

(transaction-nesting)

🤖 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/20260714003813_create_workspace_with_authenticated_user.sql`
at line 3, Remove the explicit begin transaction statement at the start of the
migration and the matching commit statement at the end, leaving the migration
statements otherwise unchanged so Supabase CLI manages the transaction.

Source: Linters/SAST tools

Comment on lines +27 to +28
-- OAuth 콜백에서 profiles upsert가 완료된 인증 사용자만 생성할 수 있다.
select real_name into strict v_nickname from profiles where id = v_user_id;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

select ... into strict의 암묵적 예외가 사용자에게 불명확하게 노출될 수 있습니다.

profiles row가 없으면 strictno_data_found 예외를 던지지만, 메시지가 일반적인 Postgres 오류로 노출됩니다. v_user_id is null 체크처럼 명시적인 raise exception + 한국어 메시지로 처리하면 클라이언트 측 에러 메시지 일관성이 좋아집니다.

🤖 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/20260714003813_create_workspace_with_authenticated_user.sql`
around lines 27 - 28, `select ... into strict`를 사용하는 profiles 조회에서 발생하는 암묵적
`no_data_found` 예외를 제거하고, 조회 전에 `v_user_id`가 유효한지 명시적으로 검증하세요. 누락된 경우 `raise
exception`으로 기존 함수의 한국어 오류 메시지 형식에 맞는 명확한 메시지를 반환하고, 유효한 사용자에 대해서는
`profiles.real_name` 조회 동작을 유지하세요.

@seongjinss555
seongjinss555 force-pushed the feat/#50/auth-workspace-notices branch from 3d83344 to 58a8410 Compare July 14, 2026 02:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/entities/work-schedule/api/work-schedule-actions.ts`:
- Around line 140-142: Make the server and board use the same default shift ID
for missing entries: in
src/entities/work-schedule/api/work-schedule-actions.ts:140-142, return or pass
the ID selected by getDefaultWorkShiftOption(shifts) into the correction flow;
update useWorkScheduleState in
src/features/manage-work-schedule/model/use-work-schedule-state.ts:80-102 to
accept and propagate that ID; and update WorkScheduleBoard in
src/features/manage-work-schedule/ui/WorkScheduleBoard.tsx:64 to call
completeMissingEntries with the server-selected ID instead of newShift.id.
- Around line 140-142: 서버와 클라이언트가 누락된 주간 근무 엔트리에 동일한 shiftTypeId를 사용하도록 맞추세요.
`ensureWeeklyWorkScheduleEntries` 호출 시 서버가 선택한 기본 근무유형 ID를 전달하거나, 클라이언트의
`newShift.id` 대신 서버가 반환·선택한 ID를 로컬 상태에 사용하도록 수정해 기존 기본 근무유형이 있을 때도 DB와 화면이 일치하게
하세요.
🪄 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: abf89bb9-9f21-4bf7-98ad-a609e2602e68

📥 Commits

Reviewing files that changed from the base of the PR and between 7611fad and 3d83344.

📒 Files selected for processing (5)
  • src/app/workspaces/[workspaceId]/page.tsx
  • src/entities/work-schedule/api/work-schedule-actions.ts
  • src/features/manage-work-schedule/model/use-work-schedule-state.ts
  • src/features/manage-work-schedule/ui/WorkScheduleBoard.tsx
  • src/widgets/workspace-shell/ui/WorkspaceSidebar.tsx

Comment thread src/entities/work-schedule/api/work-schedule-actions.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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/entities/work-schedule/api/ensure-weekly-work-schedule-entries.ts`:
- Line 33: Update the completion check in ensureWeeklyWorkScheduleEntries so it
validates rows against the current members’ IDs and each expected weekday/date
key, rather than relying on the aggregate count. Ensure missing rows for new
members are created while stale rows for former members do not satisfy the
check; alternatively, make the expected schedule rows idempotently upserted
before returning defaultShift.

In `@src/entities/work-schedule/api/work-schedule-actions.ts`:
- Around line 143-151: Update the work-shift creation flow containing
ensureWeeklyWorkScheduleEntries so the work_shift_types insert and weekly-entry
correction execute atomically in one RPC/database transaction, or reliably
delete the newly created shift on any subsequent failure. Ensure failed actions
cannot leave an orphaned shift that causes duplicate creation on retry.

In `@supabase/migrations/20260713010000_secure_announcements.sql`:
- Around line 62-69: UPDATE 트리거 검증에서 author_id와 created_at이 변경되면 예외를 발생시키도록
추가하세요. supabase/migrations/20260713010000_secure_announcements.sql의 해당 UPDATE
검증과 supabase/migrations/20260714022856_harden_announcements_mutations.sql의 동일한
announcements 변경 검증에 모두 적용하고, 기존 workspace_id 불변 검증 방식과 일관되게 처리하세요.
🪄 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: 4f297636-c27a-4bcd-8fa8-32e8886f4ef3

📥 Commits

Reviewing files that changed from the base of the PR and between 58a8410 and 63cccf0.

📒 Files selected for processing (7)
  • src/entities/notice/api/notice-actions.ts
  • src/entities/work-schedule/api/ensure-weekly-work-schedule-entries.ts
  • src/entities/work-schedule/api/work-schedule-actions.ts
  • src/features/manage-notices/model/use-notice-board-state.ts
  • src/features/manage-work-schedule/ui/WorkScheduleBoard.tsx
  • supabase/migrations/20260713010000_secure_announcements.sql
  • supabase/migrations/20260714022856_harden_announcements_mutations.sql


if (countError) throw new Error(`근무 스케줄 수 조회에 실패했습니다: ${countError.message}`);
if (count === members.length * weekdays.length) return;
if (count === members.length * weekdays.length) return defaultShift;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

멤버·요일별 누락을 전체 행 수로 판정하지 마세요.

count === members.length * weekdays.length는 현재 멤버가 아닌 기존 멤버의 행으로도 만족할 수 있습니다. 신규 멤버의 스케줄이 누락되고 탈퇴 멤버의 행이 남아 있으면 조기 반환되어 DB에 누락 행이 생성되지 않습니다. 현재 멤버 ID와 날짜별 기대 키를 직접 비교하거나, 기대 행 전체를 멱등적으로 upsert 하세요.

🤖 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/entities/work-schedule/api/ensure-weekly-work-schedule-entries.ts` at
line 33, Update the completion check in ensureWeeklyWorkScheduleEntries so it
validates rows against the current members’ IDs and each expected weekday/date
key, rather than relying on the aggregate count. Ensure missing rows for new
members are created while stale rows for former members do not satisfy the
check; alternatively, make the expected schedule rows idempotently upserted
before returning defaultShift.

Comment thread src/entities/work-schedule/api/work-schedule-actions.ts Outdated
Comment thread supabase/migrations/20260713010000_secure_announcements.sql

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/20260714024702_make_work_shift_creation_atomic.sql`:
- Line 2: Remove the explicit transaction control statements from the migration,
including the begin; at the start and its matching commit; at the end, while
leaving the migration operations unchanged so the Supabase CLI remains the sole
transaction manager.
🪄 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: 14ba8ba8-4356-42b4-ab9f-9b3cdf1c6f21

📥 Commits

Reviewing files that changed from the base of the PR and between 63cccf0 and 32561de.

📒 Files selected for processing (6)
  • src/entities/work-schedule/api/ensure-weekly-work-schedule-entries.ts
  • src/entities/work-schedule/api/work-schedule-actions.ts
  • src/shared/model/database.types.ts
  • supabase/migrations/20260713010000_secure_announcements.sql
  • supabase/migrations/20260714022856_harden_announcements_mutations.sql
  • supabase/migrations/20260714024702_make_work_shift_creation_atomic.sql

@@ -0,0 +1,119 @@
-- 근무유형 생성과 이번 주 기본 배정을 하나의 DB 트랜잭션으로 처리한다.
begin;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n## repo files of interest\n'
git ls-files | rg -n '(^|/)(squawk|supabase).*|(\.squawk|\.sqlfluff|migrations|config)'
printf '\n## relevant migration file outline\n'
ast-grep outline supabase/migrations/20260714024702_make_work_shift_creation_atomic.sql --view expanded || true
printf '\n## squawk config references\n'
rg -n --hidden --glob '!.git' 'assume_in_transaction|transaction-nesting|squawk-disable-assume-in-transaction|squawk' .
printf '\n## file sizes\n'
wc -l supabase/migrations/20260714024702_make_work_shift_creation_atomic.sql 2>/dev/null || true
for f in $(git ls-files | rg '(^|/)(\.squawk|squawk|supabase|sqlfluff)'); do
  [ -f "$f" ] && printf '%s: ' "$f" && wc -l "$f"
done

Repository: TeampleRun/syncly

Length of output: 1946


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
p = Path('supabase/migrations/20260714024702_make_work_shift_creation_atomic.sql')
print(p.exists(), p)
if p.exists():
    text = p.read_text()
    for needle in ['begin;', 'commit;', 'revoke all on function', 'grant execute on function']:
        print(needle, text.lower().count(needle))
PY

Repository: TeampleRun/syncly

Length of output: 303


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check Supabase CLI docs locally if present
for f in README.md docs/* supabase/config.toml .squawk.toml .sqlfluff .github/*; do
  [ -e "$f" ] || continue
  printf '\n## %s\n' "$f"
  sed -n '1,220p' "$f" 2>/dev/null | rg -n 'supabase|transaction|assume_in_transaction|squawk|migration|begin|commit' || true
done

Repository: TeampleRun/syncly

Length of output: 503


🌐 Web query:

Supabase CLI migration transaction wrap each file transaction begin commit assume_in_transaction docs

💡 Result:

The Supabase CLI automatically wraps each migration file in a transaction (BEGIN/COMMIT) by default [1][2]. You generally do not need to manually wrap your migration files in BEGIN/COMMIT statements, and doing so can sometimes cause issues or errors with the migration runner [3][4]. Key Technical Details: 1. Default Behavior: The CLI applies each migration file inside a single transaction block [1][2]. 2. Pipeline-Incompatible Statements: Certain SQL statements, such as CREATE INDEX CONCURRENTLY, REINDEX CONCURRENTLY, VACUUM, ALTER SYSTEM, and CLUSTER, cannot be executed within a transaction block in PostgreSQL (SQLSTATE 25001) [5][1][2]. 3. Automatic Handling: Recent versions of the Supabase CLI (specifically those incorporating the native TypeScript migration runner) automatically detect these pipeline-incompatible statements [1][2]. When detected, the CLI will flush the current transaction, execute the incompatible statement standalone, and then resume batching [1][2]. This allows these operations to succeed without requiring manual intervention [1][2]. 4. Potential Issues: Manually adding BEGIN; or COMMIT; within a migration file may conflict with the CLI's internal transaction management, potentially leading to warnings ("there is no transaction in progress") or unexpected migration failure if the CLI loses track of the transaction state [3]. It is recommended to write your migration files as a sequence of standard SQL statements and allow the Supabase CLI to handle the transactional wrapping [4]. If you require a statement that cannot run in a transaction, the CLI handles the necessary adjustments automatically [1][2].

Citations:


begin; / commit;을 제거하세요
Supabase CLI는 마이그레이션 파일을 기본적으로 트랜잭션으로 감싸므로, 이 파일의 명시적 트랜잭션 시작/종료는 충돌할 수 있습니다. 한 군데만 트랜잭션을 관리하도록 맞추세요.

🧰 Tools
🪛 Squawk (2.59.0)

[warning] 2-2: There is an existing transaction already in progress, managed by your migration tool. Put migration statements in separate files to have them be in separate transactions or don't use the assume-in-transaction setting.

(transaction-nesting)

🤖 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/20260714024702_make_work_shift_creation_atomic.sql` at
line 2, Remove the explicit transaction control statements from the migration,
including the begin; at the start and its matching commit; at the end, while
leaving the migration operations unchanged so the Supabase CLI remains the sole
transaction manager.

Source: Linters/SAST tools

@Kwon812 Kwon812 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~

@seongjinss555
seongjinss555 merged commit 269b17c into develop Jul 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants