diff --git a/src/shared/firebase/.gitkeep b/src/shared/firebase/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/shared/firebase/FirebaseTest.tsx b/src/shared/firebase/FirebaseTest.tsx new file mode 100644 index 0000000..8d5d7aa --- /dev/null +++ b/src/shared/firebase/FirebaseTest.tsx @@ -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(0); + const [renewal, setRenewal] = useState(false); + const [title, setTitle] = useState(""); + const [contents, setContents] = useState(""); + const [createdAt, setCreatedAt] = useState(new Date()); + const [notices, setNotices] = useState([]); + const [noticeId, setNoticeId] = useState(""); + return ( + <> +
+

공지사항 생성

+ + + + + +
+
+

전체 공지사항 조회

+ +
+ {notices.map((notice, index) => ( +
+ {`ID: ${notice.id}, title: ${notice.title}, contents: ${notice.contents}, order: ${notice.order}, renewal: ${notice.renewal ? "O" : "X"}, createdAt: ${notice.createdAt}`} +
+ ))} +
+
+
+

공지사항 조회 / 수정

+ + + + + + + + +
+ + ); +} + +export default FirebaseTest; diff --git a/src/shared/firebase/firebaseConfig.ts b/src/shared/firebase/firebaseConfig.ts new file mode 100644 index 0000000..7061877 --- /dev/null +++ b/src/shared/firebase/firebaseConfig.ts @@ -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_FIREBASE_API_KEY, + authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, + projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, + storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, + messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, + appId: import.meta.env.VITE_FIREBASE_APP_ID, + measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID, +}; + +const app = initializeApp(firebaseConfig); + +export const db = getFirestore(app); +export const analytics = getAnalytics(app); diff --git a/src/shared/firebase/noticeConverter.ts b/src/shared/firebase/noticeConverter.ts new file mode 100644 index 0000000..85d6429 --- /dev/null +++ b/src/shared/firebase/noticeConverter.ts @@ -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 = { + 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; + }, +}; diff --git a/src/shared/firebase/noticeService.ts b/src/shared/firebase/noticeService.ts new file mode 100644 index 0000000..0b71a38 --- /dev/null +++ b/src/shared/firebase/noticeService.ts @@ -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 { + 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 { + 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); + } +} diff --git a/src/shared/types/notice.ts b/src/shared/types/notice.ts new file mode 100644 index 0000000..74fddf4 --- /dev/null +++ b/src/shared/types/notice.ts @@ -0,0 +1,13 @@ +// 공지사항 타입 +export interface Notice { + createdAt: Date; + order: number; + renewal: boolean; + title: string; + contents: string; +} + +// 공지사항 반환 타입 +export interface NoticeDto extends Notice { + id: string; +}