my-blog Firestore Database CRUD
1. 프로젝트
1. 프로젝트 설정
vue create my-blog
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, PWA, Router, Vuex
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) n
2. vuetify 설치
cd my-blog
vue add vuetify
? Choose a preset: (Use arrow keys)
Vuetify 2 - Configure Vue CLI (advanced)
> Vuetify 2 - Vue CLI (recommended)
Vuetify 2 - Prototype (rapid development)
Vuetify 3 - Vite (preview)
Vuetify 3 - Vue CLI (preview)
3. 프로그램 설치
npm install firebase
npm install sanitize-html
npm install uuid
4. 프로젝트 폴더 구조
2. my-blog DB CRUD
1. users
users : 사용자 collections
id : collection id
email : 이메일
name : 이름 (별명 포함)
subscriptions[] : 구독하는 글쓴이들 (user.id) 배열
uids[] : Google auth를 통해 돌려받는 uid, 계정연동 (uid) 배열
1. 등록된 전체 회원 정보를 가져오기
async fetchUsers({ commit }) {
try {
const usersSnapshot = await db.collection('users').get();
const users = usersSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
} catch (error) {
commit('setError', error.message);
}
},
2. 회원 등록
async register({ commit, dispatch }, { email, password, name }) {
try {
// 이메일과 비밀번호로 구글 계정을 만든다.
const { user } = await auth.createUserWithEmailAndPassword(email, password, name);
// 구글 계정에서 받은 uid를 웹앱 계정 uids에 넣고 웹앱에 계정을 만든다.
const newUser = {
email: email,
name: name,
uids: [user.uid]
};
// newUser 계정 정보로 웹앱에 계정을 만든다.
dispatch('addUser', newUser);
} catch (error) {
console.log(error);
commit('setError', error.message);
}
},
async addUser({ dispatch, commit }, user) {
try {
await db.collection('users').add(user)
.then(newUser => {
// 로그인 설정
user.id = newUser.id;
console.log('addUser :', user);
commit('setUser', user);
});
// 등록된 모든 회원 정보 읽기
dispatch('fetchUsers');
} catch (error) {
console.error('Error adding user:', error);
}
},
3. 로그인
async loginUser() {
await this.login({ email: this.email, password: this.password });
}
async login({ commit, dispatch }, { email, password }) {
try {
// Google Auth로 구글에 로그인하여 구글의 계정을 받아온다.
const { user } = await auth.signInWithEmailAndPassword(email, password);;
// Google 계정의 uid로 웹앱의 계정 정보를 가져와 로그인 설정을 한다.
dispatch('fetchUserWithUid', {uid: user.uid});
router.push("/"); // home으로
} catch (error) {
commit('setError', error.message);
}
},
// Google 계정의 uid로 웹앱의 계정 정보를 가져
async fetchUserWithUid({ commit }, {uid}) {
try {
const querySnapshot = await db.collection('users').where('uids', 'array-contains', uid).get();
const user = querySnapshot.docs. map(doc => ({ id: doc.id, ...doc.data() }));
// 로그인 설정을 한다.
commit('setUser', user[0]);
} catch (error) {
console.error('Error fetching user:', error);
}
},
4. Google 계정 연동
async addGoogleUid({ commit, dispatch, getters }) {
try {
// Google 계정에 로그인하여 계정 정보를 가져온다.
const { user } = await auth.signInWithPopup(googleProvider);
try {
// 이미 연동된 회원의 경우 알림 메시지 출력한다.
const myUser = getters.getUserByUid(user.uid);
if (myUser) {
console.log("sos:", myUser);
// 이미 연동되어 있다.
commit('setError','이미 연동되어 있습니다.');
} else {
// 구글 연동을 진행한다.
dispatch('addUidToUser', user.uid);
}
} catch (error) {
commit('setError','Error adding google uid');
}
} catch (error) {
commit('setError', error.message);
}
},
// Google 계정의 uid를 웹앱 계정의 uids에 저장한다.
async addUidToUser({ state }, newUid) {
//console.log('user.id:', state.user.id);
if (state.user) {
try {
await db.collection('users').doc(state.user.id).update({
uids: firebase.firestore.FieldValue.arrayUnion(newUid)
});
// Update the local state if neededa
state.user.uids = [...(state.user.uids || []), newUid];
} catch (error) {
console.error('Error adding UID to user:', error);
}
}
},
5. Google 계정으로 로그인
async googleLogin({ commit, getters }) {
try {
// 구글 계정에 로그인
const { user } = await auth.signInWithPopup(googleProvider);
try {
// 구글계정의 uid로 웹앱 계정의 정보를 가져옴.
// 웹앱 계정은 사이트에 접속할 때 전체 회원 정보를 로드하였으므로
// 이미 로드된 회원 리스트에서 구글 계정 uid를 가진 myUser를 가져온다.
const myUser = getters.getUserByUid(user.uid);
if (myUser) {
commit('setUser', myUser);
router.push("/"); // home으로
} else {
console.log('등록된 회원이 아닙니다.');
// 이경우 회원 가입 페이지로 이동 필요
}
} catch (error) {
console.error('Error adding user:', error);
}
} catch (error) {
commit('setError', error.message);
}
},
// 이미 읽어 둔 회원 리스트에서 회원 정보를 얻는다.
getUserByUid: (state) => (uid) => {
return state.users.find(user => user.uids && user.uids.includes(uid));
}
2. posts
posts : 쓴글 collections
id : collection id
title : 제목
content : 내용
createdAt : 글쓴날
userId : 글쓴이 user.id
userName : 글쓴이 이름
viewedBy[] : 글 조회 (date, userId) 배열
views : 조회수
1. 글쓰기
async submitPost() {
const user = this.$store.state.auth.user;
if (user) {
try {
await db.collection('posts').add({
title: this.title,
content: this.content,
userId: user.id,
userName: user.name,
createdAt: new Date(),
views: 0, // Initialize view count
viewedBy: [] // Initialize viewedBy array
});
this.$router.push('/');
} catch (error) {
console.error("Error writing document: ", error);
}
} else {
console.error("User not authenticated");
}
},
2. 회원의 글 가져오기
posts: [] : 글쓴이의 post들
userId = 글쓴이의 user.id
try {
const querySnapshot = await db.collection('posts').where('userId', '==', userId).get();
this.posts = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
} catch (error) {
this.error = error;
} finally {
this.loading = false;
}
3. 나의 독자들 가져오기
회원 정보 항목 중 subscriptions[] 에 나의 user.id를 가지고 있는 회원들의 리스트를 가져 옵니다.
async fetchSubscribers() {
const userId = this.$store.state.auth.user.id;
const querySnapshot = await db.collection('users').where('subscriptions', 'array-contains', userId).get();
this.subscribers = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
},
4. 구독하기
내 회원 정보 중 구독 리스트 subscriptions[]에 포함된 user들의 글들을 가져옵니다.
const userDoc = await db.collection('users').doc(this.user.id).get();
if (userDoc.exists) {
// 구독 리스트
const subscriptions = userDoc.data().subscriptions;
if (subscriptions.length) {
const querySnapshot = await db.collection('posts').where('userId', 'in', subscriptions).get();
this.subscribedPosts = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
}
}
5. 글 검색하기
title 또는 content 에 검색어(searchQuery)를 포함하는 글들을 가져옵니다.
const querySnapshot = await db.collection('posts').get();
this.searchResults = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
.filter(post =>
post.title.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
post.content.toLowerCase().includes(this.searchQuery.toLowerCase())
);
6. 글 상세 보기
- poisId로 글을 읽는다.
- 글을 화면에 나타낼 때 줄바꾸기 등의 이유로 sanitizeContent를 사용한다.
- 글쓴이의 회원 정보를 가져온다.
- 오늘 처음 읽는 회원이면 조회수 증가시킨다.
const postRef = db.collection('posts').doc(postId);
const postDoc = await postRef.get();
if (postDoc.exists) {
this.post = postDoc.data();
// 줄바꾸기 등 일부 html을 사용한다.
this.postContent = this.sanitizeContent(this.post.content);
this.loadComments(postId);
// author 정보를 가져오자.
// 이미 읽어둔 전체 회원 정보에서 글쓴이의 회원 정보를 가져온다.
const users = this.$store.state.auth.users;
this.author = users.find(user => user.id === this.post.userId);
// 글의 조회수를 처리한다.
// 한 사람은 하루에 하나의 조회수를 증가시킬 수 있다.
const today = new Date().toISOString().split('T')[0];
let userId;
const user = this.$store.state.auth.user;
if (user) {
// 계정 등록된 user
userId = user.id;
} else {
// 계정 등록이 되지 않은 user
userId = this.getOrCreateAnonymousUserId();
}
// 조회수를 증가시키기 위해 이미 조회한 회원인지 확인한다.
const alreadyViewed = this.post.viewedBy.some(view => view.userId === userId && view.date === today);
if (!alreadyViewed) {
await postRef.update({
views: firebase.firestore.FieldValue.increment(1),
viewedBy: firebase.firestore.FieldValue.arrayUnion({ userId, date: today })
});
this.post.views += 1; // Update the local view count
}
}
7. 댓글 쓰기
async addComment() {
const postId = this.$route.params.id;
const user = this.$store.state.auth.user;
const userName = user.name;
// 댓글의 구조
const comment = {
content: this.newComment,
userId: user.id,
userName,
createdAt: new Date(),
replies: [] // 댓글에 대한 답글
};
try {
await db.collection('posts').doc(postId).collection('comments').add(comment)
.then(addedComment => {
// 추가된 댓글의 ID를 받아 넣는다.
comment.id = addedComment.id;
this.comments.push(comment);
});
this.newComment = '';
} catch (error) {
this.error = error;
}
},
8. 댓글 삭제
async deleteComment(commentId) {
const postId = this.$route.params.id;
const commentRef = await db.collection('posts').doc(postId).collection('comments').doc(commentId);
const commentDoc = await commentRef.get();
if (commentDoc.exists) {
const commentData = commentDoc.data();
// 답글이 없어야 댓글을 삭제할 수 있다.
if (commentData.replies.length === 0) {
await commentRef.delete();
this.comments = this.comments.filter(c => c.id !== commentId);
} else {
alert('Cannot delete a comment with replies.');
}
}
},
9. 댓글 가져오기
async loadComments(postId) {
try {
const commentsSnapshot = await db.collection('posts').doc(postId).collection('comments').get();
this.comments = commentsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
} catch (error) {
this.error = error;
}
},
10. 댓글에 답글 쓰기
async addReply(commentId) {
const postId = this.$route.params.id;
const userId = this.$store.state.auth.user.id;
const userName = this.$store.state.auth.user.name;
// 답글의 구조
const reply = {
id: uuidv4(), // 답글의 id 생성
userId,
content: this.newReply,
userName,
createdAt: new Date()
};
try {
const commentRef = db.collection('posts').doc(postId).collection('comments').doc(commentId);
await commentRef.update({
replies: firebase.firestore.FieldValue.arrayUnion(reply)
});
const comment = this.comments.find(comment => comment.id === commentId);
comment.replies.push(reply);
} catch (error) {
this.error = error;
}
},
11. 답글 삭제
async deleteReply(commentId, reply) {
const postId = this.$route.params.id;
const commentRef = db.collection('posts').doc(postId).collection('comments').doc(commentId);
const commentDoc = await commentRef.get();
if (commentDoc.exists) {
const commentData = commentDoc.data();
const userId = this.$store.state.auth.user.id;
if (reply.userId === userId) {
const updatedReplies = commentData.replies.filter(r => r.id !== reply.id);
await commentRef.update({
replies: updatedReplies
});
const comment = this.comments.find(c => c.id === commentId);
if (comment) {
comment.replies = updatedReplies;
}
}
}
},
'PWA' 카테고리의 다른 글
ChatGPT와 FCM 개발 - FCM (1) | 2024.09.11 |
---|---|
ChatGPT와 함께 PWA Blog 개발 - my-blog Source (3) | 2024.08.28 |
ChatGPT와 함께 PWA Blog 개발 - 완료, my-blog (0) | 2024.08.28 |
ChatGPT와 함께 PWA Blog 개발 - 독자 (0) | 2024.08.28 |
ChatGPT와 함께 PWA Blog 개발 - 구독 (0) | 2024.08.26 |