Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions src/feature/auth/application/auth-query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { useGuardContext } from '@/feature/shared/context/use-gaurd-context';
import { UsecaseContext } from '@/feature/shared/context/usecase-context';
import { createErrorMessage } from '@/feature/shared/error/create-error-message';
import { useRouteNavigation } from '@/routes/use-route-navigation';
import type { UserRole } from '../domain/user-role';

Expand All @@ -26,12 +27,14 @@ export const useSignIn = ({
onSuccess: (response) => {
if (response.type === 'success') {
toMain();
} else {
if (response.code === 'AUTH_002' || response.code === 'GEN_004') {
setResponseMessage('아이디 또는 비밀번호가 일치하지 않습니다.');
return;
}
return;
}
setResponseMessage(
createErrorMessage(
response.code,
'로그인에 실패했습니다. 다시 시도해 주세요.'
)
);
},
});

Expand Down Expand Up @@ -67,13 +70,14 @@ export const useSignUp = ({
onSuccess: (response) => {
if (response.type === 'success') {
toMain();
} else {
if (response.code === 'USER_003') {
setResponseMessage('이미 존재하는 이메일입니다.');
return;
}
setResponseMessage('회원가입에 실패했습니다. 다시 시도해주세요.');
return;
}
setResponseMessage(
createErrorMessage(
response.code,
'회원가입에 실패했습니다. 다시 시도해 주세요.'
)
);
},
});

Expand Down
43 changes: 43 additions & 0 deletions src/feature/shared/error/create-error-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const ERROR_MESSAGES: Record<string, string> = {
// Auth
AUTH_001: '인증 헤더가 없거나 형식이 올바르지 않습니다.',
AUTH_002: '이메일 또는 비밀번호가 일치하지 않습니다.',
AUTH_003: '리프레시 토큰이 유효하지 않습니다.',
AUTH_004: '액세스 토큰이 유효하지 않습니다.',
AUTH_005: '토큰 정보가 일치하지 않습니다.',
AUTH_006: 'OAuth 인증에 실패했습니다.',
AUTH_007: '이 작업을 수행할 권한이 없습니다.',
// User
USER_001: '이미 사용 중인 이메일입니다.',
USER_002: '이미 사용 중인 아이디입니다.',
USER_004: '사용자를 찾을 수 없습니다.',
USER_006: '이메일 인증 코드가 유효하지 않거나 만료되었습니다.',
USER_007: '이메일 인증에 실패했습니다.',
// General
GEN_002: '지원하지 않는 기능입니다.',
GEN_003: '잘못된 요청입니다.',
GEN_004: '필수 항목을 모두 입력해 주세요.',
GEN_005: '요청 형식이 올바르지 않습니다.',
// Store
STORE_001: '가게를 찾을 수 없습니다.',
// Event
EVENT_001: '이벤트를 찾을 수 없습니다.',
// Application
APPLICATION_001: '이 신청에 접근할 권한이 없습니다.',
APPLICATION_002: '신청 내역을 찾을 수 없습니다.',
APPLICATION_003: '이미 제출된 신청입니다.',
// Deposit
DEPOSIT_001: '예치금이 부족합니다.',
DEPOSIT_002: '예치금 정보를 찾을 수 없습니다.',
// S3
S3_001: '파일 업로드 URL 생성에 실패했습니다.',
};

const FALLBACK_MESSAGE = '오류가 발생했습니다. 다시 시도해 주세요.';

export const createErrorMessage = (
code: string,
fallback = FALLBACK_MESSAGE
): string => {
return ERROR_MESSAGES[code] ?? fallback;
};
127 changes: 107 additions & 20 deletions src/mocks/application/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,30 @@ export const applicationResolver: ApplicationResolver = {
const { eventId } = params;
const role = getRole(request);
if (!role) {
return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
return HttpResponse.json(
{
code: 'AUTH_001',
message: 'Authorization header is missing or malformed',
},
{ status: 401 }
);
}
if (role !== 'OWNER') {
return HttpResponse.json({ message: 'Forbidden' }, { status: 403 });
return HttpResponse.json(
{
code: 'AUTH_007',
message: 'You do not have permission to perform this action',
},
{ status: 401 }
);
}

const event = mockEvents.find((e) => e.id === eventId);
if (!event) {
return HttpResponse.json({ message: 'Event not found' }, { status: 404 });
return HttpResponse.json(
{ code: 'EVENT_001', message: 'Event not found' },
{ status: 404 }
);
}

const url = new URL(request.url);
Expand Down Expand Up @@ -75,28 +90,49 @@ export const applicationResolver: ApplicationResolver = {
const { eventId } = params;
const role = getRole(request);
if (!role) {
return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
return HttpResponse.json(
{
code: 'AUTH_001',
message: 'Authorization header is missing or malformed',
},
{ status: 401 }
);
}
if (role !== 'REVIEWER') {
return HttpResponse.json({ message: 'Forbidden' }, { status: 403 });
return HttpResponse.json(
{
code: 'AUTH_007',
message: 'You do not have permission to perform this action',
},
{ status: 401 }
);
}

const event = mockEvents.find((e) => e.id === eventId);
if (!event) {
return HttpResponse.json({ message: 'Event not found' }, { status: 404 });
return HttpResponse.json(
{ code: 'EVENT_001', message: 'Event not found' },
{ status: 404 }
);
}

// 이미 마감된 이벤트
if (!event.isActive) {
return HttpResponse.json(
{ message: 'Event already closed' },
{ code: 'GEN_003', message: 'Event is already closed' },
{ status: 400 }
);
}

// 이미 신청한 이벤트 → ALREADY_APPLIED_EVENT_ID 사용
if (eventId === ALREADY_APPLIED_EVENT_ID) {
return HttpResponse.json({ message: 'Already applied' }, { status: 409 });
return HttpResponse.json(
{
code: 'APPLICATION_003',
message: 'You have already applied for this event',
},
{ status: 409 }
);
}

return HttpResponse.json(null, { status: 200 });
Expand All @@ -106,10 +142,22 @@ export const applicationResolver: ApplicationResolver = {
getMyApplications: ({ request }) => {
const role = getRole(request);
if (!role) {
return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
return HttpResponse.json(
{
code: 'AUTH_001',
message: 'Authorization header is missing or malformed',
},
{ status: 401 }
);
}
if (role !== 'REVIEWER') {
return HttpResponse.json({ message: 'Forbidden' }, { status: 403 });
return HttpResponse.json(
{
code: 'AUTH_007',
message: 'You do not have permission to perform this action',
},
{ status: 401 }
);
}

return HttpResponse.json({
Expand All @@ -126,20 +174,32 @@ export const applicationResolver: ApplicationResolver = {
const { applicationId } = params;
const role = getRole(request);
if (!role) {
return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
return HttpResponse.json(
{
code: 'AUTH_001',
message: 'Authorization header is missing or malformed',
},
{ status: 401 }
);
}

const app = mockApplications.find((a) => a.id === applicationId);
if (!app) {
return HttpResponse.json(
{ message: 'Application not found' },
{ code: 'APPLICATION_002', message: 'Application not found' },
{ status: 404 }
);
}

// 해당 신청의 신청자가 아닌 경우 시뮬레이션: OWNER가 접근하면 403
if (role === 'OWNER') {
return HttpResponse.json({ message: 'Forbidden' }, { status: 403 });
return HttpResponse.json(
{
code: 'APPLICATION_001',
message: 'You are not allowed to access this application',
},
{ status: 403 }
);
}

return HttpResponse.json(null, { status: 200 });
Expand All @@ -150,28 +210,43 @@ export const applicationResolver: ApplicationResolver = {
const { applicationId } = params;
const role = getRole(request);
if (!role) {
return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
return HttpResponse.json(
{
code: 'AUTH_001',
message: 'Authorization header is missing or malformed',
},
{ status: 401 }
);
}

const app = mockApplications.find((a) => a.id === applicationId);
if (!app) {
return HttpResponse.json(
{ message: 'Application not found' },
{ code: 'APPLICATION_002', message: 'Application not found' },
{ status: 404 }
);
}

// 이미 인증을 제출한 신청 → ALREADY_SUBMITTED_APP_ID 사용
if (applicationId === ALREADY_SUBMITTED_APP_ID) {
return HttpResponse.json(
{ message: 'Review already submitted' },
{
code: 'APPLICATION_003',
message: 'Review has already been submitted for this application',
},
{ status: 409 }
);
}

// 해당 신청의 신청자가 아닌 경우: OWNER가 접근하면 403
if (role === 'OWNER') {
return HttpResponse.json({ message: 'Forbidden' }, { status: 403 });
return HttpResponse.json(
{
code: 'APPLICATION_001',
message: 'You are not allowed to access this application',
},
{ status: 403 }
);
}

return HttpResponse.json(null, { status: 200 });
Expand All @@ -182,16 +257,28 @@ export const applicationResolver: ApplicationResolver = {
const { applicationId } = params;
const role = getRole(request);
if (!role) {
return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
return HttpResponse.json(
{
code: 'AUTH_001',
message: 'Authorization header is missing or malformed',
},
{ status: 401 }
);
}
if (role !== 'OWNER') {
return HttpResponse.json({ message: 'Forbidden' }, { status: 403 });
return HttpResponse.json(
{
code: 'AUTH_007',
message: 'You do not have permission to perform this action',
},
{ status: 401 }
);
}

const app = mockApplications.find((a) => a.id === applicationId);
if (!app) {
return HttpResponse.json(
{ message: 'Application not found' },
{ code: 'APPLICATION_002', message: 'Application not found' },
{ status: 404 }
);
}
Expand Down
15 changes: 9 additions & 6 deletions src/mocks/auth/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,34 @@ export const authResolver: AuthResolver = {

if (!body.role || !body.username || !body.email || !body.password) {
return HttpResponse.json(
{ message: 'Required field missing' },
{ code: 'GEN_004', message: 'Required fields are missing or invalid' },
{ status: 400 }
);
}

// 중복 이메일
if (body.email === 'duplicate@example.com') {
return HttpResponse.json(
{ message: 'Email already exists' },
{ code: 'USER_001', message: 'This email is already registered' },
{ status: 409 }
);
}

// 리뷰어: 이메일 인증 코드 만료 시뮬레이션 (email에 +fail 포함)
if (body.role === 'REVIEWER' && body.email.includes('+fail')) {
return HttpResponse.json(
{ message: 'Email verification code expired or invalid' },
{
code: 'USER_006',
message: 'Email verification code is invalid or expired',
},
{ status: 400 }
);
}

// 사장님: 사전 설정된 비밀번호 불일치
if (body.role === 'OWNER' && body.password === 'wrong-owner-secret') {
return HttpResponse.json(
{ message: 'Owner password mismatch' },
{ code: 'AUTH_002', message: 'Invalid credentials' },
{ status: 401 }
);
}
Expand All @@ -51,7 +54,7 @@ export const authResolver: AuthResolver = {

if (!body.role || !body.mail || !body.password) {
return HttpResponse.json(
{ message: 'Required field missing' },
{ code: 'GEN_004', message: 'Required fields are missing or invalid' },
{ status: 400 }
);
}
Expand All @@ -62,7 +65,7 @@ export const authResolver: AuthResolver = {
body.password === 'wrongpassword'
) {
return HttpResponse.json(
{ message: 'Invalid credentials' },
{ code: 'AUTH_002', message: 'Invalid credentials' },
{ status: 401 }
);
}
Expand Down
Loading
Loading