데이터베이스
[Firebase] 4장 FirebaseStorage를 활용한 파일 업로드 실습
파아란기쁨1
2025. 8. 23. 09:29
반응형

🎠 Firebase Storage 이미지 업로드 실습 가이드
📚 4장: Firebase Storage를 활용한 파일 업로드
안녕하세요! 이번 시간에는 우리가 3장에서 만든 메시지 게시판에 이미지 첨부 기능을 추가해볼 거예요! 마치 놀이공원에서 사진을 찍고 전시하는 것처럼 말이에요! 📸
🎯 이번 시간에 배울 내용
- Firebase Storage 설정하기 - 이미지를 저장할 창고 만들기
- 이미지 파일 선택하기 - 컴퓨터에서 사진 고르기
- 이미지 업로드하기 - 사진을 클라우드에 올리기
- 업로드 진행률 보여주기 - 얼마나 올라갔는지 확인하기
- 이미지 다운로드 및 표시하기 - 올린 사진을 다시 보기
- 이미지와 함께 메시지 저장하기 - 사진과 글을 함께 저장하기
🔧 주요 기능 설명
1. 이미지 선택 기능
- 파일 선택 버튼: 컴퓨터에서 이미지 파일을 선택할 수 있어요
- 미리보기: 선택한 이미지를 바로 확인할 수 있어요
- 파일 형식: JPG, PNG, GIF 등 이미지 파일만 선택 가능해요
2. 업로드 진행률 표시
- 진행바: 이미지가 얼마나 업로드되었는지 시각적으로 보여줘요
- 퍼센트 표시: 0%부터 100%까지 숫자로도 확인할 수 있어요
- 상태 메시지: "업로드 중...", "업로드 완료!" 같은 메시지로 상황을 알려줘요
3. 이미지 저장 및 표시
- Firebase Storage: Google의 클라우드 저장소에 이미지를 안전하게 보관해요
- 다운로드 URL: 업로드된 이미지의 주소를 받아와서 화면에 보여줘요
- 이미지 클릭: 작은 이미지를 클릭하면 새 창에서 크게 볼 수 있어요
🎪 코드 핵심 포인트
이미지 업로드 함수
javascript
// 이미지를 Firebase Storage에 올리는 마법의 함수!
async function uploadImage(file) {
const fileName = Date.now() + '_' + file.name; // 특별한 이름 만들기
const storageRef = storage.ref('images/' + fileName); // 저장 위치 정하기
const uploadTask = storageRef.put(file); // 업로드 시작!
// 진행률을 보여주면서 업로드하기
uploadTask.on('state_changed',
function(snapshot) {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
// 진행바 업데이트하기
}
);
}
🚀 실습 단계
1단계: Firebase Storage 활성화
- Firebase Console에서 프로젝트 선택
- "Storage" 메뉴 클릭
- "시작하기" 버튼 클릭
- 보안 규칙 설정 (테스트 모드로 시작)
2단계: HTML 파일에 설정 추가
html
<script src="https://cdnjs.cloudflare.com/ajax/libs/firebase/9.15.0/firebase-storage-compat.min.js"></script>
3단계: JavaScript에서 Storage 초기화
javascript
const storage = firebase.storage();
4단계: 이미지 업로드 기능 테스트
- 이미지 파일 선택 버튼 클릭
- 컴퓨터에서 사진 파일 선택
- 미리보기에서 선택한 이미지 확인
- "메시지 게시하기" 버튼 클릭
- 진행바로 업로드 상태 확인
- 메시지 보드에서 이미지와 함께 표시된 메시지 확인
🎨 UI/UX 특징
시각적 피드백
- 진행바: 파란색에서 초록색으로 변하는 그라데이션으로 진행률 표시
- 버튼 상태: 업로드 중에는 버튼이 비활성화되어 중복 클릭 방지
- 호버 효과: 마우스를 올리면 버튼과 카드가 살짝 커져서 반응성 제공
사용자 친화적 기능
- 드래그 앤 드롭 스타일: 점선 테두리로 파일을 끌어다 놓을 수 있는 느낌
- 미리보기: 업로드 전에 선택한 이미지를 미리 확인 가능
- 이미지 클릭 확대: 작은 이미지를 클릭하면 새 탭에서 원본 크기로 보기
🔍 코드 상세 분석
1. 파일 선택 감지
javascript
document.getElementById('imageInput').addEventListener('change', function(e) {
const file = e.target.files[0]; // 선택한 첫 번째 파일
if (file) {
selectedFile = file; // 전역 변수에 저장
// FileReader로 미리보기 만들기
const reader = new FileReader();
reader.onload = function(e) {
// 선택한 이미지를 화면에 보여주기
};
reader.readAsDataURL(file);
}
});
2. 업로드 진행률 추적
javascript
uploadTask.on('state_changed',
function(snapshot) {
// 진행률 계산: (업로드된 바이트 / 전체 바이트) × 100
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
// 진행바 너비 업데이트
progressFill.style.width = progress + '%';
progressText.textContent = `업로드 중... ${Math.round(progress)}%`;
}
);
3. 이미지 URL 저장
javascript
// Firestore에 저장할 데이터
const messageData = {
author: author,
message: message,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
imageUrl: imageUrl // 업로드된 이미지의 다운로드 URL
};
🛡️ 보안 및 최적화 팁
Firebase Storage 보안 규칙 (초보자용)
javascript
// 테스트 모드 (누구나 읽기/쓰기 가능)
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true; // 나중에 인증 기능 추가 시 수정 필요
}
}
}
파일 크기 제한
javascript
// 파일 크기 체크 (5MB 제한)
if (file.size > 5 * 1024 * 1024) {
alert('파일 크기는 5MB 이하로 선택해주세요! 🙏');
return;
}
이미지 형식 확인
javascript
// 이미지 파일만 허용
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
alert('이미지 파일만 업로드 할 수 있어요! (JPG, PNG, GIF, WebP) 📸');
return;
}
🚨 자주 발생하는 문제와 해결법
1. 이미지가 안 보여요!
원인: Firebase Storage 보안 규칙이나 CORS 설정 문제 해결법:
- Firebase Console에서 Storage 보안 규칙 확인
- 브라우저 개발자 도구에서 에러 메시지 확인
2. 업로드가 너무 느려요!
원인: 이미지 파일이 너무 큰 경우 해결법:
- 이미지 크기 제한 추가 (예: 5MB)
- 이미지 압축 라이브러리 사용 고려
3. 진행바가 안 움직여요!
원인: 업로드 이벤트 리스너가 제대로 설정되지 않음 해결법:
- uploadTask.on('state_changed', ...) 부분 다시 확인
- 콘솔에서 에러 메시지 확인
🎯 다음 단계 학습 방향
5장 예고: Firebase Authentication
- 사용자 로그인/회원가입 기능
- 본인이 작성한 글만 수정/삭제하기
- 사용자 프로필 이미지 기능
추가 기능 아이디어
- 이미지 여러 개 업로드: 한 번에 여러 장의 사진 첨부
- 이미지 필터: 업로드 전에 사진에 필터 효과 적용
- 이미지 리사이징: 자동으로 적절한 크기로 조정
- 이미지 갤러리: 업로드된 모든 이미지를 갤러리 형태로 보기
💡 쉬운 설명
"여러분! 이번에는 우리 놀이공원 게시판에 사진관을 만들었어요! 📸
- 사진 선택하기: 컴퓨터에 있는 예쁜 사진을 고를 수 있어요
- 사진 올리기: 선택한 사진을 인터넷 구름(클라우드) 위로 올려보내요
- 진행률 보기: 사진이 얼마나 올라갔는지 막대기로 볼 수 있어요 (0%부터 100%까지!)
- 사진 보기: 올린 사진을 다른 친구들도 볼 수 있어요
- 사진 크게 보기: 작은 사진을 클릭하면 크게도 볼 수 있어요!
마치 놀이공원에서 재미있는 순간을 사진으로 찍고, 친구들에게 보여주는 것 같아요! 🎠✨"
🎪 실습 완료 체크리스트
- Firebase Storage가 프로젝트에 추가되었나요?
- 이미지 파일을 선택할 수 있나요?
- 선택한 이미지가 미리보기로 보이나요?
- 업로드 진행률이 진행바로 표시되나요?
- 업로드가 완료되면 메시지와 함께 이미지가 보이나요?
- 이미지를 클릭하면 새 창에서 크게 볼 수 있나요?
- 이미지가 포함된 메시지도 수정/삭제가 되나요?
전체 코드
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎠 놀이공원 메시지 게시판 - 이미지 업로드</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/firebase/9.15.0/firebase-app-compat.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/firebase/9.15.0/firebase-firestore-compat.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/firebase/9.15.0/firebase-storage-compat.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Comic Sans MS', cursive, sans-serif;
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%);
min-height: 100vh;
padding: 20px;
}
.playground-container {
max-width: 800px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
font-size: 1.2em;
opacity: 0.9;
}
.content {
padding: 30px;
}
/* 글쓰기 부스 스타일 */
.writing-booth {
background: linear-gradient(45deg, #ffecd2 0%, #fcb69f 100%);
border-radius: 15px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.booth-title {
font-size: 1.5em;
color: #8b4513;
margin-bottom: 20px;
text-align: center;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 8px;
color: #8b4513;
font-weight: bold;
}
.input-group input,
.input-group textarea {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 10px;
font-size: 16px;
font-family: inherit;
}
.input-group textarea {
height: 100px;
resize: vertical;
}
/* 이미지 업로드 부분 */
.image-upload-section {
background: #f0f8ff;
border: 2px dashed #4169e1;
border-radius: 10px;
padding: 20px;
text-align: center;
margin-bottom: 15px;
}
.file-input-wrapper {
position: relative;
display: inline-block;
}
.file-input {
display: none;
}
.file-input-btn {
background: linear-gradient(45deg, #4169e1, #00bfff);
color: white;
padding: 12px 25px;
border: none;
border-radius: 25px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: transform 0.3s ease;
}
.file-input-btn:hover {
transform: scale(1.05);
}
.image-preview {
margin-top: 15px;
max-width: 200px;
max-height: 200px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.progress-container {
margin-top: 15px;
display: none;
}
.progress-bar {
width: 100%;
height: 20px;
background-color: #e0e0e0;
border-radius: 10px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(45deg, #4caf50, #8bc34a);
width: 0%;
transition: width 0.3s ease;
border-radius: 10px;
}
.progress-text {
text-align: center;
margin-top: 5px;
color: #666;
font-weight: bold;
}
.submit-btn {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
padding: 15px 30px;
border: none;
border-radius: 25px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
width: 100%;
transition: transform 0.3s ease;
}
.submit-btn:hover {
transform: scale(1.02);
}
.submit-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* 메시지 보드 스타일 */
.message-board {
background: linear-gradient(45deg, #a8edea 0%, #fed6e3 100%);
border-radius: 15px;
padding: 25px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.board-title {
font-size: 1.5em;
color: #2c3e50;
margin-bottom: 20px;
text-align: center;
}
.message-card {
background: white;
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
position: relative;
transition: transform 0.3s ease;
}
.message-card:hover {
transform: translateY(-5px);
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.message-author {
font-weight: bold;
color: #2c3e50;
font-size: 1.1em;
}
.message-time {
color: #7f8c8d;
font-size: 0.9em;
}
.message-content {
color: #34495e;
line-height: 1.6;
margin-bottom: 15px;
}
.message-image {
max-width: 100%;
max-height: 300px;
border-radius: 10px;
margin-bottom: 15px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.message-actions {
display: flex;
gap: 10px;
}
.edit-btn, .delete-btn {
padding: 8px 16px;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: transform 0.3s ease;
}
.edit-btn {
background: linear-gradient(45deg, #3498db, #2980b9);
color: white;
}
.delete-btn {
background: linear-gradient(45deg, #e74c3c, #c0392b);
color: white;
}
.edit-btn:hover, .delete-btn:hover {
transform: scale(1.05);
}
.no-messages {
text-align: center;
color: #7f8c8d;
font-size: 1.2em;
padding: 40px;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
font-size: 1.1em;
}
/* 편집 모드 스타일 */
.edit-mode {
background: #fff3cd;
border: 2px solid #ffc107;
}
.edit-input, .edit-textarea {
width: 100%;
padding: 10px;
border: 2px solid #ffc107;
border-radius: 8px;
font-family: inherit;
margin-bottom: 10px;
}
.save-btn, .cancel-btn {
padding: 8px 16px;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
margin-right: 10px;
}
.save-btn {
background: linear-gradient(45deg, #28a745, #20c997);
color: white;
}
.cancel-btn {
background: linear-gradient(45deg, #6c757d, #5a6268);
color: white;
}
</style>
</head>
<body>
<div class="playground-container">
<div class="header">
<h1>🎠 놀이공원 메시지 게시판</h1>
<p>📸 이미지와 함께 즐거운 추억을 공유해보세요!</p>
</div>
<div class="content">
<!-- 글쓰기 부스 -->
<div class="writing-booth">
<h2 class="booth-title">✏️ 글쓰기 부스</h2>
<div class="input-group">
<label for="authorInput">작성자 이름:</label>
<input type="text" id="authorInput" placeholder="이름을 입력하세요">
</div>
<div class="input-group">
<label for="messageInput">메시지:</label>
<textarea id="messageInput" placeholder="즐거운 메시지를 작성해보세요!"></textarea>
</div>
<div class="input-group">
<label>이미지 첨부:</label>
<div class="image-upload-section">
<div class="file-input-wrapper">
<input type="file" id="imageInput" class="file-input" accept="image/*">
<button class="file-input-btn" onclick="document.getElementById('imageInput').click()">
📷 이미지 선택하기
</button>
</div>
<div id="imagePreview"></div>
<div class="progress-container" id="progressContainer">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text" id="progressText">업로드 중...</div>
</div>
</div>
</div>
<button class="submit-btn" id="submitBtn" onclick="submitMessage()">🎪 메시지 게시하기</button>
</div>
<!-- 메시지 보드 -->
<div class="message-board">
<h2 class="board-title">📋 메시지 보드</h2>
<div id="messagesList">
<div class="loading">메시지를 불러오는 중...</div>
</div>
</div>
</div>
</div>
<script>
// ⭐ 1단계: Firebase 설정하기
// 여러분의 Firebase 프로젝트 설정을 여기에 입력하세요!
const firebaseConfig = {
// 여기에 여러분의 Firebase 설정을 넣어주세요
// apiKey, authDomain, projectId, storageBucket 등...
};
// Firebase 초기화하기
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
const storage = firebase.storage();
// 전역 변수들
let messages = [];
let editingMessageId = null;
let selectedFile = null;
// ⭐ 2단계: 이미지 파일 선택했을 때 실행되는 함수
document.getElementById('imageInput').addEventListener('change', function(e) {
const file = e.target.files[0]; // 선택한 파일 가져오기
if (file) {
selectedFile = file; // 선택한 파일을 저장해둠
// 미리보기 이미지 만들기 (마법같은 FileReader 사용!)
const reader = new FileReader();
reader.onload = function(e) {
const preview = document.getElementById('imagePreview');
preview.innerHTML = `
<p>선택한 이미지:</p>
<img src="${e.target.result}" class="image-preview" alt="미리보기">
`;
};
reader.readAsDataURL(file); // 파일을 읽어서 이미지로 보여줌
console.log('이미지가 선택되었어요:', file.name);
}
});
// ⭐ 3단계: 이미지를 Firebase Storage에 업로드하는 함수
async function uploadImage(file) {
return new Promise((resolve, reject) => {
// 파일 이름을 특별하게 만들기 (현재시간 + 원래이름)
const fileName = Date.now() + '_' + file.name;
// Firebase Storage의 'images' 폴더에 저장
const storageRef = storage.ref('images/' + fileName);
const uploadTask = storageRef.put(file);
// 진행상황 표시하기
const progressContainer = document.getElementById('progressContainer');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
progressContainer.style.display = 'block'; // 진행바 보이기
// 업로드 진행상황 감시하기
uploadTask.on('state_changed',
function(snapshot) {
// 진행률 계산하기 (0~100%)
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
// 진행바 업데이트
progressFill.style.width = progress + '%';
progressText.textContent = `업로드 중... ${Math.round(progress)}%`;
console.log('업로드 진행률:', progress + '%');
},
function(error) {
// 에러가 발생했을 때
console.error('업로드 에러:', error);
progressText.textContent = '업로드 실패 😢';
reject(error);
},
function() {
// 업로드 완료!
progressText.textContent = '업로드 완료! 🎉';
// 다운로드 URL 가져오기
uploadTask.snapshot.ref.getDownloadURL().then(function(downloadURL) {
console.log('이미지 업로드 완료! URL:', downloadURL);
// 진행바 숨기기
setTimeout(() => {
progressContainer.style.display = 'none';
progressFill.style.width = '0%';
}, 1000);
resolve(downloadURL); // URL을 반환
});
}
);
});
}
// ⭐ 4단계: 메시지 제출하는 함수 (이미지 업로드 포함)
async function submitMessage() {
const author = document.getElementById('authorInput').value.trim();
const message = document.getElementById('messageInput').value.trim();
const submitBtn = document.getElementById('submitBtn');
// 빈칸 체크하기
if (!author || !message) {
alert('작성자와 메시지를 모두 입력해주세요! 😊');
return;
}
// 버튼 비활성화 (중복 클릭 방지)
submitBtn.disabled = true;
submitBtn.textContent = '게시 중...';
try {
let imageUrl = null;
// 이미지가 선택되었다면 업로드하기
if (selectedFile) {
console.log('이미지 업로드 시작!');
imageUrl = await uploadImage(selectedFile);
}
// Firestore에 메시지 저장하기
const messageData = {
author: author,
message: message,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
imageUrl: imageUrl // 이미지 URL도 함께 저장
};
await db.collection('messages').add(messageData);
// 입력창 비우기
document.getElementById('authorInput').value = '';
document.getElementById('messageInput').value = '';
document.getElementById('imageInput').value = '';
document.getElementById('imagePreview').innerHTML = '';
selectedFile = null;
console.log('메시지가 성공적으로 저장되었어요!');
// 성공 메시지
alert('메시지가 게시되었어요! 🎉');
} catch (error) {
console.error('메시지 저장 중 에러:', error);
alert('메시지 게시에 실패했어요 😢');
}
// 버튼 다시 활성화
submitBtn.disabled = false;
submitBtn.textContent = '🎪 메시지 게시하기';
}
// ⭐ 5단계: 메시지 목록 표시하는 함수 (이미지 포함)
function displayMessages() {
const messagesList = document.getElementById('messagesList');
if (messages.length === 0) {
messagesList.innerHTML = '<div class="no-messages">아직 메시지가 없어요. 첫 번째 메시지를 작성해보세요! 🌟</div>';
return;
}
let messagesHTML = '';
messages.forEach(msg => {
const time = msg.timestamp ? msg.timestamp.toDate().toLocaleString() : '방금 전';
// 편집 모드인지 확인
if (editingMessageId === msg.id) {
// 편집 모드 HTML
messagesHTML += `
<div class="message-card edit-mode">
<input type="text" class="edit-input" id="edit-author-${msg.id}" value="${msg.author}">
<textarea class="edit-textarea" id="edit-message-${msg.id}">${msg.message}</textarea>
${msg.imageUrl ? `<img src="${msg.imageUrl}" class="message-image" alt="첨부 이미지">` : ''}
<div class="message-actions">
<button class="save-btn" onclick="saveMessage('${msg.id}')">💾 저장</button>
<button class="cancel-btn" onclick="cancelEdit()">❌ 취소</button>
</div>
</div>
`;
} else {
// 일반 모드 HTML
messagesHTML += `
<div class="message-card">
<div class="message-header">
<span class="message-author">${msg.author}</span>
<span class="message-time">${time}</span>
</div>
<div class="message-content">${msg.message}</div>
${msg.imageUrl ? `<img src="${msg.imageUrl}" class="message-image" alt="첨부 이미지" onclick="viewFullImage('${msg.imageUrl}')">` : ''}
<div class="message-actions">
<button class="edit-btn" onclick="editMessage('${msg.id}')">✏️ 수정</button>
<button class="delete-btn" onclick="deleteMessage('${msg.id}')">🗑️ 삭제</button>
</div>
</div>
`;
}
});
messagesList.innerHTML = messagesHTML;
}
// ⭐ 6단계: 이미지 클릭했을 때 크게 보여주는 함수
function viewFullImage(imageUrl) {
// 새 창에서 이미지 보기
window.open(imageUrl, '_blank');
}
// 메시지 편집 함수
function editMessage(messageId) {
editingMessageId = messageId;
displayMessages();
}
// 편집 취소 함수
function cancelEdit() {
editingMessageId = null;
displayMessages();
}
// 메시지 저장 함수
async function saveMessage(messageId) {
const newAuthor = document.getElementById(`edit-author-${messageId}`).value.trim();
const newMessage = document.getElementById(`edit-message-${messageId}`).value.trim();
if (!newAuthor || !newMessage) {
alert('작성자와 메시지를 모두 입력해주세요!');
return;
}
try {
await db.collection('messages').doc(messageId).update({
author: newAuthor,
message: newMessage,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
editingMessageId = null;
console.log('메시지가 수정되었어요!');
} catch (error) {
console.error('메시지 수정 중 에러:', error);
alert('메시지 수정에 실패했어요 😢');
}
}
// 메시지 삭제 함수
async function deleteMessage(messageId) {
if (confirm('정말로 이 메시지를 삭제하시겠어요?')) {
try {
const messageDoc = await db.collection('messages').doc(messageId).get();
const messageData = messageDoc.data();
// 이미지가 있다면 Storage에서도 삭제
if (messageData.imageUrl) {
const imageRef = storage.refFromURL(messageData.imageUrl);
await imageRef.delete();
console.log('이미지도 함께 삭제했어요!');
}
// Firestore에서 메시지 삭제
await db.collection('messages').doc(messageId).delete();
console.log('메시지가 삭제되었어요!');
} catch (error) {
console.error('메시지 삭제 중 에러:', error);
alert('메시지 삭제에 실패했어요 😢');
}
}
}
// ⭐ 7단계: 실시간으로 메시지 목록 업데이트하기
function setupRealtimeListener() {
db.collection('messages')
.orderBy('timestamp', 'desc')
.onSnapshot((snapshot) => {
messages = [];
snapshot.forEach((doc) => {
messages.push({
id: doc.id,
...doc.data()
});
});
displayMessages();
console.log('메시지 목록이 업데이트되었어요! 총', messages.length, '개');
});
}
// 페이지 로드되면 실행
window.onload = function() {
console.log('🎪 놀이공원 메시지 게시판에 오신 걸 환영해요!');
setupRealtimeListener();
};
</script>
</body>
</html>
미리 보기

반응형