feat:소셜 로그인 기반 워크스페이스·공지 연동(#50) - #50
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough워크스페이스와 공지가 목데이터 대신 인증 세션, Supabase 데이터, 멤버십 역할을 사용하도록 변경되었습니다. 공지 CRUD·고정, 대시보드 조회, 워크스페이스 진입 및 스케줄 누락 엔트리 보완 흐름이 추가되었습니다. Changes워크스페이스 인증 사용자 연동
공지 보드 데이터와 권한 연동
근무 스케줄 엔트리 보완
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: 공지 데이터 다시 조회
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
ef20bdb to
7611fad
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (26)
src/app/workspaces/[workspaceId]/layout.tsxsrc/app/workspaces/[workspaceId]/notices/page.tsxsrc/app/workspaces/[workspaceId]/page.tsxsrc/entities/notice/api/get-notice-board.tssrc/entities/notice/api/notice-actions.tssrc/entities/notice/index.tssrc/entities/notice/model/mock-notices.tssrc/entities/notice/model/notice-query.tssrc/entities/notice/model/notice.types.tssrc/entities/workspace-member/api/get-current-workspace-member.tssrc/entities/workspace/api/create-workspace.tssrc/entities/workspace/api/get-my-workspaces.tssrc/entities/workspace/api/use-my-workspaces.tssrc/features/create-workspace/ui/CreateWorkspaceDialog.tsxsrc/features/manage-notices/model/use-notice-board-state.tssrc/features/manage-notices/ui/NoticeComposer.tsxsrc/features/manage-notices/ui/NoticeList.tsxsrc/shared/model/database.types.tssrc/views/dashboard/config/widget-catalog.tsxsrc/views/store-operation/notices/ui/NoticesView.tsxsrc/widgets/store-operation/dashboard-recent-notices/ui/RecentNotices.tsxsrc/widgets/workspace-shell/ui/WorkspaceHeader.tsxsrc/widgets/workspace-shell/ui/WorkspaceShell.tsxsrc/widgets/workspace-shell/ui/WorkspaceSidebar.tsxsupabase/migrations/20260713010000_secure_announcements.sqlsupabase/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
| const initialData = await getNoticeBoard(workspaceId); | ||
|
|
||
| return <NoticesView workspaceId={workspaceId} />; | ||
| return <NoticesView workspaceId={workspaceId} initialData={initialData} />; |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
초기 조회 직후 동일한 공지 보드를 다시 요청하지 않도록 freshness 정책을 설정하세요.
useNoticeBoardState의 useQuery는 initialData만 받고 기본 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.
| 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); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
setNoticePinned는 공지 존재 여부를 확인하지 않아 잘못된 noticeId에도 조용히 성공합니다.
createNotice/updateNotice/deleteNotice는 대상 공지를 먼저 조회(getEditableAnnouncement)해 없으면 에러를 던지지만, setNoticePinned는 getCurrentWorkspaceMember만 호출하고 곧바로 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.
| 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.
| 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}`); | ||
| } |
There was a problem hiding this comment.
🚀 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.
| 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.
| 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; |
There was a problem hiding this comment.
🔒 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 캐시 초기화 방식이 있는지 확인하고, 없다면 적용하세요.
| @@ -0,0 +1,78 @@ | |||
| -- 워크스페이스 생성자는 RPC 인자가 아닌 인증 세션(auth.uid())으로 결정한다. | |||
| -- 기존 p_user_id 인자를 제거해 호출자가 다른 사용자를 생성자로 지정할 수 없게 한다. | |||
| begin; | |||
There was a problem hiding this comment.
🩺 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:
- 1: feat(cli): port migration commands to native TypeScript (CLI-1312) supabase/cli#5671
- 2: supabase/cli@b48fad6
- 3: https://github.com/ghraw/jeremylongshore/claude-code-plugins-plus-skills/HEAD/plugins/saas-packs/supabase-pack/skills/supabase-migration-deep-dive/SKILL.md
- 4: supabase db push bails with false positive when using COMMIT; BEGIN; supabase/cli#5047
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
| -- OAuth 콜백에서 profiles upsert가 완료된 인증 사용자만 생성할 수 있다. | ||
| select real_name into strict v_nickname from profiles where id = v_user_id; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
select ... into strict의 암묵적 예외가 사용자에게 불명확하게 노출될 수 있습니다.
profiles row가 없으면 strict가 no_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` 조회 동작을 유지하세요.
3d83344 to
58a8410
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
src/app/workspaces/[workspaceId]/page.tsxsrc/entities/work-schedule/api/work-schedule-actions.tssrc/features/manage-work-schedule/model/use-work-schedule-state.tssrc/features/manage-work-schedule/ui/WorkScheduleBoard.tsxsrc/widgets/workspace-shell/ui/WorkspaceSidebar.tsx
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
src/entities/notice/api/notice-actions.tssrc/entities/work-schedule/api/ensure-weekly-work-schedule-entries.tssrc/entities/work-schedule/api/work-schedule-actions.tssrc/features/manage-notices/model/use-notice-board-state.tssrc/features/manage-work-schedule/ui/WorkScheduleBoard.tsxsupabase/migrations/20260713010000_secure_announcements.sqlsupabase/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; |
There was a problem hiding this comment.
🗄️ 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.
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/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
📒 Files selected for processing (6)
src/entities/work-schedule/api/ensure-weekly-work-schedule-entries.tssrc/entities/work-schedule/api/work-schedule-actions.tssrc/shared/model/database.types.tssupabase/migrations/20260713010000_secure_announcements.sqlsupabase/migrations/20260714022856_harden_announcements_mutations.sqlsupabase/migrations/20260714024702_make_work_shift_creation_atomic.sql
| @@ -0,0 +1,119 @@ | |||
| -- 근무유형 생성과 이번 주 기본 배정을 하나의 DB 트랜잭션으로 처리한다. | |||
| begin; | |||
There was a problem hiding this comment.
🗄️ 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"
doneRepository: 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))
PYRepository: 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
doneRepository: 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:
- 1: feat(cli): port migration commands to native TypeScript (CLI-1312) supabase/cli#5671
- 2: supabase/cli@b48fad6
- 3: supabase db push bails with false positive when using COMMIT; BEGIN; supabase/cli#5047
- 4: https://www.answeroverflow.com/m/1429254115464052907
- 5: supabase db reset fails on multi-statement migrations (42601) and CONCURRENTLY in pipeline (25001) supabase/cli#5139
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
Pull Request
작업 내용
작업 결과
auth.uid()로 결정됩니다.변경 사항
Added
auth.uid()기반create_workspaceRPC 마이그레이션Changed
Fixed
실행화면
테스트
npm run lint실행 (기존 SignupView dependency 경고 1건)npm run typechecknpm run build리뷰 체크리스트
feature/*->develop, 배포 시develop또는release/*->main)Type/#issue-number/description형식을 따릅니다.console.log, 주석, 임시 코드를 제거했습니다.리뷰 요청사항
create_workspace마이그레이션 적용 후auth.uid()기준 owner·멤버십 생성과 RLS 동작을 중점 확인 부탁드립니다.관련 이슈
Closes #50
Ref #2
Summary by CodeRabbit