├── README.md
├── client
├── .gitignore
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
└── src
│ ├── App.vue
│ ├── assets
│ └── logo.png
│ ├── components
│ ├── Activities.vue
│ ├── AppAvatar.vue
│ ├── AppNavbar.vue
│ ├── CreateCard.vue
│ ├── NewBoardForm.vue
│ ├── NewListForm.vue
│ ├── SingleBoard.vue
│ └── SingleList.vue
│ ├── feathers-client.js
│ ├── main.js
│ ├── router.js
│ ├── store
│ ├── board.js
│ ├── feathersVuex.js
│ ├── index.js
│ └── localAuth.js
│ ├── validators.js
│ └── views
│ ├── Board.vue
│ ├── Boards.vue
│ ├── Home.vue
│ ├── Login.vue
│ └── SignUp.vue
├── server
├── .editorconfig
├── .env.sample
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── config
│ ├── default.json
│ └── production.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── JWTVerifier.js
│ ├── LocalVerifier.js
│ ├── app.hooks.js
│ ├── app.js
│ ├── authentication.js
│ ├── channels.js
│ ├── hooks
│ │ ├── authorization.js
│ │ └── logger.js
│ ├── index.js
│ ├── middleware
│ │ └── index.js
│ ├── models
│ │ ├── activities.model.js
│ │ ├── boards.model.js
│ │ ├── cards.model.js
│ │ ├── lists.model.js
│ │ └── users.model.js
│ ├── mongoose.js
│ └── services
│ │ ├── activities
│ │ ├── activities.hooks.js
│ │ └── activities.service.js
│ │ ├── boards
│ │ ├── boards.hooks.js
│ │ └── boards.service.js
│ │ ├── cards
│ │ ├── cards.hooks.js
│ │ └── cards.service.js
│ │ ├── index.js
│ │ ├── lists
│ │ ├── lists.hooks.js
│ │ └── lists.service.js
│ │ └── users
│ │ ├── users.hooks.js
│ │ └── users.service.js
└── test
│ ├── app.test.js
│ └── services
│ ├── activities.test.js
│ ├── boards.test.js
│ ├── cards.test.js
│ ├── lists.test.js
│ └── users.test.js
└── trello-clone-erd.png
/README.md:
--------------------------------------------------------------------------------
1 | # Trello Clone
2 |
3 | * [x] Generate Server
4 | * [x] Generate Client
5 | * [x] Add local auth to server
6 | * [x] Add vuetify to client
7 | * [x] Add feathers-vuex to client
8 | * [x] Add login to client
9 | * [x] Add boards service to server
10 | * [x] User can create/list/view boards on client
11 | * [x] Create
12 | * [x] Add ownerId
13 | * [x] List
14 | * [x] Restrict to owner
15 | * [x] View
16 | * [x] Restrict to owner
17 | * [x] Add lists service to server
18 | * [x] User can create/list/view lists for boards on client
19 | * [x] Create
20 | * [ ] Restrict to board owner
21 | * [x] List
22 | * [ ] Restrict to board owner
23 | * [x] Store should update boards/lists when login/logout
24 | * [x] Add cards service to server
25 | * [ ] User can create/list/view cards for lists on client
26 | * [x] Create
27 | * [ ] Restrict to board owner
28 | * [x] List
29 | * [ ] Restrict to board owner
30 | * [x] User can move cards from one list to another
31 | * [ ] Restrict editing/creating lists/cards to board owner
32 | * [ ] Update to work for no boardId in query
33 | * [x] Create activities service on server
34 | * [x] All activities are logged
35 | * [ ] User can edit title of card
36 | * [ ] restrict to board members
37 | * [ ] User can add description to a card
38 | * [ ] restrict to board members
39 | * [ ] User can assign a member to a card
40 | * [ ] restrict to board members
41 | * [ ] User can re-arrange cards in list
42 | * [ ] restrict to board members
43 | * [ ] User can re-arrange lists
44 | * [ ] restrict to board members
45 | * [ ] User can add comments to a card
46 | * [ ] restrict to board members
47 |
48 |
49 | ## Extra
50 |
51 | * [ ] Move component state into the Vuex Store
52 | * [ ] Refactor large components into many smaller components
53 | * [ ] Make it look prettier
54 |
55 | 
56 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve --open",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@feathersjs/authentication-client": "^1.0.2",
12 | "@feathersjs/feathers": "^3.1.5",
13 | "@feathersjs/socketio-client": "^1.1.0",
14 | "feathers-vuex": "^1.2.1",
15 | "marked": "^0.3.19",
16 | "socket.io-client": "^2.1.0",
17 | "vue": "^2.5.16",
18 | "vue-router": "^3.0.1",
19 | "vuetify": "^1.0.16",
20 | "vuex": "^3.0.1"
21 | },
22 | "devDependencies": {
23 | "@vue/cli-plugin-babel": "^3.0.0-beta.9",
24 | "@vue/cli-plugin-eslint": "^3.0.0-beta.9",
25 | "@vue/cli-service": "^3.0.0-beta.9",
26 | "@vue/eslint-config-airbnb": "^3.0.0-beta.9",
27 | "lint-staged": "^6.0.0",
28 | "stylus": "^0.54.5",
29 | "stylus-loader": "^3.0.1",
30 | "vue-cli-plugin-vuetify": "^0.1.2",
31 | "vue-template-compiler": "^2.5.13"
32 | },
33 | "babel": {
34 | "presets": [
35 | "@vue/app"
36 | ]
37 | },
38 | "eslintConfig": {
39 | "root": true,
40 | "extends": [
41 | "plugin:vue/essential",
42 | "@vue/airbnb"
43 | ],
44 | "rules": {
45 | "import/prefer-default-export": 0,
46 | "no-shadow": 0,
47 | "import/extensions": 0,
48 | "no-param-reassign": 0,
49 | "no-underscore-dangle": 0,
50 | "no-return-assign": 0,
51 | "vue/valid-v-on": 0
52 | }
53 | },
54 | "postcss": {
55 | "plugins": {
56 | "autoprefixer": {}
57 | }
58 | },
59 | "browserslist": [
60 | "> 1%",
61 | "last 2 versions",
62 | "not ie <= 8"
63 | ],
64 | "gitHooks": {
65 | "pre-commit": "lint-staged"
66 | },
67 | "lint-staged": {
68 | "*.js": [
69 | "vue-cli-service lint",
70 | "git add"
71 | ],
72 | "*.vue": [
73 | "vue-cli-service lint",
74 | "git add"
75 | ]
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/trello-clone/c5c998cfb51274f9da1b925041e7bef0624fd1bf/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Trello Clone
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 | © 2018 - Made with 💙 by CJ
12 |
13 |
14 |
15 |
16 |
41 |
--------------------------------------------------------------------------------
/client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/trello-clone/c5c998cfb51274f9da1b925041e7bef0624fd1bf/client/src/assets/logo.png
--------------------------------------------------------------------------------
/client/src/components/Activities.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Activities
8 |
9 |
10 |
11 |
12 |
13 | local_activity
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
38 |
--------------------------------------------------------------------------------
/client/src/components/AppAvatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{user.displayName}}
4 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/client/src/components/AppNavbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | Trello Clone
6 |
7 |
8 |
9 | Login
10 | SignUp
11 |
12 |
13 |
14 | LogOut
15 |
16 |
17 |
18 |
19 |
29 |
30 |
--------------------------------------------------------------------------------
/client/src/components/CreateCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Create Card
6 |
7 |
12 |
18 | Create Card
19 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
68 |
--------------------------------------------------------------------------------
/client/src/components/NewBoardForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Create Board
6 |
7 |
12 |
18 |
24 | Create
25 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
65 |
66 |
--------------------------------------------------------------------------------
/client/src/components/NewListForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Create List
6 |
7 |
12 |
18 | Create list
19 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
61 |
--------------------------------------------------------------------------------
/client/src/components/SingleBoard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 | {{board.name}}
9 |
10 |
11 | Go
12 |
13 |
14 |
15 |
16 |
21 |
22 |
--------------------------------------------------------------------------------
/client/src/components/SingleList.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 | {{list.name}}
11 |
12 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
40 |
41 |
42 |
61 |
--------------------------------------------------------------------------------
/client/src/feathers-client.js:
--------------------------------------------------------------------------------
1 | import feathers from '@feathersjs/feathers';
2 | import socketio from '@feathersjs/socketio-client';
3 | import auth from '@feathersjs/authentication-client';
4 | import io from 'socket.io-client';
5 |
6 | let API_URL = 'https://cg-trello-clone.now.sh';
7 |
8 | if (window.location.hostname === 'localhost') {
9 | API_URL = 'http://localhost:3030';
10 | }
11 |
12 | const socket = io(API_URL, {
13 | transports: ['websocket'],
14 | });
15 |
16 | const feathersClient = feathers()
17 | .configure(socketio(socket))
18 | .configure(auth({
19 | storage: window.localStorage,
20 | }));
21 |
22 | export default feathersClient;
23 |
--------------------------------------------------------------------------------
/client/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuetify from 'vuetify';
3 |
4 | import 'vuetify/dist/vuetify.min.css';
5 |
6 | import App from './App.vue';
7 | import router from './router';
8 | import store from './store';
9 |
10 | Vue.use(Vuetify);
11 |
12 | Vue.config.productionTip = false;
13 |
14 | new Vue({
15 | router,
16 | store,
17 | render: h => h(App),
18 | }).$mount('#app');
19 |
--------------------------------------------------------------------------------
/client/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import Home from './views/Home.vue';
4 | import SignUp from './views/SignUp.vue';
5 | import Login from './views/Login.vue';
6 | import Boards from './views/Boards.vue';
7 | import Board from './views/Board.vue';
8 |
9 | import store from './store';
10 |
11 | Vue.use(Router);
12 |
13 | function isLoggedIn(to, from, next) {
14 | store.dispatch('auth/authenticate').then(() => {
15 | next();
16 | }).catch(() => {
17 | next('/login');
18 | });
19 | }
20 |
21 | export default new Router({
22 | routes: [
23 | {
24 | path: '/',
25 | name: 'home',
26 | component: Home,
27 | beforeEnter(to, from, next) {
28 | store.dispatch('auth/authenticate').then(() => {
29 | next('/boards');
30 | }).catch(() => {
31 | next('/login');
32 | });
33 | },
34 | },
35 | {
36 | path: '/signup',
37 | name: 'signup',
38 | component: SignUp,
39 | },
40 | {
41 | path: '/login',
42 | name: 'login',
43 | component: Login,
44 | },
45 | {
46 | path: '/boards',
47 | name: 'boards',
48 | component: Boards,
49 | beforeEnter: isLoggedIn,
50 | },
51 | {
52 | path: '/boards/:id',
53 | name: 'board',
54 | component: Board,
55 | beforeEnter: isLoggedIn,
56 | },
57 | ],
58 | });
59 |
--------------------------------------------------------------------------------
/client/src/store/board.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | droppingList: null,
3 | draggingCard: null,
4 | };
5 |
6 | const mutations = {
7 | setDroppingList: (state, droppingList) => state.droppingList = droppingList,
8 | setDraggingCard: (state, draggingCard) => state.draggingCard = draggingCard,
9 | };
10 |
11 | export default {
12 | namespaced: true,
13 | state,
14 | mutations,
15 | };
16 |
--------------------------------------------------------------------------------
/client/src/store/feathersVuex.js:
--------------------------------------------------------------------------------
1 | import feathersVuex from 'feathers-vuex';
2 | import feathersClient from '../feathers-client';
3 |
4 | const {
5 | service,
6 | auth,
7 | FeathersVuex,
8 | } = feathersVuex(feathersClient, {
9 | idField: '_id',
10 | });
11 |
12 | export default {
13 | service,
14 | auth,
15 | FeathersVuex,
16 | };
17 |
--------------------------------------------------------------------------------
/client/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 |
4 | import feathersVuex from './feathersVuex';
5 |
6 | import localAuth from './localAuth';
7 | import board from './board';
8 |
9 | const {
10 | service,
11 | auth,
12 | FeathersVuex,
13 | } = feathersVuex;
14 |
15 | Vue.use(Vuex);
16 | Vue.use(FeathersVuex);
17 |
18 | export default new Vuex.Store({
19 | modules: {
20 | localAuth,
21 | board,
22 | },
23 | plugins: [
24 | service('users', {
25 | instanceDefaults: {
26 | username: '',
27 | password: '',
28 | displayName: '',
29 | imageUrl: '',
30 | },
31 | }),
32 | service('boards', {
33 | instanceDefaults: {
34 | name: '',
35 | background: '',
36 | },
37 | }),
38 | service('lists', {
39 | instanceDefaults: {
40 | name: '',
41 | order: 0,
42 | boardId: '',
43 | archived: false,
44 | },
45 | }),
46 | service('cards', {
47 | instanceDefaults: {
48 | title: '',
49 | description: '',
50 | order: 0,
51 | archived: false,
52 | listId: '',
53 | boardId: '',
54 | members: [],
55 | },
56 | }),
57 | service('activities', {
58 | instanceDefaults: {
59 | text: '',
60 | boardId: '',
61 | userId: null,
62 | user: 'User',
63 | },
64 | }),
65 | auth({
66 | userService: 'users',
67 | }),
68 | ],
69 | });
70 |
--------------------------------------------------------------------------------
/client/src/store/localAuth.js:
--------------------------------------------------------------------------------
1 | import router from '@/router';
2 |
3 | const actions = {
4 | async login({ dispatch }, { valid, user }) {
5 | if (valid) {
6 | dispatch('auth/authenticate', {
7 | strategy: 'local',
8 | ...user,
9 | }, { root: true }).then(async () => {
10 | router.push('/boards');
11 | }).catch((e) => {
12 | console.error('Authentication error', e);
13 | });
14 | }
15 | },
16 | };
17 |
18 | export default {
19 | namespaced: true,
20 | actions,
21 | };
22 |
--------------------------------------------------------------------------------
/client/src/validators.js:
--------------------------------------------------------------------------------
1 | export const notEmptyRules = [value => !!value || 'Cannot be empty.'];
2 |
--------------------------------------------------------------------------------
/client/src/views/Board.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{boardsError.message}}
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 | {{board.name}}
22 |
23 |
24 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
183 |
--------------------------------------------------------------------------------
/client/src/views/Boards.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
63 |
--------------------------------------------------------------------------------
/client/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Loading...
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/client/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
16 |
23 | Login
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
59 |
--------------------------------------------------------------------------------
/client/src/views/SignUp.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
16 |
22 |
29 |
36 |
42 | SignUp
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
92 |
--------------------------------------------------------------------------------
/server/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/server/.env.sample:
--------------------------------------------------------------------------------
1 | AUTH_SECRET=
2 | MONGO_DB_URI=
--------------------------------------------------------------------------------
/server/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "node": true,
5 | "mocha": true
6 | },
7 | "parserOptions": {
8 | "ecmaVersion": 2017
9 | },
10 | "extends": "eslint:recommended",
11 | "rules": {
12 | "indent": [
13 | "error",
14 | 2
15 | ],
16 | "linebreak-style": [
17 | "error",
18 | "unix"
19 | ],
20 | "quotes": [
21 | "error",
22 | "single"
23 | ],
24 | "semi": [
25 | "error",
26 | "always"
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 |
30 | # IDEs and editors (shamelessly copied from @angular/cli's .gitignore)
31 | /.idea
32 | .project
33 | .classpath
34 | .c9/
35 | *.launch
36 | .settings/
37 | *.sublime-workspace
38 |
39 | # IDE - VSCode
40 | .vscode/*
41 | !.vscode/settings.json
42 | !.vscode/tasks.json
43 | !.vscode/launch.json
44 | !.vscode/extensions.json
45 |
46 | ### Linux ###
47 | *~
48 |
49 | # temporary files which can be created if a process still has a handle open of a deleted file
50 | .fuse_hidden*
51 |
52 | # KDE directory preferences
53 | .directory
54 |
55 | # Linux trash folder which might appear on any partition or disk
56 | .Trash-*
57 |
58 | # .nfs files are created when an open file is removed but is still being accessed
59 | .nfs*
60 |
61 | ### OSX ###
62 | *.DS_Store
63 | .AppleDouble
64 | .LSOverride
65 |
66 | # Icon must end with two \r
67 | Icon
68 |
69 |
70 | # Thumbnails
71 | ._*
72 |
73 | # Files that might appear in the root of a volume
74 | .DocumentRevisions-V100
75 | .fseventsd
76 | .Spotlight-V100
77 | .TemporaryItems
78 | .Trashes
79 | .VolumeIcon.icns
80 | .com.apple.timemachine.donotpresent
81 |
82 | # Directories potentially created on remote AFP share
83 | .AppleDB
84 | .AppleDesktop
85 | Network Trash Folder
86 | Temporary Items
87 | .apdisk
88 |
89 | ### Windows ###
90 | # Windows thumbnail cache files
91 | Thumbs.db
92 | ehthumbs.db
93 | ehthumbs_vista.db
94 |
95 | # Folder config file
96 | Desktop.ini
97 |
98 | # Recycle Bin used on file shares
99 | $RECYCLE.BIN/
100 |
101 | # Windows Installer files
102 | *.cab
103 | *.msi
104 | *.msm
105 | *.msp
106 |
107 | # Windows shortcuts
108 | *.lnk
109 |
110 | # Others
111 | lib/
112 | data/
113 | .env
114 |
--------------------------------------------------------------------------------
/server/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Feathers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # trello-clone-server
2 |
3 | > A trello clone.
4 |
5 | ## About
6 |
7 | This project uses [Feathers](http://feathersjs.com). An open source web framework for building modern real-time applications.
8 |
9 | ## Getting Started
10 |
11 | Getting up and running is as easy as 1, 2, 3.
12 |
13 | 1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed.
14 | 2. Install your dependencies
15 |
16 | ```
17 | cd path/to/trello-clone-server; npm install
18 | ```
19 |
20 | 3. Copy the .env.sample file to .env
21 |
22 | ```
23 | cp .env.sample .env
24 | ```
25 |
26 | 4. Update the new .env with your environmental variables
27 |
28 | 5. Start your app
29 |
30 | ```
31 | npm start
32 | ```
33 |
34 | ## Testing
35 |
36 | Simply run `npm test` and all your tests in the `test/` directory will be run.
37 |
38 | ## Scaffolding
39 |
40 | Feathers has a powerful command line interface. Here are a few things it can do:
41 |
42 | ```
43 | $ npm install -g @feathersjs/cli # Install Feathers CLI
44 |
45 | $ feathers generate service # Generate a new Service
46 | $ feathers generate hook # Generate a new Hook
47 | $ feathers generate model # Generate a new Model
48 | $ feathers help # Show all commands
49 | ```
50 |
51 | ## Help
52 |
53 | For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com).
54 |
55 | ## Changelog
56 |
57 | __0.1.0__
58 |
59 | - Initial release
60 |
61 | ## License
62 |
63 | Copyright (c) 2016
64 |
65 | Licensed under the [MIT license](LICENSE).
66 |
--------------------------------------------------------------------------------
/server/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "host": "localhost",
3 | "port": 3030,
4 | "public": "../public/",
5 | "paginate": {
6 | "default": 500,
7 | "max": 1000
8 | },
9 | "authentication": {
10 | "secret": "AUTH_SECRET",
11 | "strategies": [
12 | "jwt",
13 | "local"
14 | ],
15 | "path": "/authentication",
16 | "service": "users",
17 | "jwt": {
18 | "header": {
19 | "typ": "access"
20 | },
21 | "audience": "https://cg-trello-clone.now.sh",
22 | "subject": "auth",
23 | "issuer": "coding.garden",
24 | "algorithm": "HS256",
25 | "expiresIn": "1d"
26 | },
27 | "local": {
28 | "entity": "user",
29 | "usernameField": "\\username",
30 | "passwordField": "password"
31 | }
32 | },
33 | "mongodb": "MONGO_DB_URI"
34 | }
35 |
--------------------------------------------------------------------------------
/server/config/production.json:
--------------------------------------------------------------------------------
1 | {
2 | "host": "cg-trello-clone.now.sh",
3 | "port": "PORT"
4 | }
5 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trello-clone-server",
3 | "description": "A trello clone.",
4 | "version": "0.0.0",
5 | "homepage": "",
6 | "main": "src",
7 | "keywords": [
8 | "feathers"
9 | ],
10 | "author": {
11 | "name": "CJ R",
12 | "email": "cj@null.computer"
13 | },
14 | "contributors": [],
15 | "bugs": {},
16 | "directories": {
17 | "lib": "src",
18 | "test": "test/"
19 | },
20 | "engines": {
21 | "node": "^8.0.0",
22 | "npm": ">= 3.0.0"
23 | },
24 | "scripts": {
25 | "test": "npm run eslint && npm run mocha",
26 | "eslint": "eslint src/. test/. --config .eslintrc.json",
27 | "start": "node src/",
28 | "dev": "nodemon src/",
29 | "mocha": "mocha test/ --recursive --exit"
30 | },
31 | "dependencies": {
32 | "@feathersjs/authentication": "^2.1.5",
33 | "@feathersjs/authentication-jwt": "^2.0.1",
34 | "@feathersjs/authentication-local": "^1.2.1",
35 | "@feathersjs/configuration": "^1.0.2",
36 | "@feathersjs/errors": "^3.3.0",
37 | "@feathersjs/express": "^1.2.2",
38 | "@feathersjs/feathers": "^3.1.5",
39 | "@feathersjs/socketio": "^3.2.1",
40 | "compression": "^1.7.2",
41 | "cors": "^2.8.4",
42 | "dotenv": "^6.0.0",
43 | "feathers-authentication-hooks": "^0.2.0",
44 | "feathers-mongoose": "^6.1.1",
45 | "helmet": "^3.12.0",
46 | "mongoose": "^5.7.5",
47 | "serve-favicon": "^2.5.0",
48 | "winston": "^2.4.2"
49 | },
50 | "devDependencies": {
51 | "eslint": "^4.19.1",
52 | "mocha": "^5.1.1",
53 | "nodemon": "^1.17.4",
54 | "request": "^2.85.0",
55 | "request-promise": "^4.2.2"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/server/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/trello-clone/c5c998cfb51274f9da1b925041e7bef0624fd1bf/server/public/favicon.ico
--------------------------------------------------------------------------------
/server/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Welcome to Feathers
4 |
62 |
63 |
64 |
65 |
66 | A REST and realtime API layer for modern applications.
67 |
68 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/server/src/JWTVerifier.js:
--------------------------------------------------------------------------------
1 | const jwt = require('@feathersjs/authentication-jwt');
2 |
3 | class JWTVerifier extends jwt.Verifier {
4 | async verify(req, payload, done) {
5 | const User = require('mongoose').model('users');
6 |
7 | const user = await User.findOne({
8 | _id: payload.userId
9 | }, '_id username displayName imageUrl').lean();
10 |
11 | payload.user = user;
12 |
13 | done(null, user, payload);
14 | }
15 | }
16 |
17 | module.exports = JWTVerifier;
18 |
--------------------------------------------------------------------------------
/server/src/LocalVerifier.js:
--------------------------------------------------------------------------------
1 | const Debug = require('debug');
2 | const {
3 | omit
4 | } = require('lodash');
5 |
6 | const debug = Debug('@feathersjs/authentication-local:verify');
7 |
8 | const Verifier = require('@feathersjs/authentication-local/lib/verifier');
9 |
10 | class LocalVerifier extends Verifier {
11 | verify(req, username, password, done) {
12 | debug('Checking credentials', username, password);
13 |
14 | const id = this.service.id;
15 | const usernameField = this.options.entityUsernameField || this.options.usernameField;
16 | const params = Object.assign({
17 | 'query': {
18 | [usernameField]: username,
19 | '$limit': 1
20 | }
21 | }, omit(req.params, 'query', 'provider', 'headers', 'session', 'cookies'));
22 |
23 | if (id === null || id === undefined) {
24 | debug('failed: the service.id was not set');
25 | return done(new Error('the `id` property must be set on the entity service for authentication'));
26 | }
27 |
28 | // Look up the entity
29 | this.service.find(params)
30 | .then(response => {
31 | const results = response.data || response;
32 | if (!results.length) {
33 | debug(`a record with ${usernameField} of '${username}' did not exist`);
34 | }
35 | return this._normalizeResult(response);
36 | })
37 | .then(entity => this._comparePassword(entity, password))
38 | .then(entity => {
39 | const id = entity[this.service.id];
40 | const payload = {
41 | [`${this.options.entity}Id`]: id
42 | };
43 |
44 | delete entity.password;
45 | payload.user = entity;
46 |
47 | done(null, entity, payload);
48 | })
49 | .catch(error => error ? done(error) : done(null, error, {
50 | message: 'Invalid login'
51 | }));
52 | }
53 | }
54 |
55 | module.exports = LocalVerifier;
56 |
--------------------------------------------------------------------------------
/server/src/app.hooks.js:
--------------------------------------------------------------------------------
1 | // Application hooks that run for every service
2 | const logger = require('./hooks/logger');
3 |
4 | module.exports = {
5 | before: {
6 | all: [ logger() ],
7 | find: [],
8 | get: [],
9 | create: [],
10 | update: [],
11 | patch: [],
12 | remove: []
13 | },
14 |
15 | after: {
16 | all: [ logger() ],
17 | find: [],
18 | get: [],
19 | create: [],
20 | update: [],
21 | patch: [],
22 | remove: []
23 | },
24 |
25 | error: {
26 | all: [ logger() ],
27 | find: [],
28 | get: [],
29 | create: [],
30 | update: [],
31 | patch: [],
32 | remove: []
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/server/src/app.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const favicon = require('serve-favicon');
3 | const compress = require('compression');
4 | const cors = require('cors');
5 | const helmet = require('helmet');
6 | const logger = require('winston');
7 |
8 | const feathers = require('@feathersjs/feathers');
9 | const configuration = require('@feathersjs/configuration');
10 | const express = require('@feathersjs/express');
11 | const socketio = require('@feathersjs/socketio');
12 |
13 | require('dotenv').config();
14 |
15 | const middleware = require('./middleware');
16 | const services = require('./services');
17 | const appHooks = require('./app.hooks');
18 | const channels = require('./channels');
19 |
20 | const authentication = require('./authentication');
21 |
22 | const mongoose = require('./mongoose');
23 |
24 | const app = express(feathers());
25 |
26 | // Load app configuration
27 | app.configure(configuration());
28 | // Enable CORS, security, compression, favicon and body parsing
29 | app.use(cors());
30 | app.use(helmet());
31 | app.use(compress());
32 | app.use(express.json());
33 | app.use(express.urlencoded({ extended: true }));
34 | app.use(favicon(path.join(app.get('public'), 'favicon.ico')));
35 | // Host the public folder
36 | app.use('/', express.static(app.get('public')));
37 |
38 | // Set up Plugins and providers
39 | app.configure(express.rest());
40 | app.configure(socketio());
41 |
42 | app.configure(mongoose);
43 |
44 | // Configure other middleware (see `middleware/index.js`)
45 | app.configure(middleware);
46 | app.configure(authentication);
47 | // Set up our services (see `services/index.js`)
48 | app.configure(services);
49 | // Set up event channels (see channels.js)
50 | app.configure(channels);
51 |
52 | // Configure a middleware for 404s and the error handler
53 | app.use(express.notFound());
54 | app.use(express.errorHandler({ logger }));
55 |
56 | app.hooks(appHooks);
57 |
58 | module.exports = app;
59 |
--------------------------------------------------------------------------------
/server/src/authentication.js:
--------------------------------------------------------------------------------
1 | const authentication = require('@feathersjs/authentication');
2 | const jwt = require('@feathersjs/authentication-jwt');
3 | const local = require('@feathersjs/authentication-local');
4 |
5 | const JWTVerifier = require('./JWTVerifier');
6 | const LocalVerifier = require('./LocalVerifier');
7 |
8 | module.exports = function (app) {
9 | const config = app.get('authentication');
10 |
11 | // Set up authentication with the secret
12 | app.configure(authentication(config));
13 | // app.configure(local(localConfig));
14 |
15 | app.configure(jwt({
16 | Verifier: JWTVerifier
17 | }));
18 |
19 | app.configure(local({
20 | Verifier: LocalVerifier
21 | }));
22 |
23 | // The `authentication` service is used to create a JWT.
24 | // The before `create` hook registers strategies that can be used
25 | // to create a new valid JWT (e.g. local or oauth2)
26 | app.service('authentication').hooks({
27 | before: {
28 | create: [
29 | authentication.hooks.authenticate(config.strategies)
30 | ],
31 | remove: [
32 | authentication.hooks.authenticate('jwt')
33 | ]
34 | }
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/server/src/channels.js:
--------------------------------------------------------------------------------
1 | module.exports = function(app) {
2 | if(typeof app.channel !== 'function') {
3 | // If no real-time functionality has been configured just return
4 | return;
5 | }
6 |
7 | app.on('connection', connection => {
8 | // On a new real-time connection, add it to the anonymous channel
9 | app.channel('anonymous').join(connection);
10 | });
11 |
12 | app.on('login', (authResult, { connection }) => {
13 | // connection can be undefined if there is no
14 | // real-time connection, e.g. when logging in via REST
15 | if(connection) {
16 | // Obtain the logged in user from the connection
17 | // const user = connection.user;
18 |
19 | // The connection is no longer anonymous, remove it
20 | app.channel('anonymous').leave(connection);
21 |
22 | // Add it to the authenticated user channel
23 | app.channel('authenticated').join(connection);
24 |
25 | // Channels can be named anything and joined on any condition
26 |
27 | // E.g. to send real-time events only to admins use
28 | // if(user.isAdmin) { app.channel('admins').join(connection); }
29 |
30 | // If the user has joined e.g. chat rooms
31 | // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(channel));
32 |
33 | // Easily organize users by email and userid for things like messaging
34 | // app.channel(`emails/${user.email}`).join(channel);
35 | // app.channel(`userIds/$(user.id}`).join(channel);
36 | }
37 | });
38 |
39 | // eslint-disable-next-line no-unused-vars
40 | app.publish((data, hook) => {
41 | // Here you can add event publishers to channels set up in `channels.js`
42 | // To publish only for a specific event use `app.publish(eventname, () => {})`
43 |
44 | console.log('Publishing all events to all authenticated users. See `channels.js` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line
45 |
46 | // e.g. to publish all service events to all authenticated users use
47 | return app.channel('authenticated');
48 | });
49 |
50 | // Here you can also add service specific event publishers
51 | // e..g the publish the `users` service `created` event to the `admins` channel
52 | // app.service('users').publish('created', () => app.channel('admins'));
53 |
54 | // With the userid and email organization from above you can easily select involved users
55 | // app.service('messages').publish(() => {
56 | // return [
57 | // app.channel(`userIds/${data.createdBy}`),
58 | // app.channel(`emails/${data.recipientEmail}`)
59 | // ];
60 | // });
61 | };
62 |
--------------------------------------------------------------------------------
/server/src/hooks/authorization.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | async function isBoardOwner(context) {
4 | let boardId = context.params.query.boardId || context.data.boardId;
5 |
6 | if (!boardId) {
7 | return context;
8 | }
9 |
10 | const {
11 | _id
12 | } = context.params.user;
13 |
14 | const boards = mongoose.model('boards');
15 | const board = await boards.findOne({
16 | _id: boardId
17 | });
18 |
19 | if (board) {
20 | if (board.ownerId.toString() == _id.toString()) {
21 | return context;
22 | } else {
23 | return Promise.reject(new Error('Un-Authorized'));
24 | }
25 | }
26 |
27 | return context;
28 | }
29 |
30 | module.exports = {
31 | isBoardOwner
32 | };
33 |
--------------------------------------------------------------------------------
/server/src/hooks/logger.js:
--------------------------------------------------------------------------------
1 | // A hook that logs service method before, after and error
2 | // See https://github.com/winstonjs/winston for documentation
3 | // about the logger.
4 | const logger = require('winston');
5 |
6 | // To see more detailed messages, uncomment the following line:
7 | // logger.level = 'debug';
8 |
9 | module.exports = function () {
10 | return context => {
11 | // This debugs the service call and a stringified version of the hook context
12 | // You can customize the message (and logger) to your needs
13 | logger.debug(`${context.type} app.service('${context.path}').${context.method}()`);
14 |
15 | if(typeof context.toJSON === 'function') {
16 | logger.debug('Hook Context', JSON.stringify(context, null, ' '));
17 | }
18 |
19 | if (context.error) {
20 | logger.error(context.error);
21 | }
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/server/src/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const logger = require('winston');
3 | const app = require('./app');
4 | const port = app.get('port');
5 | const server = app.listen(port);
6 |
7 | process.on('unhandledRejection', (reason, p) =>
8 | logger.error('Unhandled Rejection at: Promise ', p, reason)
9 | );
10 |
11 | server.on('listening', () =>
12 | logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
13 | );
14 |
--------------------------------------------------------------------------------
/server/src/middleware/index.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-unused-vars
2 | module.exports = function (app) {
3 | // Add your custom middleware here. Remember that
4 | // in Express, the order matters.
5 | };
6 |
--------------------------------------------------------------------------------
/server/src/models/activities.model.js:
--------------------------------------------------------------------------------
1 | // activities-model.js - A mongoose model
2 | //
3 | // See http://mongoosejs.com/docs/models.html
4 | // for more of what you can do here.
5 | module.exports = function (app) {
6 | const mongooseClient = app.get('mongooseClient');
7 | const { Schema } = mongooseClient;
8 | const activities = new Schema({
9 | text: {
10 | type: String,
11 | required: true
12 | },
13 | userId: {
14 | type: Schema.Types.ObjectId,
15 | required: true
16 | },
17 | boardId: {
18 | type: Schema.Types.ObjectId,
19 | ref: 'boards'
20 | },
21 | }, {
22 | timestamps: true
23 | });
24 |
25 | return mongooseClient.model('activities', activities);
26 | };
27 |
--------------------------------------------------------------------------------
/server/src/models/boards.model.js:
--------------------------------------------------------------------------------
1 | // boards-model.js - A mongoose model
2 | //
3 | // See http://mongoosejs.com/docs/models.html
4 | // for more of what you can do here.
5 | module.exports = function (app) {
6 | const mongooseClient = app.get('mongooseClient');
7 | const { Schema } = mongooseClient;
8 | const boards = new Schema({
9 | name: {
10 | type: String,
11 | required: true
12 | },
13 | background: {
14 | type: String,
15 | required: true
16 | },
17 | ownerId: {
18 | type: Schema.Types.ObjectId,
19 | ref: 'users'
20 | },
21 | memberIds: [{
22 | type: Schema.Types.ObjectId,
23 | ref: 'users'
24 | }],
25 | }, {
26 | timestamps: true
27 | });
28 |
29 | return mongooseClient.model('boards', boards);
30 | };
31 |
--------------------------------------------------------------------------------
/server/src/models/cards.model.js:
--------------------------------------------------------------------------------
1 | // cards-model.js - A mongoose model
2 | //
3 | // See http://mongoosejs.com/docs/models.html
4 | // for more of what you can do here.
5 | module.exports = function (app) {
6 | const mongooseClient = app.get('mongooseClient');
7 | const { Schema } = mongooseClient;
8 | const cards = new Schema({
9 | title: {
10 | type: String,
11 | required: true
12 | },
13 | description: {
14 | type: String
15 | },
16 | order: {
17 | type: Number,
18 | required: true,
19 | default: 0,
20 | },
21 | archived: {
22 | type: Boolean,
23 | required: true,
24 | default: false,
25 | },
26 | listId: {
27 | type: Schema.Types.ObjectId,
28 | ref: 'lists'
29 | },
30 | boardId: {
31 | type: Schema.Types.ObjectId,
32 | ref: 'boards'
33 | },
34 | members: [{
35 | type: Schema.Types.ObjectId,
36 | ref: 'users'
37 | }],
38 | }, {
39 | timestamps: true
40 | });
41 |
42 | return mongooseClient.model('cards', cards);
43 | };
44 |
--------------------------------------------------------------------------------
/server/src/models/lists.model.js:
--------------------------------------------------------------------------------
1 | // lists-model.js - A mongoose model
2 | //
3 | // See http://mongoosejs.com/docs/models.html
4 | // for more of what you can do here.
5 | module.exports = function (app) {
6 | const mongooseClient = app.get('mongooseClient');
7 | const { Schema } = mongooseClient;
8 | const lists = new Schema({
9 | name: {
10 | type: String,
11 | required: true
12 | },
13 | order: {
14 | type: Number,
15 | required: true,
16 | default: 0,
17 | },
18 | archived: {
19 | type: Boolean,
20 | required: true,
21 | default: false,
22 | },
23 | boardId: {
24 | type: Schema.Types.ObjectId,
25 | ref: 'boards'
26 | },
27 | }, {
28 | timestamps: true
29 | });
30 |
31 | return mongooseClient.model('lists', lists);
32 | };
33 |
--------------------------------------------------------------------------------
/server/src/models/users.model.js:
--------------------------------------------------------------------------------
1 | // users-model.js - A mongoose model
2 | //
3 | // See http://mongoosejs.com/docs/models.html
4 | // for more of what you can do here.
5 | module.exports = function (app) {
6 | const mongooseClient = app.get('mongooseClient');
7 | const users = new mongooseClient.Schema({
8 | username: {
9 | type: String,
10 | unique: true
11 | },
12 | displayName: {
13 | type: String,
14 | required: true
15 | },
16 | imageUrl: {
17 | type: String,
18 | required: true
19 | },
20 | password: {
21 | type: String,
22 | required: true
23 | },
24 | }, {
25 | timestamps: true
26 | });
27 |
28 | return mongooseClient.model('users', users);
29 | };
30 |
--------------------------------------------------------------------------------
/server/src/mongoose.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | module.exports = function (app) {
4 | mongoose.connect(app.get('mongodb'), {});
5 | mongoose.Promise = global.Promise;
6 |
7 | app.set('mongooseClient', mongoose);
8 | };
9 |
--------------------------------------------------------------------------------
/server/src/services/activities/activities.hooks.js:
--------------------------------------------------------------------------------
1 | const { authenticate } = require('@feathersjs/authentication').hooks;
2 | const hooks = require('feathers-authentication-hooks');
3 | const { isBoardOwner } = require('../../hooks/authorization');
4 |
5 | module.exports = {
6 | before: {
7 | all: [ authenticate('jwt'), isBoardOwner ],
8 | find: [],
9 | get: [],
10 | create: [
11 | hooks.associateCurrentUser({
12 | as: 'userId'
13 | })
14 | ],
15 | update: [],
16 | patch: [],
17 | remove: []
18 | },
19 |
20 | after: {
21 | all: [],
22 | find: [],
23 | get: [],
24 | create: [],
25 | update: [],
26 | patch: [],
27 | remove: []
28 | },
29 |
30 | error: {
31 | all: [],
32 | find: [],
33 | get: [],
34 | create: [],
35 | update: [],
36 | patch: [],
37 | remove: []
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/server/src/services/activities/activities.service.js:
--------------------------------------------------------------------------------
1 | // Initializes the `activities` service on path `/activities`
2 | const createService = require('feathers-mongoose');
3 | const createModel = require('../../models/activities.model');
4 | const hooks = require('./activities.hooks');
5 |
6 | module.exports = function (app) {
7 | const Model = createModel(app);
8 | const paginate = app.get('paginate');
9 |
10 | const options = {
11 | name: 'activities',
12 | Model,
13 | paginate
14 | };
15 |
16 | // Initialize our service with any options it requires
17 | app.use('/activities', createService(options));
18 |
19 | // Get our initialized service so that we can register hooks and filters
20 | const service = app.service('activities');
21 |
22 | service.hooks(hooks);
23 | };
24 |
--------------------------------------------------------------------------------
/server/src/services/boards/boards.hooks.js:
--------------------------------------------------------------------------------
1 | const { authenticate } = require('@feathersjs/authentication').hooks;
2 | const hooks = require('feathers-authentication-hooks');
3 |
4 | module.exports = {
5 | before: {
6 | all: [ authenticate('jwt') ],
7 | find: [
8 | hooks.restrictToOwner({
9 | ownerField: 'ownerId'
10 | })
11 | ],
12 | get: [
13 | hooks.restrictToOwner({
14 | ownerField: 'ownerId'
15 | })
16 | ],
17 | create: [
18 | hooks.associateCurrentUser({
19 | as: 'ownerId'
20 | })
21 | ],
22 | update: [
23 | hooks.restrictToOwner({
24 | ownerField: 'ownerId'
25 | })
26 | ],
27 | patch: [
28 | hooks.restrictToOwner({
29 | ownerField: 'ownerId'
30 | })
31 | ],
32 | remove: [
33 | hooks.restrictToOwner({
34 | ownerField: 'ownerId'
35 | })
36 | ]
37 | },
38 |
39 | after: {
40 | all: [],
41 | find: [],
42 | get: [],
43 | create: [],
44 | update: [],
45 | patch: [],
46 | remove: []
47 | },
48 |
49 | error: {
50 | all: [],
51 | find: [],
52 | get: [],
53 | create: [],
54 | update: [],
55 | patch: [],
56 | remove: []
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/server/src/services/boards/boards.service.js:
--------------------------------------------------------------------------------
1 | // Initializes the `boards` service on path `/boards`
2 | const createService = require('feathers-mongoose');
3 | const createModel = require('../../models/boards.model');
4 | const hooks = require('./boards.hooks');
5 |
6 | module.exports = function (app) {
7 | const Model = createModel(app);
8 | const paginate = app.get('paginate');
9 |
10 | const options = {
11 | name: 'boards',
12 | Model,
13 | paginate
14 | };
15 |
16 | // Initialize our service with any options it requires
17 | app.use('/boards', createService(options));
18 |
19 | // Get our initialized service so that we can register hooks and filters
20 | const service = app.service('boards');
21 |
22 | service.hooks(hooks);
23 | };
24 |
--------------------------------------------------------------------------------
/server/src/services/cards/cards.hooks.js:
--------------------------------------------------------------------------------
1 | const { authenticate } = require('@feathersjs/authentication').hooks;
2 | const { isBoardOwner } = require('../../hooks/authorization');
3 |
4 | module.exports = {
5 | before: {
6 | all: [authenticate('jwt'), isBoardOwner],
7 | find: [],
8 | get: [],
9 | create: [],
10 | update: [],
11 | patch: [],
12 | remove: []
13 | },
14 |
15 | after: {
16 | all: [],
17 | find: [],
18 | get: [],
19 | create: [],
20 | update: [],
21 | patch: [],
22 | remove: []
23 | },
24 |
25 | error: {
26 | all: [],
27 | find: [],
28 | get: [],
29 | create: [],
30 | update: [],
31 | patch: [],
32 | remove: []
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/server/src/services/cards/cards.service.js:
--------------------------------------------------------------------------------
1 | // Initializes the `cards` service on path `/cards`
2 | const createService = require('feathers-mongoose');
3 | const createModel = require('../../models/cards.model');
4 | const hooks = require('./cards.hooks');
5 |
6 | module.exports = function (app) {
7 | const Model = createModel(app);
8 | const paginate = app.get('paginate');
9 |
10 | const options = {
11 | name: 'cards',
12 | Model,
13 | paginate
14 | };
15 |
16 | // Initialize our service with any options it requires
17 | app.use('/cards', createService(options));
18 |
19 | // Get our initialized service so that we can register hooks and filters
20 | const service = app.service('cards');
21 |
22 | service.hooks(hooks);
23 | };
24 |
--------------------------------------------------------------------------------
/server/src/services/index.js:
--------------------------------------------------------------------------------
1 | const users = require('./users/users.service.js');
2 | const boards = require('./boards/boards.service.js');
3 | const lists = require('./lists/lists.service.js');
4 | const cards = require('./cards/cards.service.js');
5 | const activities = require('./activities/activities.service.js');
6 | // eslint-disable-next-line no-unused-vars
7 | module.exports = function (app) {
8 | app.configure(users);
9 | app.configure(boards);
10 | app.configure(lists);
11 | app.configure(cards);
12 | app.configure(activities);
13 | };
14 |
--------------------------------------------------------------------------------
/server/src/services/lists/lists.hooks.js:
--------------------------------------------------------------------------------
1 | const { authenticate } = require('@feathersjs/authentication').hooks;
2 | const { isBoardOwner } = require('../../hooks/authorization');
3 |
4 | module.exports = {
5 | before: {
6 | all: [authenticate('jwt'), isBoardOwner],
7 | find: [],
8 | get: [],
9 | create: [],
10 | update: [],
11 | patch: [],
12 | remove: []
13 | },
14 |
15 | after: {
16 | all: [],
17 | find: [],
18 | get: [],
19 | create: [],
20 | update: [],
21 | patch: [],
22 | remove: []
23 | },
24 |
25 | error: {
26 | all: [],
27 | find: [],
28 | get: [],
29 | create: [],
30 | update: [],
31 | patch: [],
32 | remove: []
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/server/src/services/lists/lists.service.js:
--------------------------------------------------------------------------------
1 | // Initializes the `lists` service on path `/lists`
2 | const createService = require('feathers-mongoose');
3 | const createModel = require('../../models/lists.model');
4 | const hooks = require('./lists.hooks');
5 |
6 | module.exports = function (app) {
7 | const Model = createModel(app);
8 | const paginate = app.get('paginate');
9 |
10 | const options = {
11 | name: 'lists',
12 | Model,
13 | paginate
14 | };
15 |
16 | // Initialize our service with any options it requires
17 | app.use('/lists', createService(options));
18 |
19 | // Get our initialized service so that we can register hooks and filters
20 | const service = app.service('lists');
21 |
22 | service.hooks(hooks);
23 | };
24 |
--------------------------------------------------------------------------------
/server/src/services/users/users.hooks.js:
--------------------------------------------------------------------------------
1 | const { authenticate } = require('@feathersjs/authentication').hooks;
2 |
3 | const {
4 | hashPassword, protect
5 | } = require('@feathersjs/authentication-local').hooks;
6 |
7 | module.exports = {
8 | before: {
9 | all: [],
10 | find: [ authenticate('jwt') ],
11 | get: [ authenticate('jwt') ],
12 | create: [ hashPassword() ],
13 | update: [ hashPassword(), authenticate('jwt') ],
14 | patch: [ hashPassword(), authenticate('jwt') ],
15 | remove: [ authenticate('jwt') ]
16 | },
17 |
18 | after: {
19 | all: [
20 | // Make sure the password field is never sent to the client
21 | // Always must be the last hook
22 | protect('password')
23 | ],
24 | find: [],
25 | get: [],
26 | create: [],
27 | update: [],
28 | patch: [],
29 | remove: []
30 | },
31 |
32 | error: {
33 | all: [],
34 | find: [],
35 | get: [],
36 | create: [],
37 | update: [],
38 | patch: [],
39 | remove: []
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/server/src/services/users/users.service.js:
--------------------------------------------------------------------------------
1 | // Initializes the `users` service on path `/users`
2 | const createService = require('feathers-mongoose');
3 | const createModel = require('../../models/users.model');
4 | const hooks = require('./users.hooks');
5 |
6 | module.exports = function (app) {
7 | const Model = createModel(app);
8 | const paginate = app.get('paginate');
9 |
10 | const options = {
11 | name: 'users',
12 | Model,
13 | paginate
14 | };
15 |
16 | // Initialize our service with any options it requires
17 | app.use('/users', createService(options));
18 |
19 | // Get our initialized service so that we can register hooks and filters
20 | const service = app.service('users');
21 |
22 | service.hooks(hooks);
23 | };
24 |
--------------------------------------------------------------------------------
/server/test/app.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const rp = require('request-promise');
3 | const url = require('url');
4 | const app = require('../src/app');
5 |
6 | const port = app.get('port') || 3030;
7 | const getUrl = pathname => url.format({
8 | hostname: app.get('host') || 'localhost',
9 | protocol: 'http',
10 | port,
11 | pathname
12 | });
13 |
14 | describe('Feathers application tests', () => {
15 | before(function(done) {
16 | this.server = app.listen(port);
17 | this.server.once('listening', () => done());
18 | });
19 |
20 | after(function(done) {
21 | this.server.close(done);
22 | });
23 |
24 | it('starts and shows the index page', () => {
25 | return rp(getUrl()).then(body =>
26 | assert.ok(body.indexOf('') !== -1)
27 | );
28 | });
29 |
30 | describe('404', function() {
31 | it('shows a 404 HTML page', () => {
32 | return rp({
33 | url: getUrl('path/to/nowhere'),
34 | headers: {
35 | 'Accept': 'text/html'
36 | }
37 | }).catch(res => {
38 | assert.equal(res.statusCode, 404);
39 | assert.ok(res.error.indexOf('') !== -1);
40 | });
41 | });
42 |
43 | it('shows a 404 JSON error without stack trace', () => {
44 | return rp({
45 | url: getUrl('path/to/nowhere'),
46 | json: true
47 | }).catch(res => {
48 | assert.equal(res.statusCode, 404);
49 | assert.equal(res.error.code, 404);
50 | assert.equal(res.error.message, 'Page not found');
51 | assert.equal(res.error.name, 'NotFound');
52 | });
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/server/test/services/activities.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const app = require('../../src/app');
3 |
4 | describe('\'activities\' service', () => {
5 | it('registered the service', () => {
6 | const service = app.service('activities');
7 |
8 | assert.ok(service, 'Registered the service');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/server/test/services/boards.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const app = require('../../src/app');
3 |
4 | describe('\'boards\' service', () => {
5 | it('registered the service', () => {
6 | const service = app.service('boards');
7 |
8 | assert.ok(service, 'Registered the service');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/server/test/services/cards.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const app = require('../../src/app');
3 |
4 | describe('\'cards\' service', () => {
5 | it('registered the service', () => {
6 | const service = app.service('cards');
7 |
8 | assert.ok(service, 'Registered the service');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/server/test/services/lists.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const app = require('../../src/app');
3 |
4 | describe('\'lists\' service', () => {
5 | it('registered the service', () => {
6 | const service = app.service('lists');
7 |
8 | assert.ok(service, 'Registered the service');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/server/test/services/users.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const app = require('../../src/app');
3 |
4 | describe('\'users\' service', () => {
5 | it('registered the service', () => {
6 | const service = app.service('users');
7 |
8 | assert.ok(service, 'Registered the service');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/trello-clone-erd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/trello-clone/c5c998cfb51274f9da1b925041e7bef0624fd1bf/trello-clone-erd.png
--------------------------------------------------------------------------------