Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/notice crud #11

Merged
merged 5 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Empty file removed src/shared/firebase/.gitkeep
Empty file.
132 changes: 132 additions & 0 deletions src/shared/firebase/FirebaseTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { useState } from "react";
import {
createNotice,
getNoticeList,
getNoticeById,
updateNotice,
deleteNotice,
} from "./noticeService";
import { NoticeDto } from "../types/notice";

// 테스트용 컴포넌트 추후 삭제 예정
function FirebaseTest() {
const [order, setOrder] = useState<number>(0);
const [renewal, setRenewal] = useState<boolean>(false);
const [title, setTitle] = useState<string>("");
const [contents, setContents] = useState<string>("");
const [createdAt, setCreatedAt] = useState<Date>(new Date());
const [notices, setNotices] = useState<NoticeDto[]>([]);
const [noticeId, setNoticeId] = useState<string>("");
return (
<>
<div>
<h1>공지사항 생성</h1>
<label>
제목:
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
</label>
<label>
내용:
<input type="text" value={contents} onChange={(e) => setContents(e.target.value)} />
</label>
<label>
순서:
<input type="text" value={order} onChange={(e) => setOrder(parseInt(e.target.value))} />
</label>
<label>
new 표시 여부:
<input type="checkbox" checked={renewal} onChange={(e) => setRenewal(e.target.checked)} />
</label>
<button
onClick={async () => {
await createNotice({
createdAt: new Date(), // 현재 시간으로 업데이트
order,
renewal,
title,
contents,
});
}}
>
생성
</button>
</div>
<div>
<h1>전체 공지사항 조회</h1>
<button
onClick={async () => {
const list = await getNoticeList();
setNotices(list);
}}
>
조회
</button>
<div>
{notices.map((notice, index) => (
<div key={index}>
{`ID: ${notice.id}, title: ${notice.title}, contents: ${notice.contents}, order: ${notice.order}, renewal: ${notice.renewal ? "O" : "X"}, createdAt: ${notice.createdAt}`}
</div>
))}
</div>
</div>
<div>
<h1>공지사항 조회 / 수정</h1>
<label>
공지사항 ID:
<input type="text" value={noticeId} onChange={(e) => setNoticeId(e.target.value)} />
</label>
<button
onClick={async () => {
const notice = await getNoticeById(noticeId);
setNoticeId(notice.id);
setTitle(notice.title);
setContents(notice.contents);
setOrder(notice.order);
setRenewal(notice.renewal);
setCreatedAt(notice.createdAt);
}}
>
조회
</button>
<label>
제목:
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
</label>
<label>
내용:
<input type="text" value={contents} onChange={(e) => setContents(e.target.value)} />
</label>
<label>
순서:
<input type="text" value={order} onChange={(e) => setOrder(parseInt(e.target.value))} />
</label>
<label>
new 표시 여부:
<input type="checkbox" checked={renewal} onChange={(e) => setRenewal(e.target.checked)} />
</label>
<button
onClick={async () => {
await updateNotice(noticeId, {
createdAt,
order,
renewal,
title,
contents,
});
}}
>
수정
</button>
<button
onClick={async () => {
await deleteNotice(noticeId);
}}
>
삭제
</button>
</div>
</>
);
}

export default FirebaseTest;
18 changes: 18 additions & 0 deletions src/shared/firebase/firebaseConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import { getFirestore } from "firebase/firestore/lite";

const firebaseConfig = {
apiKey: import.meta.env.VITE_apiKey,
authDomain: import.meta.env.VITE_authDomain,
projectId: import.meta.env.VITE_projectId,
storageBucket: import.meta.env.VITE_storageBucket,
messagingSenderId: import.meta.env.VITE_messagingSenderId,
appId: import.meta.env.VITE_appId,
measurementId: import.meta.env.VITE_measurementId,
};
treasure-sky marked this conversation as resolved.
Show resolved Hide resolved

const app = initializeApp(firebaseConfig);

export const db = getFirestore(app);
export const analytics = getAnalytics(app);
treasure-sky marked this conversation as resolved.
Show resolved Hide resolved
45 changes: 45 additions & 0 deletions src/shared/firebase/noticeConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
DocumentData,
FirestoreDataConverter,
QueryDocumentSnapshot,
Timestamp,
} from "firebase/firestore/lite";
import { Notice } from "../types/notice";

// 유효성 검사 함수
function validation(notice: Notice) {
if (!(notice.createdAt instanceof Date)) throw new Error("Invalid or missing createdAt.");
if (typeof notice.order !== "number" || notice.order < 0)
throw new Error("Invalid or missing order.");
if (typeof notice.renewal !== "boolean") throw new Error("Invalid or missing renewal.");
if (typeof notice.title !== "string" || !notice.title.trim())
throw new Error("Invalid or missing title.");
if (typeof notice.contents !== "string" || !notice.contents.trim())
throw new Error("Invalid or missing contents.");
}

// Firestore data converter
export const noticeConverter: FirestoreDataConverter<Notice> = {
toFirestore(notice: Notice): DocumentData {
validation(notice);
return {
createdAt: Timestamp.fromDate(notice.createdAt), // Date 객체를 Firestore Timestamp로 변환
order: notice.order,
renewal: notice.renewal,
title: notice.title,
contents: notice.contents,
};
},
fromFirestore(snapshot: QueryDocumentSnapshot): Notice {
const data = snapshot.data();
const notice: Notice = {
createdAt: data.createdAt.toDate(), // Firestore Timestamp를 Date 객체로 변환
order: data.order,
renewal: data.renewal,
title: data.title,
contents: data.contents,
};
validation(notice);
return notice;
},
};
71 changes: 71 additions & 0 deletions src/shared/firebase/noticeService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
collection,
addDoc,
updateDoc,
getDocs,
getDoc,
deleteDoc,
doc,
} from "firebase/firestore/lite";
import { Notice, NoticeDto } from "../types/notice";
import { db } from "./firebaseConfig";
import { noticeConverter } from "./noticeConverter";

// 공지사항 생성
export async function createNotice(newNotice: Notice) {
try {
const docRef = await addDoc(collection(db, "notice").withConverter(noticeConverter), newNotice);
return docRef.id;
} catch (e) {
throw new Error("Error adding document: " + e);
}
}

// 전체 공지사항 조회
export async function getNoticeList(): Promise<NoticeDto[]> {
try {
const querySnapshot = await getDocs(collection(db, "notice").withConverter(noticeConverter));
const result: NoticeDto[] = [];
querySnapshot.forEach((doc) => {
result.push({ id: doc.id, ...doc.data() });
});
return result;
} catch (e) {
throw new Error("Error getting documents: " + e);
}
}

// 공지사항 조회
export async function getNoticeById(noticeId: string): Promise<NoticeDto> {
try {
const docRef = doc(db, "notice", noticeId).withConverter(noticeConverter);
const docSnapshot = await getDoc(docRef);

if (!docSnapshot.exists()) {
throw new Error("No document found with id " + noticeId); // 문서가 없는 경우 예외 처리
}

return { id: docSnapshot.id, ...docSnapshot.data() };
} catch (e) {
throw new Error("Error getting document: " + e);
}
}

// 공지사항 수정
export async function updateNotice(noticeId: string, newNotice: Notice) {
try {
const docRef = doc(db, "notice", noticeId).withConverter(noticeConverter);
await updateDoc(docRef, { ...newNotice });
} catch (error) {
throw new Error("Error updating document: " + error);
}
}

// 공지사항 삭제
export async function deleteNotice(noticeId: string) {
try {
await deleteDoc(doc(db, "notice", noticeId));
} catch (error) {
throw new Error("Error removing document: " + error);
}
}
13 changes: 13 additions & 0 deletions src/shared/types/notice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 공지사항 타입
export interface Notice {
createdAt: Date;
order: number;
renewal: boolean;
title: string;
contents: string;
}

// 공지사항 반환 타입
export interface NoticeDto extends Notice {
id: string;
}