Vue로 PWA 개발 - 그랜파 개발자
개발이 진행됨에 따라 소스 코드를 계속 추가해 갑니다.
처음 마이로그에 접속을 하면 홈페이지가 열립니다. 이 홈페이지에는 등록된 전체 마이로그의 목록을 나타냅니다.

게시글, 댓글, 답글, 사용자 프로필 설명 등 사용자로부터 입력된 HTML이 필요한 경우, 이를 표시하기 전에 sanitizeHtml로 필터링합니다.
sanitizeHtml은 사용자로부터 입력된 HTML 콘텐츠에서 잠재적으로 위험하거나 불필요한 태그와 속성을 제거하여 안전하게 만드는 기능입니다. 주로 사용자가 입력한 HTML을 서버나 클라이언트에서 처리할 때 XSS(Cross-Site Scripting) 공격을 방지하기 위해 사용됩니다.
일반적으로 sanitizeHtml 라이브러리는 신뢰할 수 없는 콘텐츠에 포함된 악의적이거나 위험한 요소를 제거하거나 필터링하여, 웹페이지에 콘텐츠를 표시할 때 보안을 유지할 수 있도록 돕습니다.
sanitize-html 패키지는 Node.js 환경에서 사용할 수 있는 HTML 필터링 라이브러리입니다. npm 패키지 매니저를 사용하여 설치합니다.
npm install sanitize-html
마이로그는 앱에 접속할 때 로드하여 store의 mylogs state에 저장합니다.
// src/main.js
. . .
new Vue({
. .
created() {
. . .
dispatch('mylogs/fetchMylogs');
}
}).$mount('#app')
<!-- src/App.vue -->
<template>
<v-app>
. . .
<v-footer app>
. . .
<v-btn class="ml-2" icon v-if="$route.name == 'home'" @click="orderByAsc">
<v-icon>mdi-arrow-up</v-icon>
</v-btn>
<v-btn class="ml-2" icon v-if="$route.name == 'home'" @click="orderByDesc">
<v-icon>mdi-arrow-down</v-icon>
</v-btn>
. . .
</v-footer>
</v-app>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import router from '@/router'; // Vue Router import
export default {
name: 'App',
. . .
computed: {
...mapGetters('auth', ['user']),
. . .
}
},
methods: {
...mapActions('auth', [‘'logout']),
...mapActions('mylogs', ['fetchMylogs', 'fetchMylogsAsc']),
. . .
orderByDesc() {
this.fetchMylogs();
},
orderByAsc() {
this.fetchMylogsAsc();
}
. . .
}
};
</script>
// src/store/modules/mylogs.js
import { db, doc, collection, getDoc, getDocs } from "@/firebase";
import { query, orderBy } from "@/firebase";
const state = {
. . .
mylogs: [],
};
const mutations = {
. . .
setMylogs(state, mylogs) {
state.mylogs = mylogs;
},
. . .
};
const actions = {
// 전체 마이로그를 작성일 역순으로 로드한다.
async fetchMylogs({ commit }) {
commit('setLoading', true);
try {
const mylogs = [];
const mylogsRef = query(collection(db, "mylogs"), orderBy("createdAt", "desc"));
const querySnapshot = await getDocs(mylogsRef);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
mylogs.push({ id: doc.id, ...doc.data() });
});
// state에 저장한다.
commit('setMylogs', mylogs);
} catch (error) {
console.log("error: ", error);
commit('setError', error);
} finally {
commit('setLoading', false);
}
},
// 전체 마이로그를 작성일 순으로 로드한다.
async fetchMylogsAsc({ commit }) {
commit('setLoading', true);
try {
const mylogs = [];
const mylogsRef = query(collection(db, "mylogs"), orderBy("createdAt", "asc"));
const querySnapshot = await getDocs(mylogsRef);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
mylogs.push({ id: doc.id, ...doc.data() });
});
// state에 저장한다.
commit('setMylogs', mylogs);
} catch (error) {
commit('setError', error);
} finally {
commit('setLoading', false);
}
},
. . .
};
const getters = {
. . .
mylogs: state => state.mylogs
};
export default {
namespaced: true,
state,
mutations,
actions,
getters
};
전체 마이로그는 앱이 열릴 때 로드 하여 mylogs state에 저장 하고, 홈 페이지(HomeView.vue) 가 열릴 때 state에 저장되어 있는 전체 마이로그 mylogs를 화면에 나타냅니다. 마이로그의 내용은 sanitizeContent 함수로 안전한 HTML로 변경한 후 화면에 나타냅니다.
<!-- src/views/HomeView.vue -->
<template>
<v-container>
. . .
<v-row>
<v-col v-for="mylog in mylogs" :key="mylog.id" @click="viewMylog(mylog.id)" cols="12">
<v-card>
<v-card-title style="font-size:1em">{{ mylog.title }}</v-card-title>
<!-- eslint-disable -->
<v-card-text style="font-size:1em" class="mt-n2 mb-n6" v-html="sanitizeContent(mylog.content)"></v-card-text>
<!-- eslint-enable -->
<v-card-subtitle>
<b>댓글</b> <span v-if="mylog.commentCount > 0"> {{mylog.commentCount }} </span>
{{ mylog.userName }} . {{ formatDate(mylog.createdAt) }}
<span v-if="mylog.views > 0" > ({{ mylog.views }})</span>
</v-card-subtitle>
</v-card>
</v-col>
</v-row>
. . .
</v-container>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import sanitizeHtml from 'sanitize-html';
export default {
name:'HomeView',
data() {
return {
};
},
computed: {
...mapGetters('mylogs',['mylogs', 'error', 'loading'])
},
async created() {
},
methods: {
...mapActions('mylogs', ['resetError']),
. . .
// content를 안전한 html로 바꿔준다.
sanitizeContent(content) {
return sanitizeHtml(content.replace(/\n/g, '<br>'), {
allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],
allowedAttributes: {}
});
},
},
};
</script>