├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .woloxci ├── Dockerfile └── config.yml ├── CONTIBUTING.md ├── Jenkinsfile ├── LICENSE.md ├── README.md ├── assets └── FEA_open_source_sm.png ├── package-lock.json ├── package.json ├── pull_request_template.md ├── src ├── Mock │ ├── authMock.js │ ├── deleteMock.js │ ├── getMock.js │ ├── initializeMock.js │ ├── postMock.js │ └── putMock.js ├── Utils │ ├── collectionUtils.js │ └── queryUtils.js ├── constants.js ├── firestore-service.js └── test.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-object-rest-spread", 4 | [ 5 | "module-resolver", 6 | { 7 | "root": ["./src"] 8 | } 9 | ], 10 | [ 11 | "transform-runtime", 12 | { 13 | "polyfill": false, 14 | "regenerator": true 15 | } 16 | ] 17 | ], 18 | "presets": [ 19 | ["env", { 20 | "targets": { 21 | "browsers": ["last 2 versions", "safari >= 7"] 22 | } 23 | }] 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["wolox"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # production 8 | /dist 9 | 10 | # misc 11 | .gitignore 12 | .DS_Store 13 | .env 14 | .mock.js 15 | .test.js 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | package-lock.json 25 | -------------------------------------------------------------------------------- /.woloxci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.9.4 2 | 3 | WORKDIR /usr/src/app 4 | 5 | ENV NODE_ENV development 6 | 7 | ENV HOME /usr/src/app 8 | 9 | RUN mkdir -p /install 10 | 11 | ENV NODE_PATH=/install/node_modules 12 | 13 | # Install app dependencies 14 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 15 | # where available (npm@5+) 16 | COPY package*.json /install/ 17 | 18 | WORKDIR /install 19 | 20 | RUN npm install 21 | # If you are building your code for production 22 | # RUN npm install --only=production 23 | 24 | WORKDIR /usr/src/app 25 | 26 | # Bundle app source 27 | COPY . . 28 | -------------------------------------------------------------------------------- /.woloxci/config.yml: -------------------------------------------------------------------------------- 1 | config: 2 | dockerfile: .woloxci/Dockerfile 3 | project_name: firestore-service 4 | 5 | steps: 6 | copy_node_modules: 7 | - cp -r $NODE_PATH/ ./ 8 | lint: 9 | - cd /install 10 | - npm run precommit 11 | 12 | deploy: 13 | copy_node_modules: 14 | - cp -r $NODE_PATH/ ./ 15 | s3_push: 16 | - echo $AWS_ACCESS_KEY_ID 17 | - echo $AWS_SECRET_ACCESS_KEY 18 | - gulp s3 --env stage 19 | 20 | environment: 21 | GIT_COMMITTER_NAME: a 22 | GIT_COMMITTER_EMAIL: b 23 | LANG: C.UTF-8 24 | 25 | -------------------------------------------------------------------------------- /CONTIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First off, thank you for considering contributing to this project! Now, to start contributing: 4 | 5 | ## Issues and suggestions 6 | 7 | If you either find a bug or have any suggestion or opinion you want to discuss and share, please open an issue and add the proper label to it so we can get in contact with you. 8 | There are no wrong opinions! All feedback is welcome to make this the most suitable tool for you to use and for us to grow. 9 | 10 | ## How to contribute 11 | 12 | If you have a new feature you want to add or a bug you think you can fix, follow this steps: 13 | 14 | 1. Fork the repo 15 | 2. Create your feature branch (`git checkout -b my-new-feature`) 16 | 3. Commit your changes (`git commit -m 'Add some feature'`) 17 | 4. Push to the branch (`git push origin my-new-feature`) 18 | 5. Create new Pull Request with **clear title and description** 19 | 20 | ## Testing 21 | 22 | Your new feature **must** be tested with the proper tools. In this project, we use Jest. Once your tests are written, run: 23 | 24 | ```bash 25 | npm run test 26 | ``` 27 | All new tests needs to be pass in order to approve the PR. 28 | `TEST: INITIALIZE - Initialize Firestore Service` must not be marked as `skipped` for the tests to run properly. This means that if you want to run some test individually keep in mind that you will have to run `TEST: INITIALIZE - Initialize Firestore Service` too. 29 | 30 | ## Documentation 31 | 32 | If you are adding a new feature, you **must** add the documentation for it, showing how to use it. 33 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | @Library('wolox-ci') _ 2 | 3 | node { 4 | 5 | checkout scm 6 | 7 | woloxCi('.woloxci/config.yml'); 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Wolox 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firestore Service 2 | 3 | [![FEArmy](./assets/FEA_open_source_sm.png)](https://github.com/orgs/Wolox/teams/front-end-army/members) 4 | 5 | Create firestore queries as if you were using a REST api. 6 | 7 | ## Installation 8 | 9 | ``` 10 | npm install firestore-service 11 | ``` 12 | 13 | ## Prerequisites 14 | 15 | 1 - Create a firestore data base, you can do it [here](https://console.firebase.google.com/) 16 | 17 | 2 - Get your database credentials. You can find those in 18 | `Project Overview -> Add firebase to your web app` 19 | 20 | Note: We strongly recommend you to save the credentials on a `.env` file and don't upload them to any repository. 21 | 22 | ## Getting Started 23 | 24 | Once you have your credentials and the package install you can start using firestore service. 25 | 26 | You will need to initialize the service as soon as you can, the code should look something like this: 27 | 28 | ```js 29 | import firestoreService from 'firestore-service'; 30 | 31 | const firebaseConfig = { 32 | apiKey: xxxxxxxxxxxx, 33 | authDomain: xxxxxxxxxxxx, 34 | databaseURL: xxxxxxxxxxxx, 35 | projectId: xxxxxxxxxxxx, 36 | storageBucket: xxxxxxxxxxxx, 37 | messagingSenderId: xxxxxxxxxxxx 38 | }; 39 | 40 | firestoreService.initialize(firebaseConfig); 41 | ``` 42 | 43 | ## Response format 44 | 45 | Every HTTP method will return a response with the following body. 46 | 47 | ``` 48 | { 49 | ok: a boolean -> true: Success, false: Failure 50 | data: The data you requested, 51 | status: An HTTP status code, see the code table below, 52 | statusText: 'OK' or 'Failure', 53 | request: the actual request (GET, POST, etc.) 54 | } 55 | ``` 56 | 57 | ## Supported methods 58 | 59 | ### GET 60 | 61 | You can get all the elements from a collection or a specific element if the element's id is specified. 62 | 63 | Note: The path will always be `collection/id/collection2/id2/...`. The "url" will finish with a collection if you want an array of elements or with an id if you want a single one. 64 | 65 | E.g.: 66 | 67 | ```js 68 | const response = await firestoreService.get('regions'); 69 | ``` 70 | 71 | The response will have all the "regions" 72 | 73 | ```js 74 | const response = await firestoreService.get('regions/32'); 75 | ``` 76 | 77 | The response will have the information about the region with id 32. 78 | 79 | #### Extra query parameters 80 | 81 | Firestore service allows you to use certain tools to manipulate your data in the query. For this you should add extra parameters into the function get. 82 | 83 | - Limit 84 | 85 | The response will only have 20 "regions" 86 | 87 | ```js 88 | const response = await firestoreService.get('regions/32', { limit: 20 }); 89 | ``` 90 | 91 | - Filter 92 | 93 | The response will only have users who are older than 22 years old and younger than 35 94 | 95 | ```js 96 | const response = await firestoreService.get('regions/32', { 97 | filters: [ 98 | { field: 'age', condition: '<', value: 35 }, 99 | { field: 'age', condition: '>', value: 22 } 100 | ] 101 | }); 102 | ``` 103 | 104 | Supported condition operators: 105 | 106 | - `<` 107 | - `<=` 108 | - `>` 109 | - `>=` 110 | - `==` 111 | - `array-contains` 112 | 113 | Note: If you want to combine `==` with any of the others you will have to create an index in your db. More info about this [here](https://firebase.google.com/docs/firestore/query-data/indexing) 114 | 115 | - Order By 116 | 117 | Allows for ordering the query result by database field and `ascending/descending` direction. The default `orderDirection` is `ascending` 118 | 119 | The following query will get `regions` ordered by `age` `ascending` 120 | 121 | ```js 122 | const response = await firestoreService.get('regions', { orderBy: 'age' }); 123 | ``` 124 | 125 | The following query will get `regions` ordered by `age` `descending` 126 | 127 | ```js 128 | const response = await firestoreService.get('regions', { 129 | orderBy: 'age', 130 | descending: true 131 | }); 132 | ``` 133 | 134 | The following query will get `regions` ordered by `age` and `name` `ascending` 135 | 136 | ```js 137 | const response = await firestoreService.get('regions', { 138 | orderBy: ['age', 'name'] 139 | }); 140 | ``` 141 | 142 | Note: If you want to order your query by several fields you will have to create an index in your db,More info about this [here](https://firebase.google.com/docs/firestore/query-data/indexing) 143 | 144 | ### POST 145 | 146 | You can create an element by calling POST with the collection's path and the body with the element's data. 147 | 148 | E.g.: 149 | 150 | ```js 151 | const body = { 152 | firstName: 'New name' 153 | lastName: 'New last name' 154 | }; 155 | 156 | firestoreService.post('regions/32/users', body) 157 | ``` 158 | 159 | The previous request will create a new user under the region with id 32 using the information sent in the body. It will return the created user with its id. 160 | 161 | ### DELETE 162 | 163 | You can delete a certain element from a collection by using DELETE with a path to the element. 164 | 165 | E.g.: 166 | 167 | ```js 168 | firestoreService.delete('regions/32/users/1'); 169 | ``` 170 | 171 | The user from the region with id 32 that has id 1 will be deleted. The response will be empty 172 | 173 | ### PATCH 174 | 175 | You can update an element by calling PATCH and providing only the fields desired to be updated from an element. 176 | 177 | E.g.: 178 | 179 | ```js 180 | const body = { 181 | firstName: 'New name2' 182 | }; 183 | 184 | firestoreService.patch('regions/32/users/1', body); 185 | ``` 186 | 187 | The user with id 1 that belongs to the region with id 32 will have his name altered but will keep the previous values. The response will contain the updated user 188 | 189 | ### PUT 190 | 191 | You can update an element by calling PUT and providing all the fields in an element, providing less fields will make the rest of the values `null` (except for the id). Providing a different id in the body will result in an error. 192 | 193 | E.g.: 194 | 195 | ```js 196 | const body = { 197 | id: 1, 198 | firstName: 'New name3', 199 | lastName: 'New last name3' 200 | }; 201 | 202 | firestoreService.put('regions/32/users/1', body); 203 | ``` 204 | 205 | The user with id 1 that belongs to the region with id 32 will be altered.The response will contain the edited user. 206 | 207 | ```js 208 | const body = { 209 | firstName: 'New name3' 210 | }; 211 | 212 | firestoreService.put('regions/32/users/1', body); 213 | ``` 214 | 215 | The user with id 1 that belongs to the region with id 32 will be altered and the `lastName` field will be set to null. The response will contain the edited user. 216 | 217 | ```js 218 | const body = { 219 | id: 2, 220 | firstName: 'New name3', 221 | lastName: 'New last name3' 222 | }; 223 | 224 | firestoreService.patch('regions/32/users/1', body); 225 | ``` 226 | 227 | An error will be thrown because of id mismatch. 228 | 229 | 230 | ## Authentications 231 | 232 | Firestore service also supports methods to authenticate users. These are not http methods, are functions to be used with the same package. 233 | 234 | ### LOGIN 235 | 236 | You can login with an already created user (You can create them in the firebase [console](https://console.firebase.google.com/)) 237 | 238 | ```js 239 | const email = 'email@example.com' 240 | const password = 'xxxxxx' 241 | 242 | 243 | const response = await firestoreService.login(email, password); 244 | ``` 245 | 246 | The response will have all the user info. 247 | 248 | ### SIGN UP 249 | 250 | You can sign up a new user with the sign up method. 251 | 252 | ```js 253 | const email = 'email@example.com' 254 | const password = 'xxxxxx' 255 | 256 | 257 | const response = await firestoreService.signUp(email, password); 258 | ``` 259 | 260 | If the response is succesful, the user will be created and all it's information will be in the response. 261 | 262 | ### UPDATE PROFILE 263 | 264 | You can update a user's profile with the following method. 265 | 266 | ```js 267 | const body = { name: 'example name', phone: 'example phone'} 268 | 269 | 270 | const response = await firestoreService.updateProfile(body); 271 | ``` 272 | 273 | If the response is succesful, the user will be updated. 274 | 275 | Note: The user should be logged in to perform the update 276 | 277 | ## Supported status codes 278 | 279 | ``` 280 | OK: 200 281 | CREATED: 201 282 | NO_CONTENT: 204 283 | BAD_REQUEST: 400 284 | UNAUTHORIZED: 401 285 | FORBIDDEN: 403 286 | NOT_FOUND: 404 287 | CONFLICT: 409 288 | ``` 289 | 290 | ## License 291 | 292 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 293 | 294 | ## Contributing 295 | 296 | Learn how to contribute in the [CONTRIBUTING.md](CONTRIBUTING.md) file. 297 | 298 | ## About 299 | 300 | This project is maintained by [Lucas Zibell](https://github.com/LucasZibell) and it was written by [Wolox](http://www.wolox.com.ar). 301 | 302 | ![Wolox](https://raw.githubusercontent.com/Wolox/press-kit/master/logos/logo_banner.png) 303 | -------------------------------------------------------------------------------- /assets/FEA_open_source_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/firestore-service/3af0e0650d2db5026f6de21999c883c77eb0c3f1/assets/FEA_open_source_sm.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firestore-service", 3 | "version": "1.0.9", 4 | "description": "Create firestore queries with simple HTTP methods", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "test": "jest --forceExit --coverage --detectOpenHandles", 11 | "build": "webpack", 12 | "prepublishOnly": "npm run build", 13 | "lint": "find ./src -name \\**.js | xargs ./node_modules/eslint/bin/eslint.js", 14 | "precommit": "npm run lint" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/Wolox/firestore-service.git" 19 | }, 20 | "author": "Lucas Zibell ", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/Wolox/firestore-service/issues" 24 | }, 25 | "homepage": "https://github.com/Wolox/firestore-service#readme", 26 | "dependencies": { 27 | "eslint": "^5.9.0", 28 | "firebase": "^5.2.0" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.0.0-beta.55", 32 | "@babel/plugin-transform-spread": "^7.0.0-beta.55", 33 | "babel-cli": "^6.26.0", 34 | "babel-eslint": "^8.2.5", 35 | "babel-jest": "^23.2.0", 36 | "babel-loader": "^7.1.5", 37 | "babel-plugin-module-resolver": "^3.1.1", 38 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 39 | "babel-plugin-transform-runtime": "^6.23.0", 40 | "babel-preset-env": "^1.7.0", 41 | "clean-webpack-plugin": "^0.1.19", 42 | "dotenv": "^6.0.0", 43 | "eslint-config-wolox": "^2.2.2", 44 | "husky": "^1.2.0", 45 | "jest": "^23.2.0", 46 | "module-resolver": "^1.0.0", 47 | "transform-runtime": "0.0.0", 48 | "uglifyjs-webpack-plugin": "^1.2.7", 49 | "webpack": "^4.16.3", 50 | "webpack-cli": "^3.0.8" 51 | }, 52 | "env": { 53 | "jest/globals": true 54 | }, 55 | "jest": { 56 | "testURL": "http://localhost/", 57 | "moduleFileExtensions": [ 58 | "js" 59 | ], 60 | "moduleDirectories": [ 61 | "node_modules" 62 | ] 63 | }, 64 | "husky": { 65 | "hooks": { 66 | "pre-commit": "npm test && npm run lint" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | [Change!] Describe your feature, problems you had, notes, improvements and others. 3 | 4 | ## Screenshots 5 | [Change!] Show screenshots of the test running successfully. 6 | -------------------------------------------------------------------------------- /src/Mock/authMock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | login: { 3 | success: { 4 | email: 'test@test.com', 5 | password: '123123', 6 | response: { 7 | ok: true, 8 | request: 'LOGIN', 9 | status: 200, 10 | statusText: 'OK' 11 | } 12 | }, 13 | failure: { 14 | email: 'test@test.com', 15 | password: 'xxx', 16 | response: { 17 | ok: false, 18 | request: 'LOGIN', 19 | status: 401, 20 | statusText: 'Failure' 21 | } 22 | } 23 | }, 24 | signUp: { 25 | email: 'test@test.com', 26 | password: 'xxx', 27 | response: { 28 | ok: false, 29 | request: 'SIGN_UP', 30 | status: 403, 31 | statusText: 'Failure' 32 | } 33 | }, 34 | update: { 35 | body: { 36 | name: 'newName', 37 | surname: 'newSurname' 38 | }, 39 | response: { 40 | ok: true, 41 | request: 'UPDATE_PROFILE', 42 | status: 200, 43 | statusText: 'OK' 44 | }, 45 | failureResponse: { 46 | ok: false, 47 | request: 'UPDATE_PROFILE', 48 | status: 400, 49 | statusText: 'Failure' 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/Mock/deleteMock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | success: { 3 | path: 'tests/create/animals', 4 | response: { 5 | ok: true, 6 | request: 'DELETE', 7 | status: 200, 8 | statusText: 'OK' 9 | } 10 | }, 11 | failure: { 12 | path: 'tests/failure/delete/request/wrong', 13 | response: { 14 | ok: false, 15 | request: 'DELETE', 16 | status: 400, 17 | statusText: 'Failure' 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/Mock/getMock.js: -------------------------------------------------------------------------------- 1 | export const getMock = { 2 | success: { 3 | path: 'tests/get/users/J8NR45UzDffyMY0wBNoa', 4 | response: { 5 | data: { 6 | age: 33, 7 | firstName: 'Mike', 8 | id: 'J8NR45UzDffyMY0wBNoa', 9 | lastName: 'Poe' 10 | }, 11 | ok: true, 12 | request: 'GET', 13 | status: 200, 14 | statusText: 'OK' 15 | } 16 | }, 17 | wrongId: { 18 | path: 'tests/get/users/wrongId', 19 | response: { 20 | data: { 21 | id: 'wrongId' 22 | }, 23 | ok: true, 24 | request: 'GET', 25 | status: 200, 26 | statusText: 'OK' 27 | } 28 | }, 29 | wrongPath: { 30 | path: 'tests/get/wrongPath', 31 | response: { 32 | data: [], 33 | ok: true, 34 | request: 'GET', 35 | status: 200, 36 | statusText: 'OK' 37 | } 38 | } 39 | }; 40 | 41 | export const getCollectionMock = { 42 | getAll: { 43 | path: 'tests/get/users', 44 | response: { 45 | data: [ 46 | { 47 | age: 25, 48 | firstName: 'Kyle', 49 | id: '7QhqXVN0bQ3Zd6cKP1br', 50 | lastName: 'July' 51 | }, 52 | { 53 | age: 22, 54 | firstName: 'Matt', 55 | id: 'EFUsC6gMx052i39GFz8a', 56 | lastName: 'Myers' 57 | }, 58 | { 59 | age: 33, 60 | firstName: 'Mike', 61 | id: 'J8NR45UzDffyMY0wBNoa', 62 | lastName: 'Poe' 63 | }, 64 | { 65 | age: 20, 66 | firstName: 'May', 67 | id: 'q5LpatgtZwj2U2OalZKR', 68 | lastName: 'June' 69 | } 70 | ], 71 | ok: true, 72 | request: 'GET', 73 | status: 200, 74 | statusText: 'OK' 75 | } 76 | }, 77 | getOnlyTwo: { 78 | path: 'tests/get/users', 79 | body: { limit: 2 }, 80 | response: { 81 | data: [ 82 | { 83 | age: 25, 84 | firstName: 'Kyle', 85 | id: '7QhqXVN0bQ3Zd6cKP1br', 86 | lastName: 'July' 87 | }, 88 | { 89 | age: 22, 90 | firstName: 'Matt', 91 | id: 'EFUsC6gMx052i39GFz8a', 92 | lastName: 'Myers' 93 | } 94 | ], 95 | ok: true, 96 | request: 'GET', 97 | status: 200, 98 | statusText: 'OK' 99 | } 100 | }, 101 | getOnlyThree: { 102 | path: 'tests/get/users', 103 | body: { limit: 3 }, 104 | response: { 105 | data: [ 106 | { 107 | age: 25, 108 | firstName: 'Kyle', 109 | id: '7QhqXVN0bQ3Zd6cKP1br', 110 | lastName: 'July' 111 | }, 112 | { 113 | age: 22, 114 | firstName: 'Matt', 115 | id: 'EFUsC6gMx052i39GFz8a', 116 | lastName: 'Myers' 117 | }, 118 | { 119 | age: 33, 120 | firstName: 'Mike', 121 | id: 'J8NR45UzDffyMY0wBNoa', 122 | lastName: 'Poe' 123 | } 124 | ], 125 | ok: true, 126 | request: 'GET', 127 | status: 200, 128 | statusText: 'OK' 129 | } 130 | }, 131 | wrongLimit: { 132 | path: 'tests/get/users', 133 | body: { limit: -1 }, 134 | response: { 135 | ok: false, 136 | request: 'GET', 137 | status: 400, 138 | statusText: 'Failure' 139 | } 140 | }, 141 | getWithFilters: { 142 | path: 'tests/get/users', 143 | body: { filters: [{ field: 'age', condition: '<', value: 32 }] }, 144 | response: { 145 | data: [ 146 | { 147 | age: 20, 148 | firstName: 'May', 149 | id: 'q5LpatgtZwj2U2OalZKR', 150 | lastName: 'June' 151 | }, 152 | { 153 | age: 22, 154 | firstName: 'Matt', 155 | id: 'EFUsC6gMx052i39GFz8a', 156 | lastName: 'Myers' 157 | }, 158 | { 159 | age: 25, 160 | firstName: 'Kyle', 161 | id: '7QhqXVN0bQ3Zd6cKP1br', 162 | lastName: 'July' 163 | } 164 | ], 165 | ok: true, 166 | request: 'GET', 167 | status: 200, 168 | statusText: 'OK' 169 | } 170 | }, 171 | getWithMultipleFilters: { 172 | path: 'tests/get/users', 173 | body: { 174 | filters: [ 175 | { field: 'age', condition: '<', value: 32 }, 176 | { field: 'age', condition: '>', value: 21 } 177 | ] 178 | }, 179 | response: { 180 | data: [ 181 | { 182 | age: 22, 183 | firstName: 'Matt', 184 | id: 'EFUsC6gMx052i39GFz8a', 185 | lastName: 'Myers' 186 | }, 187 | { 188 | age: 25, 189 | firstName: 'Kyle', 190 | id: '7QhqXVN0bQ3Zd6cKP1br', 191 | lastName: 'July' 192 | } 193 | ], 194 | ok: true, 195 | request: 'GET', 196 | status: 200, 197 | statusText: 'OK' 198 | } 199 | }, 200 | wrongFilter: { 201 | path: 'tests/get/users', 202 | body: { filters: [{ field: null, condition: undefined, value: 32 }] }, 203 | response: { 204 | ok: false, 205 | request: 'GET', 206 | status: 400, 207 | statusText: 'Failure' 208 | } 209 | }, 210 | getWithOrderByAgeDescending: { 211 | path: 'tests/get/users', 212 | body: { orderBy: 'age', descending: true }, 213 | response: { 214 | data: [ 215 | { 216 | age: 33, 217 | firstName: 'Mike', 218 | id: 'J8NR45UzDffyMY0wBNoa', 219 | lastName: 'Poe' 220 | }, 221 | { 222 | age: 25, 223 | firstName: 'Kyle', 224 | id: '7QhqXVN0bQ3Zd6cKP1br', 225 | lastName: 'July' 226 | }, 227 | { 228 | age: 22, 229 | firstName: 'Matt', 230 | id: 'EFUsC6gMx052i39GFz8a', 231 | lastName: 'Myers' 232 | }, 233 | { 234 | age: 20, 235 | firstName: 'May', 236 | id: 'q5LpatgtZwj2U2OalZKR', 237 | lastName: 'June' 238 | } 239 | ], 240 | ok: true, 241 | request: 'GET', 242 | status: 200, 243 | statusText: 'OK' 244 | } 245 | }, 246 | getWithOrderByAgeAscending: { 247 | path: 'tests/get/users', 248 | body: { orderBy: 'age' }, 249 | response: { 250 | data: [ 251 | { 252 | age: 20, 253 | firstName: 'May', 254 | id: 'q5LpatgtZwj2U2OalZKR', 255 | lastName: 'June' 256 | }, 257 | { 258 | age: 22, 259 | firstName: 'Matt', 260 | id: 'EFUsC6gMx052i39GFz8a', 261 | lastName: 'Myers' 262 | }, 263 | { 264 | age: 25, 265 | firstName: 'Kyle', 266 | id: '7QhqXVN0bQ3Zd6cKP1br', 267 | lastName: 'July' 268 | }, 269 | { 270 | age: 33, 271 | firstName: 'Mike', 272 | id: 'J8NR45UzDffyMY0wBNoa', 273 | lastName: 'Poe' 274 | } 275 | ], 276 | ok: true, 277 | request: 'GET', 278 | status: 200, 279 | statusText: 'OK' 280 | } 281 | } 282 | }; 283 | -------------------------------------------------------------------------------- /src/Mock/initializeMock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | success: { 3 | ok: true, 4 | request: 'INITIALIZE', 5 | status: 200, 6 | statusText: 'OK' 7 | }, 8 | failure: { 9 | ok: false, 10 | request: 'INITIALIZE', 11 | status: 400, 12 | statusText: 'Failure' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/Mock/postMock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | success: { 3 | path: 'tests/create/animals', 4 | body: { name: 'Bob the test Bear', specimen: 'Bear' }, 5 | response: { 6 | ok: true, 7 | request: 'POST', 8 | status: 201, 9 | statusText: 'OK' 10 | } 11 | }, 12 | failure: { 13 | path: 'tests/create/animals/home/thisIsWrong', 14 | body: undefined, 15 | response: { 16 | ok: false, 17 | request: 'POST', 18 | status: 400, 19 | statusText: 'Failure' 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/Mock/putMock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | success: { 3 | path: 'tests/create/animals', 4 | body: { specimen: 'unkown', name: 'None' }, 5 | newBody: { specimen: 'Shark', name: 'Little Shark' }, 6 | response: { 7 | ok: true, 8 | request: 'GET', 9 | status: 200, 10 | statusText: 'OK' 11 | } 12 | }, 13 | failure: { 14 | path: 'tests/create/animals', 15 | body: { specimen: 'unkown', name: 'None' }, 16 | newBody: undefined, 17 | response: { 18 | ok: false, 19 | request: 'PUT', 20 | status: 400, 21 | statusText: 'Failure' 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/Utils/collectionUtils.js: -------------------------------------------------------------------------------- 1 | export function getIdFromPath(path) { 2 | const pathArr = path.split('/'); 3 | const EVEN_LENGTH_DIVISOR = 2; 4 | return pathArr.length % EVEN_LENGTH_DIVISOR === 0 && pathArr.pop(); 5 | } 6 | 7 | export function getCollectionPath(pathname) { 8 | return pathname.substring(0, pathname.lastIndexOf('/')); 9 | } 10 | 11 | export function getPathAndElementId(pathname) { 12 | const id = getIdFromPath(pathname); 13 | const path = id ? getCollectionPath(pathname) : pathname; 14 | return { id, path }; 15 | } 16 | -------------------------------------------------------------------------------- /src/Utils/queryUtils.js: -------------------------------------------------------------------------------- 1 | const applyFilters = (query, filters) => 2 | filters.reduce( 3 | (accumulator, filter) => 4 | accumulator.where(filter.field, filter.condition, filter.value), 5 | query 6 | ); 7 | 8 | const pipeOrderBy = (query, orderDirection, orderBy) => 9 | orderBy.reduce( 10 | (accumulator, field) => accumulator.orderBy(field, orderDirection), 11 | query 12 | ); 13 | 14 | export function queryForID(data, id) { 15 | return data 16 | .doc(id) 17 | .get() 18 | .then(item => ({ id: item.id, ...item.data() })); 19 | } 20 | 21 | export function queryForCollection(data, body) { 22 | let query = data; 23 | const { limit, filters, orderBy, descending } = body; 24 | if (filters) { 25 | query = applyFilters(query, filters); 26 | } 27 | if (limit) { 28 | query = query.limit(Number(limit)); 29 | } 30 | if (orderBy) { 31 | const orderByArr = Array.isArray(orderBy) ? orderBy : [orderBy]; 32 | const orderDirection = descending ? 'desc' : 'asc'; 33 | query = pipeOrderBy(query, orderDirection, orderByArr); 34 | } 35 | return query 36 | .get() 37 | .then(snapshot => 38 | snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id })) 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const STATUS = { 2 | OK: 'OK', 3 | FAILURE: 'Failure' 4 | }; 5 | 6 | export const SUCCESS_CODES = { 7 | OK: 200, 8 | CREATED: 201, 9 | NO_CONTENT: 204 10 | }; 11 | 12 | export const CLIENT_ERROR_CODES = { 13 | BAD_REQUEST: 400, 14 | UNAUTHORIZED: 401, 15 | FORBIDDEN: 403, 16 | NOT_FOUND: 404, 17 | CONFLICT: 409 18 | }; 19 | 20 | export const REQUEST = { 21 | INITIALIZE: 'INITIALIZE', 22 | CREATE: 'CREATE', 23 | GET: 'GET', 24 | POST: 'POST', 25 | DELETE: 'DELETE', 26 | PUT: 'PUT', 27 | LOGIN: 'LOGIN', 28 | SIGN_UP: 'SIGN_UP', 29 | UPDATE_PROFILE: 'UPDATE_PROFILE' 30 | }; 31 | -------------------------------------------------------------------------------- /src/firestore-service.js: -------------------------------------------------------------------------------- 1 | import url from 'url'; 2 | 3 | import firebase from 'firebase'; 4 | 5 | import { getPathAndElementId } from './Utils/collectionUtils'; 6 | import { queryForID, queryForCollection } from './Utils/queryUtils'; 7 | import { STATUS, SUCCESS_CODES, CLIENT_ERROR_CODES, REQUEST } from './constants'; 8 | 9 | let firestore = null; 10 | let userAdmin = null; 11 | 12 | // eslint-disable-next-line max-params 13 | const generateResponse = (ok, data, status, statusText, request) => ({ 14 | ok, 15 | data, 16 | status, 17 | statusText, 18 | request 19 | }); 20 | 21 | function initializeFirestore(keys) { 22 | try { 23 | firestore = firebase.initializeApp(keys).firestore(); 24 | userAdmin = firebase.initializeApp(keys, 'userAdmin'); 25 | firestore.settings({ timestampsInSnapshots: true }); 26 | return generateResponse(true, firestore, SUCCESS_CODES.OK, STATUS.OK, REQUEST.INITIALIZE); 27 | } catch (error) { 28 | return generateResponse(false, error, CLIENT_ERROR_CODES.BAD_REQUEST, STATUS.FAILURE, REQUEST.INITIALIZE); 29 | } 30 | } 31 | 32 | async function getData({ pathname }, body = {}) { 33 | try { 34 | const { id, path } = getPathAndElementId(pathname); 35 | let data = firestore.collection(path); 36 | data = await (id ? queryForID(data, id) : queryForCollection(data, body)); 37 | return generateResponse(true, data, SUCCESS_CODES.OK, STATUS.OK, REQUEST.GET); 38 | } catch (error) { 39 | return generateResponse(false, error, CLIENT_ERROR_CODES.BAD_REQUEST, STATUS.FAILURE, REQUEST.GET); 40 | } 41 | } 42 | 43 | async function createDoc({ pathname }, body) { 44 | try { 45 | const { path } = getPathAndElementId(pathname); 46 | const data = await firestore 47 | .collection(path) 48 | .add(body) 49 | .then(ref => ref.id); 50 | return generateResponse(true, data, SUCCESS_CODES.CREATED, STATUS.OK, REQUEST.POST); 51 | } catch (error) { 52 | return generateResponse(false, error, CLIENT_ERROR_CODES.BAD_REQUEST, STATUS.FAILURE, REQUEST.POST); 53 | } 54 | } 55 | 56 | async function modifyDoc({ pathname }, body) { 57 | try { 58 | const { id, path } = getPathAndElementId(pathname); 59 | const data = await firestore 60 | .collection(path) 61 | .doc(id) 62 | .set(body); 63 | return generateResponse(true, data, SUCCESS_CODES.OK, STATUS.OK, REQUEST.PUT); 64 | } catch (error) { 65 | return generateResponse(false, error, CLIENT_ERROR_CODES.BAD_REQUEST, STATUS.FAILURE, REQUEST.PUT); 66 | } 67 | } 68 | 69 | async function deleteDoc({ pathname }) { 70 | try { 71 | const { id, path } = getPathAndElementId(pathname); 72 | const data = await firestore 73 | .collection(path) 74 | .doc(id) 75 | .delete(); 76 | return generateResponse(true, data, SUCCESS_CODES.OK, STATUS.OK, REQUEST.DELETE); 77 | } catch (error) { 78 | return generateResponse(false, error, CLIENT_ERROR_CODES.BAD_REQUEST, STATUS.FAILURE, REQUEST.DELETE); 79 | } 80 | } 81 | 82 | async function login(email, password) { 83 | try { 84 | await firebase.auth().signInWithEmailAndPassword(email, password); 85 | const data = await firebase.auth().currentUser; 86 | return generateResponse(true, data, SUCCESS_CODES.OK, STATUS.OK, REQUEST.LOGIN); 87 | } catch (error) { 88 | return generateResponse(false, error, CLIENT_ERROR_CODES.UNAUTHORIZED, STATUS.FAILURE, REQUEST.LOGIN); 89 | } 90 | } 91 | 92 | async function signUp(email, password) { 93 | try { 94 | await userAdmin.auth().createUserWithEmailAndPassword(email, password); 95 | const data = await userAdmin.auth().currentUser; 96 | return generateResponse(true, data, SUCCESS_CODES.OK, STATUS.OK, REQUEST.SIGN_UP); 97 | } catch (error) { 98 | return generateResponse(false, error, CLIENT_ERROR_CODES.FORBIDDEN, STATUS.FAILURE, REQUEST.SIGN_UP); 99 | } 100 | } 101 | 102 | async function updateProfile(body) { 103 | try { 104 | const user = firebase.auth().currentUser; 105 | await user.updateProfile(body); 106 | return generateResponse(true, null, SUCCESS_CODES.OK, STATUS.OK, REQUEST.UPDATE_PROFILE); 107 | } catch (error) { 108 | return generateResponse(false, error, CLIENT_ERROR_CODES.BAD_REQUEST, STATUS.FAILURE, REQUEST.UPDATE_PROFILE); 109 | } 110 | } 111 | 112 | const firestoreService = { 113 | initialize: keys => initializeFirestore(keys), 114 | get: (path, body) => getData(url.parse(path, true), body), 115 | put: (path, body) => modifyDoc(url.parse(path, true), body), 116 | delete: path => deleteDoc(url.parse(path, true)), 117 | post: (path, body) => createDoc(url.parse(path, true), body), 118 | patch: (path, body) => modifyDoc(url.parse(path, true), body), 119 | login: (email, password) => login(email, password), 120 | signUp: (email, password) => signUp(email, password), 121 | updateProfile: body => updateProfile(body) 122 | }; 123 | 124 | export default firestoreService; 125 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import initializeMock from './Mock/initializeMock'; 3 | import { getMock, getCollectionMock } from './Mock/getMock'; 4 | import postMock from './Mock/postMock'; 5 | import deleteMock from './Mock/deleteMock'; 6 | import putMock from './Mock/putMock'; 7 | import authMock from './Mock/authMock'; 8 | import firestoreService from './firestore-service'; 9 | 10 | require('dotenv').config(); 11 | 12 | const firebaseConfig = { 13 | apiKey: process.env.REACT_APP_FIREBASE_API_KEY, 14 | authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, 15 | databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL, 16 | projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, 17 | storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, 18 | messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID 19 | }; 20 | 21 | test('TEST: INITIALIZE - Initialize Firestore Service', async () => { 22 | const response = await firestoreService.initialize(firebaseConfig); 23 | expect(response).toEqual(expect.objectContaining(initializeMock.success)); 24 | }); 25 | 26 | test('TEST: FAILURE INITIALIZE - Recieve a response with an error when the params are wrong', async () => { 27 | const response = await firestoreService.initialize(); 28 | expect(response).toEqual(expect.objectContaining(initializeMock.failure)); 29 | }); 30 | 31 | test('TEST: GET - Get a specific id from a collection', async () => { 32 | const response = await firestoreService.get(getMock.success.path); 33 | expect(response).toEqual(getMock.success.response); 34 | }); 35 | 36 | test('TEST: GET - Get an entire collection from a specific path', async () => { 37 | const response = await firestoreService.get(getCollectionMock.getAll.path); 38 | expect(response).toEqual(getCollectionMock.getAll.response); 39 | }); 40 | 41 | test('TEST: GET WITH LIMIT 2 - Get only 2 elements from a collection', async () => { 42 | const response = await firestoreService.get(getCollectionMock.getOnlyTwo.path, getCollectionMock.getOnlyTwo.body); 43 | expect(response).toEqual(getCollectionMock.getOnlyTwo.response); 44 | }); 45 | 46 | test('TEST: GET WITH LIMIT 3 - Get only 3 elements from a collection', async () => { 47 | const response = await firestoreService.get(getCollectionMock.getOnlyThree.path, getCollectionMock.getOnlyThree.body); 48 | expect(response).toEqual(getCollectionMock.getOnlyThree.response); 49 | }); 50 | 51 | test('TEST: GET WITH ORDER BY DESCENDING - Get elements ordered by age, descending', async () => { 52 | const response = await firestoreService.get(getCollectionMock.getWithOrderByAgeDescending.path, getCollectionMock.getWithOrderByAgeDescending.body); 53 | expect(response).toEqual(getCollectionMock.getWithOrderByAgeDescending.response); 54 | }); 55 | 56 | test('TEST: GET WITH ORDER BY DEFAULT ORDER DIRECTION - Get elements ordered by age, ascending by default', async () => { 57 | const response = await firestoreService.get(getCollectionMock.getWithOrderByAgeAscending.path, getCollectionMock.getWithOrderByAgeAscending.body); 58 | expect(response).toEqual(getCollectionMock.getWithOrderByAgeAscending.response); 59 | }); 60 | 61 | test('TEST: FAILURE GET WITH LIMIT - Recieve a response with an error when the params are wrong', async () => { 62 | const response = await firestoreService.get(getCollectionMock.wrongLimit.path, getCollectionMock.wrongLimit.body); 63 | expect(response).toEqual(expect.objectContaining(getCollectionMock.wrongLimit.response)); 64 | }); 65 | 66 | test('TEST: GET WITH FILTER - Get only users who are less than 32 years old', async () => { 67 | const response = await firestoreService.get(getCollectionMock.getWithFilters.path, getCollectionMock.getWithFilters.body); 68 | expect(response).toEqual(getCollectionMock.getWithFilters.response); 69 | }); 70 | 71 | test('TEST: GET WITH MULTIPLE FILTERS - Get only users who are less than 32 years old and their name is May ', async () => { 72 | const response = await firestoreService.get(getCollectionMock.getWithMultipleFilters.path, getCollectionMock.getWithMultipleFilters.body); 73 | expect(response).toEqual(getCollectionMock.getWithMultipleFilters.response); 74 | }); 75 | 76 | test('TEST: FAILURE WRONG FILTERS - Recieve a response with an error when the params are wrong ', async () => { 77 | const response = await firestoreService.get(getCollectionMock.wrongFilter.path, getCollectionMock.wrongFilter.body); 78 | expect(response).toEqual(expect.objectContaining(getCollectionMock.wrongFilter.response)); 79 | }); 80 | 81 | test('TEST: EMPTY GET BY ID - Recieve an empty response when element with id does not exist', async () => { 82 | const response = await firestoreService.get(getMock.wrongId.path); 83 | expect(response).toEqual(expect.objectContaining(getMock.wrongId.response)); 84 | }); 85 | 86 | test('TEST: WRONG PATH - Recieve an empty response when path does not exist', async () => { 87 | const response = await firestoreService.get(getMock.wrongPath.path); 88 | expect(response).toEqual(expect.objectContaining(getMock.wrongPath.response)); 89 | }); 90 | 91 | test('TEST: CREATE - Create a new document in a specific collection', async () => { 92 | const response = await firestoreService.post(postMock.success.path, postMock.success.body); 93 | const newUser = await firestoreService.get(`${postMock.success.path}/${response.data}`); 94 | expect(response).toEqual({ 95 | data: newUser.data.id, 96 | ...postMock.success.response 97 | }); 98 | }); 99 | 100 | test('TEST: FAILURE CREATE - Recieve a response with an error when the params are wrong', async () => { 101 | const response = await firestoreService.post(postMock.failure.path, postMock.failure.body); 102 | expect(response).toEqual(expect.objectContaining(postMock.failure.response)); 103 | }); 104 | 105 | test('TEST: DELETE - Deletes a specific document in a collection', async () => { 106 | const response = await firestoreService.post(deleteMock.success.path); 107 | const deleteResponse = await firestoreService.delete(`${deleteMock.success.path}/${response.data}`); 108 | expect(deleteResponse).toEqual(deleteMock.success.response); 109 | }); 110 | 111 | test('TEST: FAILURE DELETE - Recieve a response with an error when the params are wrong', async () => { 112 | const response = await firestoreService.delete(deleteMock.failure.path); 113 | expect(response).toEqual(expect.objectContaining(deleteMock.failure.response)); 114 | }); 115 | 116 | test('TEST: PUT - Modifies a specific document in a collection', async () => { 117 | const response = await firestoreService.post(putMock.success.path, putMock.success.body); 118 | await firestoreService.put(`${putMock.success.path}/${response.data}`, putMock.success.newBody); 119 | const modified = await firestoreService.get(`${putMock.success.path}/${response.data}`); 120 | expect(modified).toEqual({ 121 | data: { ...putMock.success.newBody, id: response.data }, 122 | ...putMock.success.response 123 | }); 124 | }); 125 | 126 | test('TEST: PATCH - Modifies a specific docuemnt in a collection', async () => { 127 | const response = await firestoreService.post(putMock.success.path, putMock.success.body); 128 | await firestoreService.patch(`${putMock.success.path}/${response.data}`, putMock.success.newBody); 129 | const modified = await firestoreService.get(`${putMock.success.path}/${response.data}`); 130 | expect(modified).toEqual({ 131 | data: { ...putMock.success.newBody, id: response.data }, 132 | ...putMock.success.response 133 | }); 134 | }); 135 | 136 | test('TEST: FAILURE POST/PATCH - Recieve a response with an error when the params are wrong', async () => { 137 | const response = await firestoreService.post(putMock.failure.path, putMock.failure.body); 138 | const modifyResponse = await firestoreService.put( 139 | `${putMock.failure.path}/${response.data}`, 140 | putMock.failure.newBody 141 | ); 142 | expect(modifyResponse).toEqual(expect.objectContaining(putMock.failure.response)); 143 | }); 144 | 145 | test('TEST: SIGNUP FAILURE - Recieve a response with an error when the credentials are wrong', async () => { 146 | const response = await firestoreService.signUp(authMock.signUp.email, authMock.signUp.password); 147 | expect(response).toEqual(expect.objectContaining(authMock.signUp.response)); 148 | }); 149 | 150 | test('TEST: LOGIN - Recieve an user info when the login is accomplished', async () => { 151 | const response = await firestoreService.login(authMock.login.success.email, authMock.login.success.password); 152 | expect(response).toEqual(expect.objectContaining(authMock.login.success.response)); 153 | }); 154 | 155 | test('TEST: LOGIN FAILURE - Recieve a response with an error when the credentials are wrong', async () => { 156 | const response = await firestoreService.login(authMock.login.failure.email, authMock.login.failure.password); 157 | expect(response).toEqual(expect.objectContaining(authMock.login.failure.response)); 158 | }); 159 | 160 | test('TEST: UPDATE PROFILE - Recieve a success when the information is correct', async () => { 161 | await firestoreService.login(authMock.login.success.email, authMock.login.success.password); 162 | const response = await firestoreService.updateProfile(authMock.update.body); 163 | expect(response).toEqual(expect.objectContaining(authMock.update.response)); 164 | }); 165 | 166 | test('TEST: UPDATE PROFILE FAILURE - Recieve a response with an error when the body is undefined', async () => { 167 | await firestoreService.login(authMock.login.success.email, authMock.login.success.password); 168 | const response = await firestoreService.updateProfile(); 169 | expect(response).toEqual(expect.objectContaining(authMock.update.failureResponse)); 170 | }); 171 | 172 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const webpack = require('webpack'); 4 | const path = require('path'); 5 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 7 | 8 | module.exports = { 9 | entry: './src/firestore-service.js', 10 | output: { 11 | path: path.join(__dirname, '/dist'), 12 | filename: 'index.js', 13 | library: 'firestore-service', 14 | libraryTarget: 'umd' 15 | }, 16 | mode: 'production', 17 | resolve: { 18 | extensions: ['.js'], 19 | modules: ['node_modules'] 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.js$/, 25 | exclude: /node_modules/, 26 | loader: 'babel-loader' 27 | } 28 | ] 29 | }, 30 | plugins: [ 31 | new CleanWebpackPlugin() 32 | ], 33 | optimization: { 34 | minimizer: [ 35 | new UglifyJsPlugin({ 36 | cache: true, 37 | parallel: true 38 | }) 39 | ] 40 | } 41 | }; 42 | --------------------------------------------------------------------------------