Skip to content

Commit

Permalink
fix: private message api
Browse files Browse the repository at this point in the history
  • Loading branch information
kcwww committed Dec 13, 2024
1 parent e5af3a6 commit 8f65cb6
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 6 deletions.
18 changes: 18 additions & 0 deletions app/(protected)/main/_utils/fetchCrystal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,22 @@ const fetchCrystal = async (user_id: string) => {
}
};

export const fetchVisitCrystal = async (user_id: string) => {
try {
const response = await clientComponentFetch(BACKEND_ROUTES.VISIT_CRYSTAL, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-User-Id': user_id,
},
});
if (!response.ok)
throw new Error('Failed to fetch crystal' + response.error);

return response.data;
} catch (error) {
throw new Error('Failed to fetch crystal' + error);
}
};

export default fetchCrystal;
18 changes: 18 additions & 0 deletions app/(protected)/main/_utils/fetchMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,22 @@ const fetchMessages = async (crystal_id: string) => {
}
};

export const fetchVisitMessages = async (crystal_id: string) => {
try {
const response = await clientComponentFetch(BACKEND_ROUTES.VISIT_MESSAGES, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Crystal-Id': crystal_id,
},
});
if (!response.ok)
throw new Error('Failed to fetch message' + response.error);

return response.data;
} catch (error) {
throw new Error('Failed to fetch message' + error);
}
};

export default fetchMessages;
4 changes: 2 additions & 2 deletions app/(public)/visit/_components/Decorations.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fetchMessages from '@/app/(protected)/main/_utils/fetchMessages';
import { fetchVisitMessages } from '@/app/(protected)/main/_utils/fetchMessages';
import Decoration from '@/app/(public)/visit/_components/Decoration';
import { getDecoPosition } from '@/shared/components/canvas/utils/canvas';
import { Crystal } from '@/shared/types/crystal';
Expand All @@ -12,7 +12,7 @@ const MemoizedDecoration = memo(Decoration);
const Decorations = ({ crystal }: { crystal: Crystal['_id'] }) => {
const { data, isLoading, isError } = useQuery<Message[]>({
queryKey: ['messages', crystal],
queryFn: () => fetchMessages(crystal),
queryFn: () => fetchVisitMessages(crystal),
gcTime: 0,
staleTime: 0,
});
Expand Down
20 changes: 20 additions & 0 deletions app/(public)/visit/_components/PrivateButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { LockKeyhole } from 'lucide-react';
import useModal from '@/shared/hooks/useModal';
import MODAL_TYPE from '@/shared/constants/modal';

const PrivateButton = ({ isPrivate }: { isPrivate: Date | null }) => {
const { onOpen } = useModal();

return (
<button
onClick={() => {
onOpen(MODAL_TYPE.PRIVATE_INFO);
}}
className="transfrom pointer-events-auto rounded-full p-2 text-white"
>
{isPrivate && <LockKeyhole />}
</button>
);
};

export default PrivateButton;
11 changes: 7 additions & 4 deletions app/(public)/visit/_components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ import { Button } from '@/components/ui/button';
import { ROUTES } from '@/shared/constants/routes';
import { UserType } from '@/shared/types/user';
import { Crystal } from '@/shared/types/crystal';
import fetchCrystal from '@/app/(protected)/main/_utils/fetchCrystal';
import { fetchVisitCrystal } from '@/app/(protected)/main/_utils/fetchCrystal';
import ErrorPage from '@/app/not-found';
import PrivateButton from '@/app/(public)/visit/_components/PrivateButton';

const Visit = ({ userData }: { userData: UserType }) => {
const router = useRouter();
const [current, setCurrent] = useState(0);

const { data, isLoading, isError } = useQuery<Crystal[]>({
queryKey: ['crystal', userData._id],
queryFn: () => fetchCrystal(userData._id),
queryFn: () => fetchVisitCrystal(userData._id),
gcTime: 0,
});

Expand All @@ -51,8 +53,8 @@ const Visit = ({ userData }: { userData: UserType }) => {
return true;
}, [data, current]);

if (isLoading || isError) return null;
if (!data) return null;
if (isLoading) return null;
if (!data || isError) return <ErrorPage />;

return (
<>
Expand All @@ -61,6 +63,7 @@ const Visit = ({ userData }: { userData: UserType }) => {
<div className="flex flex-col items-center gap-2">
<UserHeader user={userData.username!} />
<MessageCount count={data[current].message_id.length} />
<PrivateButton isPrivate={data[current].is_private} />
</div>
<ArrowButtons
maxIndex={data.length - 1}
Expand Down
45 changes: 45 additions & 0 deletions app/api/visit/crystal/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from 'next/server';

import { connectToMongoDB } from '@/shared/database/mongodb/config';
import Crystal from '@/shared/database/mongodb/models/crystalModel';
import User from '@/shared/database/mongodb/models/userModel';
import { UserType } from '@/shared/types/user';
import { CURRENT_SEASON } from '@/shared/constants/Date';
import { CURRENT_YEAR } from '@/shared/constants/Date';

export const dynamic = 'force-dynamic';

export const GET = async (req: NextRequest) => {
const userId = req.headers.get('X-User-Id');

if (!userId) {
return NextResponse.json({ error: 'User ID is required' }, { status: 400 });
}
// MongoDB에 연결
await connectToMongoDB();

try {
// user_id에 해당하는 유저의 crystal_id 조회
const crystal = (
(await User.findOne({ _id: userId }).select('crystal_id')) as UserType
).crystal_id?.get(CURRENT_YEAR)?.[CURRENT_SEASON];

if (!crystal) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}

const crystalDatas = await Promise.all(
crystal.map(async (crystalId) => {
return await Crystal.findOne({ _id: crystalId });
})
);

return NextResponse.json({ data: crystalDatas, ok: true });
} catch (error) {
console.error('Error fetching crystal:', error);
return NextResponse.json(
{ error: 'Failed to fetch crystal' + error },
{ status: 500 }
);
}
};
53 changes: 53 additions & 0 deletions app/api/visit/messages/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';

import { connectToMongoDB } from '@/shared/database/mongodb/config';
import Crystal from '@/shared/database/mongodb/models/crystalModel';
import { Crystal as CrystalType } from '@/shared/types/crystal';
import Message from '@/shared/database/mongodb/models/messageModel';

export const dynamic = 'force-dynamic';

export const GET = async (req: NextRequest) => {
const crystalId = req.headers.get('X-Crystal-Id');

if (!crystalId) {
return NextResponse.json(
{ error: 'Crystal ID is required' },
{ status: 400 }
);
}

try {
// MongoDB에 연결
await connectToMongoDB();

const crystal = (await Crystal.findOne({ _id: crystalId }).select(
'message_id is_private'
)) as CrystalType;

if (!crystal) {
return NextResponse.json({ error: 'Crystal not found' }, { status: 404 });
}
const isPrivate = crystal.is_private;
const messages = crystal.message_id;

const messageDatas = await Promise.all(
messages.map(async (messageId) => {
const message = await Message.findOne({ _id: messageId });
if (message && isPrivate !== null) {
message.content = '비공개 메시지입니다.';
message.sender = '익명';
}
return message;
})
);

return NextResponse.json({ data: messageDatas, ok: true });
} catch (error) {
console.error('Error fetching messages:', error);
return NextResponse.json(
{ error: 'Failed to fetch messages' + error },
{ status: 500 }
);
}
};
52 changes: 52 additions & 0 deletions shared/components/modals/PrivateInfoModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client';

import useModal from '@/shared/hooks/useModal';

import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import MODAL_TYPE from '@/shared/constants/modal';

const PrivateInfoModal = () => {
const { isOpen, onClose, type } = useModal();

if (!isOpen || type !== MODAL_TYPE.PRIVATE_INFO) {
return null;
}

return (
<Dialog open={isOpen}>
<DialogContent
className="no-scrollbar rounded-lg border-none bg-primary text-white"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<DialogHeader className="flex flex-col items-center justify-center gap-2">
<DialogTitle>비공개 수정구슬</DialogTitle>
<DialogDescription>
현재 해당 수정구슬은 비공개로 설정되어 있습니다.
<br />
비공개 수정구슬은 수정구슬 주인만 메세지를 확인할 수 있어요.
</DialogDescription>
</DialogHeader>

<DialogFooter className="block">
<Button
className="w-full"
variant={'secondary'}
onClick={() => onClose()}
>
닫기
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

export default PrivateInfoModal;
2 changes: 2 additions & 0 deletions shared/components/providers/ModalProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FormModal from '@/shared/components/modals/FormModal';
import MessageListModal from '@/shared/components/modals/MessageListModal';
import PrivateModal from '@/shared/components/modals/PrivateModal';
import MessageSubmitModal from '@/shared/components/modals/MessageSubmitModal';
import PrivateInfoModal from '@/shared/components/modals/PrivateInfoModal';

const ModalProvider = () => {
const [isMounted, setIsMounted] = useState(false);
Expand All @@ -28,6 +29,7 @@ const ModalProvider = () => {
<MessageListModal />
<PrivateModal />
<MessageSubmitModal />
<PrivateInfoModal />
</>
);
};
Expand Down
1 change: 1 addition & 0 deletions shared/constants/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const MODAL_TYPE = {
FORM: 'Form',
ALL_MESSAGE: 'AllMessage',
PRIVATE: 'Private',
PRIVATE_INFO: 'PrivateInfo',
MESSAGE_SUBMIT: 'MessageSubmit',
} as const;

Expand Down
2 changes: 2 additions & 0 deletions shared/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const ROUTES = {
export const BACKEND_ROUTES = {
NICKNAME: '/api/user/nickname',
CRYSTAL: '/api/crystal',
VISIT_CRYSTAL: '/api/visit/crystal',
VISIT_MESSAGES: '/api/visit/messages',
MESSAGE: '/api/crystal/message',
MESSAGES: '/api/crystal/messages',
ALL_MESSAGES: '/api/user/messages',
Expand Down

0 comments on commit 8f65cb6

Please sign in to comment.