Skip to content

Commit

Permalink
Merge pull request #11 from treasure-sky/feat/notice-crud
Browse files Browse the repository at this point in the history
  • Loading branch information
flareseek authored Sep 11, 2024
2 parents 22b32f2 + 22b066d commit 4076048
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 0 deletions.
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_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);
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;
}

0 comments on commit 4076048

Please sign in to comment.