├── .jsbeautifyrc
├── ERD.png
├── README.md
└── reddit-client
├── .gitignore
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
└── src
├── App.vue
├── assets
└── logo.png
├── auth.js
├── db.js
├── firebase.js
├── main.js
├── router.js
├── store
├── auth.js
├── index.js
├── subreddit.js
├── subreddits.js
└── users.js
└── views
├── Home.vue
├── Post.vue
├── Subreddit.vue
└── Subreddits.vue
/.jsbeautifyrc:
--------------------------------------------------------------------------------
1 | {
2 | "wrap_attributes": "force"
3 | }
--------------------------------------------------------------------------------
/ERD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/vue-firestore-reddit-clone/889dece6030724f4a2cbae09cb42a846e4e5b560/ERD.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Full Stack Reddit Clone with Firebase Firestore, Vue.js/Vuex, Bulma
2 |
3 | * [x] Generate Vue App
4 | * [x] Create Firebase Project
5 | * [x] STRICT RULES
6 | * [x] Add Firebase Auth
7 | * https://firebase.google.com/docs/auth/web/google-signin
8 | * https://firebase.google.com/docs/auth/web/manage-users
9 | * Save user on login
10 | * [x] Update Rules
11 | * [x] Add VuexFire
12 | * https://github.com/posva/vuexfire/tree/firestore
13 | * [x] Add a few subreddits in Firestore
14 | * general
15 | * javascript
16 | * learn-programming
17 | * pics
18 | * funny
19 | * [x] Show Subreddits on Home Page
20 | * [x] Show Single Subreddit
21 | * [x] New Post Form
22 | * Update Rules
23 | * [x] Display Posts on Subreddit Page
24 | * Types: Image/Text/Url
25 |
26 |
27 | ## Extra
28 | * [x] Hide submit form if not logged in
29 | * [x] Show Usernames
30 | * [x] Show User Images
31 | * [x] Format Dates
32 | * [x] Fix card formatting
33 | * [x] Search/Filter Posts
34 | * [ ] UpVote/DownVote Posts
35 | * Update Rules
36 | * [ ] Order by total score
37 | * [ ] Order by created
38 | * [ ] Add comment to Post
39 | * Update Rules
40 | * [ ] User Profile
41 | * [ ] Show Submitted Posts
42 | * [ ] Show UpVoted Posts
43 | * [ ] Show Comments
44 | * [ ] Edit Post
45 | * [ ] Edit Comment
46 | * [ ] UpVote/DownVote Comment
47 | * [ ] Reply to Comment
48 | * [ ] Display Error Image if bad link/error
49 | * [ ] Cloud Function Score Aggregator
50 | * https://firebase.google.com/docs/firestore/solutions/aggregation#solution_cloud_functions
51 | * [x] Delete Post
52 | * [ ] Cloud Function "Cascade Delete" comments
53 | * [ ] Upload Images to Firebase storage
54 |
55 | ## Resources
56 |
57 | * https://angularfirebase.com/tag/firestore/
58 |
59 | 
--------------------------------------------------------------------------------
/reddit-client/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/reddit-client/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app',
4 | ],
5 | };
6 |
--------------------------------------------------------------------------------
/reddit-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reddit-client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "buefy": "^0.6.6",
12 | "firebase": "^5.0.4",
13 | "vue": "^2.5.16",
14 | "vue-router": "^3.0.1",
15 | "vuex": "^3.0.1",
16 | "vuexfire": "^3.0.0-alpha.5"
17 | },
18 | "devDependencies": {
19 | "@vue/cli-plugin-babel": "^3.0.0-beta.15",
20 | "@vue/cli-plugin-eslint": "^3.0.0-beta.15",
21 | "@vue/cli-service": "^3.0.0-beta.15",
22 | "@vue/eslint-config-airbnb": "^3.0.0-beta.15",
23 | "node-sass": "^4.9.0",
24 | "sass-loader": "^7.0.1",
25 | "vue-template-compiler": "^2.5.16"
26 | },
27 | "eslintConfig": {
28 | "root": true,
29 | "env": {
30 | "node": true
31 | },
32 | "extends": [
33 | "plugin:vue/essential",
34 | "@vue/airbnb"
35 | ],
36 | "rules": {
37 | "no-param-reassign": 0,
38 | "no-shadow": 0,
39 | "camelcase": 0
40 | },
41 | "parserOptions": {
42 | "parser": "babel-eslint"
43 | }
44 | },
45 | "postcss": {
46 | "plugins": {
47 | "autoprefixer": {}
48 | }
49 | },
50 | "browserslist": [
51 | "> 1%",
52 | "last 2 versions",
53 | "not ie <= 8"
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/reddit-client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/vue-firestore-reddit-clone/889dece6030724f4a2cbae09cb42a846e4e5b560/reddit-client/public/favicon.ico
--------------------------------------------------------------------------------
/reddit-client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Seedling 🌱
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/reddit-client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
36 |
37 |
45 |
46 |
47 |
63 |
--------------------------------------------------------------------------------
/reddit-client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/vue-firestore-reddit-clone/889dece6030724f4a2cbae09cb42a846e4e5b560/reddit-client/src/assets/logo.png
--------------------------------------------------------------------------------
/reddit-client/src/auth.js:
--------------------------------------------------------------------------------
1 | import firebase from '@/firebase';
2 | import store from '@/store';
3 | import db from '@/db';
4 | import router from '@/router';
5 |
6 | firebase.auth().onAuthStateChanged((user) => {
7 | if (user) {
8 | if (user.user) {
9 | /* eslint-disable */
10 | user = user.user;
11 | /* eslint-enable */
12 | }
13 | const setUser = {
14 | id: user.uid,
15 | name: user.displayName,
16 | image: user.photoURL,
17 | created_at: firebase.firestore.FieldValue.serverTimestamp(),
18 | };
19 | db.collection('users').doc(setUser.id).set(setUser);
20 | store.commit('auth/setUser', setUser);
21 | router.push('/subreddits');
22 | } else {
23 | store.commit('auth/setUser', null);
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/reddit-client/src/db.js:
--------------------------------------------------------------------------------
1 | import firebase from '@/firebase';
2 |
3 | const db = firebase.firestore();
4 | db.settings({
5 | timestampsInSnapshots: true,
6 | });
7 |
8 | export default db;
9 |
--------------------------------------------------------------------------------
/reddit-client/src/firebase.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase';
2 |
3 | const config = {
4 | apiKey: 'AIzaSyBsaUdHrN_e2KWq0YbgDunIAJaoR8Sfwz0',
5 | authDomain: 'reddit-clone-c5154.firebaseapp.com',
6 | databaseURL: 'https://reddit-clone-c5154.firebaseio.com',
7 | projectId: 'reddit-clone-c5154',
8 | storageBucket: 'reddit-clone-c5154.appspot.com',
9 | messagingSenderId: '110712594228',
10 | };
11 |
12 | firebase.initializeApp(config);
13 |
14 | export default firebase;
15 |
--------------------------------------------------------------------------------
/reddit-client/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Buefy from 'buefy';
3 | import 'buefy/lib/buefy.css';
4 |
5 | /* eslint-disable */
6 | import firebase from './firebase';
7 | import auth from './auth';
8 | /* eslint-enable */
9 | import App from './App.vue';
10 | import router from './router';
11 | import store from './store/index';
12 |
13 | Vue.config.productionTip = false;
14 |
15 | Vue.use(Buefy);
16 |
17 | new Vue({
18 | router,
19 | store,
20 | render: h => h(App),
21 | }).$mount('#app');
22 |
--------------------------------------------------------------------------------
/reddit-client/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import Home from './views/Home.vue';
4 | import Subreddits from './views/Subreddits.vue';
5 | import Subreddit from './views/Subreddit.vue';
6 | import Post from './views/Post.vue';
7 |
8 | Vue.use(Router);
9 |
10 | export default new Router({
11 | routes: [
12 | {
13 | path: '/',
14 | name: 'home',
15 | component: Home,
16 | },
17 | {
18 | path: '/subreddits',
19 | name: 'subreddits',
20 | component: Subreddits,
21 | },
22 | {
23 | path: '/r/:name',
24 | name: 'subreddit',
25 | component: Subreddit,
26 | },
27 | {
28 | path: '/r/:name/:post_id',
29 | name: 'post',
30 | component: Post,
31 | },
32 | ],
33 | });
34 |
--------------------------------------------------------------------------------
/reddit-client/src/store/auth.js:
--------------------------------------------------------------------------------
1 | import firebase from '@/firebase';
2 |
3 | const state = {
4 | user: {},
5 | isLoggedIn: false,
6 | };
7 |
8 | const mutations = {
9 | setUser(state, user) {
10 | if (user) {
11 | state.user = user;
12 | state.isLoggedIn = true;
13 | } else {
14 | state.user = {};
15 | state.isLoggedIn = false;
16 | }
17 | },
18 | };
19 |
20 | const actions = {
21 | async login() {
22 | const provider = new firebase.auth.GoogleAuthProvider();
23 | await firebase.auth().signInWithPopup(provider);
24 | },
25 | async logout() {
26 | await firebase.auth().signOut();
27 | },
28 | };
29 |
30 | export default {
31 | namespaced: true,
32 | state,
33 | mutations,
34 | actions,
35 | };
36 |
--------------------------------------------------------------------------------
/reddit-client/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import { firebaseMutations } from 'vuexfire';
4 |
5 | import auth from './auth';
6 | import subreddits from './subreddits';
7 | import subreddit from './subreddit';
8 | import users from './users';
9 |
10 | Vue.use(Vuex);
11 |
12 | export default new Vuex.Store({
13 | mutations: firebaseMutations,
14 | modules: {
15 | auth,
16 | subreddits,
17 | subreddit,
18 | users,
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/reddit-client/src/store/subreddit.js:
--------------------------------------------------------------------------------
1 | import { firebaseAction } from 'vuexfire';
2 | import firebase from '@/firebase';
3 | import db from '@/db';
4 |
5 | const posts = db.collection('posts');
6 |
7 | const state = {
8 | subreddits: [],
9 | posts: [],
10 | };
11 |
12 | const getters = {
13 | subreddit: state => (state.subreddits[0] ? state.subreddits[0] : {}),
14 | };
15 |
16 | const actions = {
17 | async createPost({ getters }, post) {
18 | const result = posts.doc();
19 | post.id = result.id;
20 | post.subreddit_id = getters.subreddit.id;
21 | post.user_id = firebase.auth().currentUser.uid;
22 | post.created_at = firebase.firestore.FieldValue.serverTimestamp();
23 | post.updated_at = firebase.firestore.FieldValue.serverTimestamp();
24 | try {
25 | await posts.doc(post.id).set(post);
26 | } catch (error) {
27 | console.error(error);
28 | }
29 | },
30 | async deletePost(_, post_id) {
31 | await posts.doc(post_id).delete();
32 | },
33 | initSubreddit: firebaseAction(({ bindFirebaseRef }, name) => {
34 | bindFirebaseRef('subreddits', db.collection('subreddits').where('name', '==', name));
35 | }),
36 | initPosts: firebaseAction(({ bindFirebaseRef }, subreddit_id) => {
37 | bindFirebaseRef('posts', posts.where('subreddit_id', '==', subreddit_id));
38 | }),
39 | };
40 |
41 | export default {
42 | namespaced: true,
43 | state,
44 | actions,
45 | getters,
46 | };
47 |
48 |
--------------------------------------------------------------------------------
/reddit-client/src/store/subreddits.js:
--------------------------------------------------------------------------------
1 | import { firebaseAction } from 'vuexfire';
2 | import db from '@/db';
3 |
4 | const state = {
5 | subreddits: [],
6 | };
7 |
8 | const actions = {
9 | init: firebaseAction(({ bindFirebaseRef }) => {
10 | bindFirebaseRef('subreddits', db.collection('subreddits'));
11 | }),
12 | };
13 |
14 | export default {
15 | namespaced: true,
16 | state,
17 | actions,
18 | };
19 |
20 |
--------------------------------------------------------------------------------
/reddit-client/src/store/users.js:
--------------------------------------------------------------------------------
1 | import { firebaseAction } from 'vuexfire';
2 | import db from '@/db';
3 |
4 | const state = {
5 | users: [],
6 | };
7 |
8 | const getters = {
9 | usersById(state) {
10 | return state.users.reduce((byId, user) => {
11 | byId[user.id] = user;
12 | return byId;
13 | }, {});
14 | },
15 | };
16 |
17 | const actions = {
18 | init: firebaseAction(({ bindFirebaseRef }) => {
19 | bindFirebaseRef('users', db.collection('users'));
20 | }),
21 | };
22 |
23 | export default {
24 | namespaced: true,
25 | state,
26 | actions,
27 | getters,
28 | };
29 |
30 |
--------------------------------------------------------------------------------
/reddit-client/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | View Subreddits
5 |
6 |
7 |
--------------------------------------------------------------------------------
/reddit-client/src/views/Post.vue:
--------------------------------------------------------------------------------
1 |
2 | {{$route.params.post_id}}
3 |
4 |
5 |
10 |
11 |
14 |
--------------------------------------------------------------------------------
/reddit-client/src/views/Subreddit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
28 |
33 |
34 |
37 |
38 |
40 |
41 |
43 |
44 |
45 |
46 |
64 |
65 | {{post.description}}
66 |
67 |
68 |
69 |
75 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
201 |
202 |
221 |
--------------------------------------------------------------------------------
/reddit-client/src/views/Subreddits.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
28 |
29 |
--------------------------------------------------------------------------------