├── .gitignore
├── .vscode
└── settings.json
├── LICENSE.txt
├── README.md
├── inventorydemo
├── README.md
├── build
│ ├── bundle.js
│ ├── bundle.js.LICENSE.txt
│ ├── images
│ │ ├── 50.jpeg
│ │ ├── Free.jpeg
│ │ ├── Portrait.jpeg
│ │ ├── beautiful life.jpeg
│ │ ├── body & arms.jpeg
│ │ ├── hold me in your arms.jpeg
│ │ ├── keep it turned on.jpeg
│ │ ├── logo.png
│ │ ├── my red book.jpeg
│ │ ├── transparent.png
│ │ └── whenever you need.jpeg
│ └── index.html
├── client
│ ├── App.jsx
│ ├── components
│ │ ├── Display.jsx
│ │ ├── InventoryList.jsx
│ │ └── Item.jsx
│ ├── index.css
│ ├── index.js
│ └── services
│ │ ├── events.js
│ │ └── inventory.js
└── public
│ ├── images
│ ├── 50.jpeg
│ ├── Free.jpeg
│ ├── Portrait.jpeg
│ ├── beautiful life.jpeg
│ ├── body & arms.jpeg
│ ├── hold me in your arms.jpeg
│ ├── keep it turned on.jpeg
│ ├── logo.png
│ ├── my red book.jpeg
│ ├── transparent.png
│ └── whenever you need.jpeg
│ └── index.html
├── libraries
├── clientlib
│ ├── README.md
│ ├── customHook.js
│ └── package.json
└── serverlib
│ ├── README.md
│ ├── database
│ └── databaseConnect.js
│ ├── events
│ └── eventHelperFuncs.js
│ ├── package.json
│ └── setupWebsocket.js
├── package-lock.json
├── package.json
├── server
├── controllers
│ └── inventoryController.js
├── keys
│ ├── server.crt
│ └── server.key
├── models
│ └── inventoryModel.js
├── routes
│ └── inventory.js
├── server.js
└── utils
│ └── utils.js
├── storedemo
├── README.md
├── build
│ ├── bundle.js
│ ├── bundle.js.LICENSE.txt
│ ├── images
│ │ ├── 50.jpeg
│ │ ├── Free.jpeg
│ │ ├── Portrait.jpeg
│ │ ├── beautiful life.jpeg
│ │ ├── body & arms.jpeg
│ │ ├── hold me in your arms.jpeg
│ │ ├── keep it turned on.jpeg
│ │ ├── logo.png
│ │ ├── my red book.jpeg
│ │ ├── transparent.png
│ │ └── whenever you need.jpeg
│ └── index.html
├── client
│ ├── App.jsx
│ ├── components
│ │ ├── Cart.jsx
│ │ ├── Display.jsx
│ │ ├── InventoryList.jsx
│ │ └── Item.jsx
│ ├── index.css
│ ├── index.js
│ └── services
│ │ ├── events.js
│ │ ├── inventory.js
│ │ └── ytapi.js
└── public
│ ├── images
│ ├── 50.jpeg
│ ├── Free.jpeg
│ ├── Portrait.jpeg
│ ├── beautiful life.jpeg
│ ├── body & arms.jpeg
│ ├── hold me in your arms.jpeg
│ ├── keep it turned on.jpeg
│ ├── logo.png
│ ├── my red book.jpeg
│ ├── transparent.png
│ └── whenever you need.jpeg
│ └── index.html
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5501
3 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2022 LiveStateDB
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
4 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
5 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
11 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 | [![Contributors][contributors-shield]][contributors-url]
15 | [![Forks][forks-shield]][forks-url]
16 | [![Stargazers][stars-shield]][stars-url]
17 | [![Issues][issues-shield]][issues-url]
18 | [![MIT License][license-shield]][license-url]
19 | [![LinkedIn][linkedin-shield]][linkedin-url]
20 |
21 |
22 |
23 |
38 |
39 | ## About
40 |
41 |
42 |
43 | LiveStateDB is a database subscription API that enables developers to make state reflect database changes in real time. Developers will have access to a custom hook, ```useSubscribe()```, to implement in your front-end codebase, and a back-end library which allows the server to interface with your database. Currently, LiveStateDB only supports MongoDB.
44 |
45 |
46 | ### Built With
47 |
48 | [![MongoDB][MongoDB]][MongoDB-url]
49 | [![Redis][Redis]][Redis-url]
50 | [![Socket.io][Socket.io]][Socket.io-url]
51 |
52 |
53 |
54 | # Getting Started
55 |
56 | LiveStateDB features a client side library and a server side library that can be installed via npm or yarn with the following commands. The libraries need to be installed on both sides in order to make use of LiveStateDB's real time updates.
57 |
58 | ```js
59 | npm install @livestatedb/client
60 | yarn install @livestatedb/client
61 |
62 | npm install @livestatedb/server
63 | yarn install @livestatedb/server
64 | ```
65 |
66 | ## Using the hook useSubscribe( )
67 |
68 |
69 |
70 | First import the module into the file you wish to use it.
71 |
72 | ```js
73 | import { useSubscribe } from '@livestatedb/client';
74 | ```
75 |
76 | When useSubscribe is called, it will open a web socket through which database updates get passed when changes occur that state is 'subscribed to'.
77 |
78 | useSubscribe takes one argument: an object with three key-value pairs that correspond with the database name, collection name, and query to a database. Here's an example.
79 |
80 | ```js
81 | const options = {
82 | database: 'DBname',
83 | collection: 'someCollectionName',
84 | query: {}
85 | };
86 | ```
87 |
88 | useSubscribe returns the way to reference state in your front-end code, and a function that ends that piece of state's subscription to the database.
89 |
90 | ```js
91 | const [ state, endSubscription ] = useSubscribe(options);
92 | ```
93 |
94 | _**NOTE!**_
95 | useSubscribe will only receive updates from the database that match the options passed-in _at the time it was called_. If a database change occurs that would match the query parameters, it will not automatically be included in what useSubscribe is subscribed to. You would need to re-render the page to call useSubscribe again to capture this.
96 |
97 | Additionally, your query filter parameters should be constant. For example, if you are building a dashboard for managing inventory, your filter parameters could include condition: (new, like new, fair, poor), but should not include parameters such as quantity: (>50, <=50). In this example, if you wanted to be subscribed to data that had a certain quantity, you would need write your options object, and specifically your query parameters, to include all of the data you're interested in, regardless of quantity. You would then be able to filter out only the data you're interested in in your front-end codebase.
98 |
99 |
100 |
101 |
102 | ## Using the back-end library
103 |
104 |
105 |
106 | useSubscribe won't work until you pass information about your MongoDB and Redis into a function that opens a web socket and connects everything. Require the module in wherever you instantiate an http server, for example server.js. Feel free to give it a label or to to simply invoke the required-in function with the necessary arguments.
107 |
108 | ```js
109 | const liveStateDB = require('@livestatedb/server');
110 | liveStateDB(httpServer, databaseInfo);
111 | ```
112 | OR
113 | ```js
114 | require('@livestatedb/server')(httpServer, databaseInfo);
115 | ```
116 |
117 | The second paramater, databaseInfo, must be an object with two key-value pairs - these are for passing in information about your MongoDB database and Redis, respectively. The values are both objects that contain connection information for each database. See example below.
118 |
119 | ```js
120 | const databaseInfo =
121 | {
122 | mongoDbOptions:
123 | {
124 | uri: "mongodb+srv://name:12345678910abcde@cluster0.aabbcc.mongodb.net/?retryWrites=true&w=majority"
125 | },
126 | redisDbOptions:
127 | {
128 | host: 'redis-00000.c00.us-east-0-0.ec2.cloud.redislabs.com',
129 | port: 15711,
130 | password: 'lkajsdf092j3jlsdfmop3jfspdkgpoi',
131 | family: 4
132 | },
133 | }
134 | ```
135 |
136 | That's it! Your front-end will now be able to display or work with the subscribed database data as it updates in real time - regardless of where the change originates from.
137 |
138 | Thanks for your interest in LiveStateDB, and we hope this improves your development experience.
139 |
140 | (back to top )
141 |
142 |
143 |
144 |
145 | # Contributing
146 |
147 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
148 |
149 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
150 | Don't forget to give the project a star! Thanks again!
151 |
152 | ## Starting Notes
153 | - Main branch is only for production
154 | - Dev branch is for development. Two person review process for pull requests to the dev and main branch.
155 |
156 | ## Starting off
157 | 1. Clone main repository to local machine
158 | 2. git checkout -b [name/feature] -> Create feature branch off of dev
159 | 3. Commit to your local feature branch often!
160 |
161 | ## Pushing changes to the dev repo
162 | 1. 'Git checkout dev' (locally switch to dev branch)
163 | 2. 'Git pull origin dev' (Pull updates of dev down to your local system)
164 | 3. 'Git checkout [your branch] (switch back to your branch locally)
165 | 4. 'Git merge dev' (Brings dev into your local branch)
166 | 5. Resolve conflicts or :q if there aren't any
167 | 6. 'Git push origin ' (Push merged branch up to github)
168 | 7. Create a pull request in github from => dev
169 | 8. Repeat as needed
170 | 9. When ready to publish main, do step 7 but from dev => main
171 |
172 |
173 |
181 |
182 | ## License
183 |
184 | Distributed under the MIT License. See `LICENSE.txt` for more information.
185 |
186 | # Contact
187 |
188 | Kevin Richardson LinkedIn | GitHub
189 | Stephanie Page LinkedIn | GitHub
190 | David Cheng LinkedIn | GitHub
191 | Evan Deam LinkedIn | GitHub
192 |
193 | (back to top )
194 |
195 |
196 |
210 |
211 |
212 |
213 | [contributors-shield]: https://img.shields.io/github/contributors/oslabs-beta/LiveStateDB?style=for-the-badge
214 | [contributors-url]: https://github.com/oslabs-beta/LiveStateDB/graphs/contributors
215 |
216 | [forks-shield]: https://img.shields.io/github/forks/oslabs-beta/LiveStateDB?style=for-the-badge
217 | [forks-url]: https://github.com/oslabs-beta/LiveStateDB/network/members
218 |
219 | [stars-shield]: https://img.shields.io/github/stars/oslabs-beta/LiveStateDB?style=for-the-badge
220 | [stars-url]: https://github.com/oslabs-beta/LiveStateDB/stargazers
221 |
222 | [issues-shield]: https://img.shields.io/github/issues/oslabs-beta/LiveStateDB?style=for-the-badge
223 | [issues-url]: https://github.com/oslabs-beta/LiveStateDB/issues
224 |
225 | [license-shield]: https://img.shields.io/github/license/oslabs-beta/LiveStateDB?style=for-the-badge
226 | [license-url]: https://github.com/oslabs-beta/LiveStateDB/blob/master/LICENSE.txt
227 |
228 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
229 | [linkedin-url]: https://www.linkedin.com/company/livestatedb/about/?viewAsMember=true
230 |
231 | [product-screenshot]: images/screeenshot2.png
232 |
233 |
234 | [Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
235 | [Next-url]: https://nextjs.org/
236 | [Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D
237 | [Vue-url]: https://vuejs.org/
238 | [Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white
239 | [Angular-url]: https://angular.io/
240 | [Svelte.dev]: https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte&logoColor=FF3E00
241 | [Svelte-url]: https://svelte.dev/
242 | [Laravel.com]: https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white
243 | [Laravel-url]: https://laravel.com
244 | [Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white
245 | [Bootstrap-url]: https://getbootstrap.com
246 | [JQuery.com]: https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white
247 | [JQuery-url]: https://jquery.com
248 |
249 | [React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB
250 | [React-url]: https://reactjs.org/
251 | [ReactRouter]: https://img.shields.io/badge/React_Router-CA4245?style=for-the-badge&logo=react-router&logoColor=white
252 | [MongoDB]: https://img.shields.io/badge/MongoDB-4EA94B?style=for-the-badge&logo=mongodb&logoColor=white
253 | [MongoDB-url]: https://www.mongodb.com/
254 | [ElephantSQL]: https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white
255 | [ElephantSQL-url]: https://www.elephantsql.com/
256 | [Webpack]: https://img.shields.io/badge/webpack-%238DD6F9.svg?style=for-the-badge&logo=webpack&logoColor=black
257 | [Webpack-url]: https://webpack.js.org/plugins/html-webpack-plugin/
258 | [Redis]: https://img.shields.io/badge/-Redis-DC382D?style=for-the-badge&logo=redis&logoColor=white
259 | [Redis-url]: https://redis.io/
260 | [Socket.io]: https://img.shields.io/badge/-Socket.io-010101?style=for-the-badge&logo=Socket.io&logoColor=white
261 | [Socket.io-url]: https://socket.io/
262 |
--------------------------------------------------------------------------------
/inventorydemo/README.md:
--------------------------------------------------------------------------------
1 | # demo
2 |
3 | A template repository for a MongoDB - Express - React (w/ Redux) - Node project.
4 |
5 |
--------------------------------------------------------------------------------
/inventorydemo/build/bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
2 |
3 | /**
4 | * @license React
5 | * react-dom.production.min.js
6 | *
7 | * Copyright (c) Facebook, Inc. and its affiliates.
8 | *
9 | * This source code is licensed under the MIT license found in the
10 | * LICENSE file in the root directory of this source tree.
11 | */
12 |
13 | /**
14 | * @license React
15 | * react.production.min.js
16 | *
17 | * Copyright (c) Facebook, Inc. and its affiliates.
18 | *
19 | * This source code is licensed under the MIT license found in the
20 | * LICENSE file in the root directory of this source tree.
21 | */
22 |
23 | /**
24 | * @license React
25 | * scheduler.production.min.js
26 | *
27 | * Copyright (c) Facebook, Inc. and its affiliates.
28 | *
29 | * This source code is licensed under the MIT license found in the
30 | * LICENSE file in the root directory of this source tree.
31 | */
32 |
33 | //! Can pass props to Display {database, collection, query}, then just pass those variables as args into useSubscribe(database, collection, query)
34 |
--------------------------------------------------------------------------------
/inventorydemo/build/images/50.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/50.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/Free.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/Free.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/Portrait.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/Portrait.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/beautiful life.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/beautiful life.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/body & arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/body & arms.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/hold me in your arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/hold me in your arms.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/keep it turned on.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/keep it turned on.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/logo.png
--------------------------------------------------------------------------------
/inventorydemo/build/images/my red book.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/my red book.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/images/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/transparent.png
--------------------------------------------------------------------------------
/inventorydemo/build/images/whenever you need.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/build/images/whenever you need.jpeg
--------------------------------------------------------------------------------
/inventorydemo/build/index.html:
--------------------------------------------------------------------------------
1 | inventory Demo
--------------------------------------------------------------------------------
/inventorydemo/client/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Display from './components/Display.jsx'
3 |
4 | const App = () => {
5 |
6 | return (
7 |
8 | );
9 | };
10 |
11 | export default App;
12 |
--------------------------------------------------------------------------------
/inventorydemo/client/components/Display.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo } from "react"
2 | import InventoryList from './InventoryList.jsx'
3 | import { changeSingleInventoryField } from '../services/inventory'
4 | import { useSubscribe } from "../../../libraries/clientlib/customHook";
5 |
6 | //! Can pass props to Display {database, collection, query}, then just pass those variables as args into useSubscribe(database, collection, query)
7 | const Display = () => {
8 |
9 | const [ inventoryHookOptions, setInventoryHookOptions] = useState({
10 | database: 'inventoryDemo',
11 | collection: 'inventoryitems',
12 | query: {},
13 | })
14 |
15 | console.log('we are in Display React Component')
16 | const [ inventoryList, endSubscription ] = useSubscribe({
17 | database: 'inventoryDemo',
18 | collection: 'inventoryitems',
19 | query: {},
20 | });
21 |
22 | //increment/decrement click function
23 | const handleIncDecClick = (id, field, value) => {
24 | changeSingleInventoryField(id, field, value)
25 | }
26 |
27 | return (
28 |
29 |
30 |
31 |
32 | Rick Rollin' Records
33 |
34 |
35 |
36 |
37 |
Inventory Manager
38 |
42 |
43 |
44 | );
45 | }
46 |
47 | export default Display;
--------------------------------------------------------------------------------
/inventorydemo/client/components/InventoryList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react"
2 | import Item from './Item.jsx'
3 |
4 |
5 | const InventoryList = ({ inventoryList, handleIncDecClick }) => {
6 |
7 |
8 | const inventory = [];
9 | let i = 0;
10 |
11 | inventory.push(
12 |
21 | )
22 |
23 | //for every item in the database we add an item component for rendering
24 | for(let items in inventoryList){
25 | inventory.push(
26 |
36 | )
37 | i++;
38 | }
39 |
40 | return (
41 |
42 | { inventory }
43 |
44 | );
45 | }
46 |
47 | export default InventoryList;
--------------------------------------------------------------------------------
/inventorydemo/client/components/Item.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 |
3 | const Item = (
4 | { item,
5 | description,
6 | quantity,
7 | price,
8 | handleIncDecClick,
9 | id,
10 | className
11 | }) => {
12 |
13 | return(
14 |
15 |
16 | {item}
17 |
18 |
19 | {description}
20 |
21 |
22 | {quantity}
23 |
24 | {handleIncDecClick &&
25 | handleIncDecClick(id, 'quantity', 1)}>
26 | +
27 |
28 | }
29 | {handleIncDecClick &&
30 | handleIncDecClick(id, 'quantity', -1)}>
31 | -
32 |
33 | }
34 |
35 |
36 |
37 | {price}
38 |
39 |
40 | )
41 | }
42 |
43 | export default Item;
--------------------------------------------------------------------------------
/inventorydemo/client/index.css:
--------------------------------------------------------------------------------
1 | /* ul > li {
2 | display: inline-block;
3 | }
4 |
5 | li {
6 | padding-left: 20px;
7 | } */
8 |
9 | * {
10 | background-color: #f1f5f8;
11 | color: #0D0D0D;
12 | padding: 0px;
13 | margin: 0px;
14 | font-family: sans-serif;
15 | }
16 |
17 | h1 {
18 | font-size: 8vw;
19 | text-align: center;
20 | }
21 |
22 | h2 {
23 | font-size: 5vw;
24 | text-align: center;
25 | margin-bottom: 5px;
26 | }
27 |
28 | .rickPicRight {
29 | width: 5%;
30 | margin-left: 5px;
31 | }
32 |
33 | .rickPicLeft {
34 | width: 5%;
35 | margin-right: 5px;
36 | transform: scaleX(-1);
37 | }
38 |
39 | .header {
40 | margin-top: 10px;
41 | display: flex;
42 | }
43 |
44 | .display {
45 | border: solid 2px #383838;
46 | margin: auto;
47 | width: 90%;
48 | height: 90%;
49 | padding: 5px;
50 | }
51 |
52 | .row {
53 | display: flex;
54 | justify-content: space-evenly;
55 | margin-bottom: -1px;
56 | }
57 |
58 | .values{
59 | color: #232323;
60 | border: solid 1px #808080;
61 | border-radius: 0px;
62 | width: 100%;
63 | text-align: center;
64 | margin-left: -1px;
65 | }
66 |
67 | .label {
68 | display: flex;
69 | justify-content: space-evenly;
70 | margin-bottom: 5px;
71 | font-weight: bold;
72 | }
73 |
74 | .button{
75 | /* width: 35px; */
76 | width: 40%;
77 | height: 17px;
78 | background-color: #f1f5f8;
79 | margin-left: 2px;
80 |
81 | }
82 |
83 | .buttonContainer {
84 | display: none;
85 | position: absolute;
86 | width: 5%;
87 | margin-left: 8px;
88 | }
89 |
90 | #quantity:hover > .buttonContainer {
91 | display: inline-block;
92 | }
93 |
94 | #quantity {
95 | display: inline-block;
96 | }
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/inventorydemo/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import App from './App.jsx';
4 | import './index.css';
5 |
6 | const container = document.getElementById('root');
7 | const root = createRoot(container);
8 |
9 | root.render (
10 |
11 | )
--------------------------------------------------------------------------------
/inventorydemo/client/services/events.js:
--------------------------------------------------------------------------------
1 | import { changeSingleInventoryField, getSingleInventory } from './inventory'
2 |
3 | //helper functions for events
4 |
5 | const handleIncremementClickHelper = async (id, field, value) => {
6 | //wait for result of calling function to increment DB
7 | await changeSingleInventoryField(id, field, value)
8 | //then call the fucntion that gets inventory of single item from DB (update)
9 | // return getSingleInventory(id)
10 | }
11 |
12 | const handleDecrementClickHelper = async (id, field, value) => {
13 | //wait for result of calling function to decrement DB
14 | return changeSingleInventoryField(id, field, value)
15 | //then call the fucntion that gets inventory of single item from DB (update)
16 | // return getSingleInventory(id)
17 | }
18 |
19 | export {
20 | handleIncremementClickHelper,
21 | handleDecrementClickHelper,
22 | }
23 |
--------------------------------------------------------------------------------
/inventorydemo/client/services/inventory.js:
--------------------------------------------------------------------------------
1 | //helper functions for all requests to inventory router
2 | module.exports = inventoryFunctions = {
3 |
4 | getAllInventory: () => {
5 | return fetch('/inventory/all')
6 | .then(data => data.json())
7 | },
8 |
9 | getSingleInventory: (id) => {
10 | return fetch('/inventory/getOne/' + id)
11 | .then(data => data.json())
12 | },
13 |
14 | createInventory: (item, quantity, description, price) => {
15 | return fetch('/inventory/create', {
16 | method: 'POST',
17 | headers: { 'Content-Type': 'application/json' },
18 | body: JSON.stringify({
19 | item: item,
20 | quantity: quantity,
21 | description: description,
22 | price: price,
23 | })
24 | });
25 | },
26 |
27 | changeSingleInventoryField: (id, field, value) => {
28 | return fetch('/inventory/changeSingleField', {
29 | method: 'PATCH',
30 | headers: { 'Content-Type': 'application/json' },
31 | body: JSON.stringify({
32 | id: id,
33 | field: field,
34 | value: value,
35 | })
36 | })
37 | }
38 | }
--------------------------------------------------------------------------------
/inventorydemo/public/images/50.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/50.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/Free.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/Free.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/Portrait.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/Portrait.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/beautiful life.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/beautiful life.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/body & arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/body & arms.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/hold me in your arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/hold me in your arms.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/keep it turned on.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/keep it turned on.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/logo.png
--------------------------------------------------------------------------------
/inventorydemo/public/images/my red book.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/my red book.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/images/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/transparent.png
--------------------------------------------------------------------------------
/inventorydemo/public/images/whenever you need.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/inventorydemo/public/images/whenever you need.jpeg
--------------------------------------------------------------------------------
/inventorydemo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | inventory Demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/libraries/clientlib/README.md:
--------------------------------------------------------------------------------
1 | # Using the hook useSubscribe( )
2 |
3 | First import the module into the file you wish to use it.
4 |
5 | ```js
6 | import { useSubscribe } from '@livestatedb/client';
7 | ```
8 |
9 | When useSubscribe is called, it will open a web socket through which database updates get passed when changes occur that state is 'subscribed to'.
10 |
11 | useSubscribe takes one argument: an object with three key-value pairs that correspond with the database name, collection name, and query to a database. Here's an example.
12 |
13 | ```js
14 | const options = {
15 | database: 'DBname',
16 | collection: 'someCollectionName',
17 | query: {}
18 | };
19 | ```
20 |
21 | useSubscribe returns the way to reference state in your front-end code, and a function that ends that piece of state's subscription to the database.
22 |
23 | ```js
24 | const [ state, endSubscription ] = useSubscribe(options);
25 | ```
26 |
27 | _**NOTE!**_
28 | useSubscribe will only receive updates from the database that match the options passed-in _at the time it was called_. If a database change occurs that would match the query parameters, it will not automatically be included in what useSubscribe is subscribed to. You would need to re-render the page to call useSubscribe again to capture this.
29 |
30 | Additionally, your query filter parameters should be constant. For example, if you are building a dashboard for managing inventory, your filter parameters could include condition: (new, like new, fair, poor), but should not include parameters such as quantity: (>50, <=50). In this example, if you wanted to be subscribed to data that had a certain quantity, you would need write your options object, and specifically your query parameters, to include all of the data you're interested in, regardless of quantity. You would then be able to filter out only the data you're interested in in your front-end codebase.
--------------------------------------------------------------------------------
/libraries/clientlib/customHook.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo, useRef } from 'react';
2 | import uuid from 'react-uuid';
3 | import { io } from 'socket.io-client'
4 |
5 | const Subscribe = () => {
6 | const socket = io("/", {
7 | path: '/websocket',
8 | transports: ["websocket"]
9 | })
10 | const setup = (params) => socket.emit('setup', params);
11 |
12 | const useSubscribe = ({ database, collection, query }) => {
13 | const [ state, setstate ] = useState({});
14 | const subscriptionId = useMemo(() => uuid(), [])
15 | const stringifiedQuery = JSON.stringify(query)
16 | const currSocket = useRef(null);
17 | const didMount = useRef(false);
18 |
19 | useEffect(() => {
20 | const params = {
21 | database: database,
22 | collection: collection,
23 | query: stringifiedQuery,
24 | subscriptionId: subscriptionId
25 | }
26 |
27 | setup(params);
28 | // socket.emit('setup', params)
29 | currSocket.current = socket;
30 |
31 | socket.on(subscriptionId, (e) => {
32 | const {type, data} = JSON.parse(e);
33 | const id = data.documentKey?._id;
34 | switch (type) {
35 | case 'get':
36 | {
37 | const obj = {};
38 | for(let i = 0; i < data.length; i++){
39 | obj[data[i]._id] = data[i];
40 | // delete obj[data[i]._id]._id;
41 | }
42 | setstate(obj);
43 | break;
44 | }
45 | case 'insert' :
46 | {
47 | const newInsertion = {};
48 | newInsertion[id] = data.fullDocument;
49 | setstate((previousstate) =>
50 | {
51 | const updatedstate = JSON.parse(JSON.stringify(previousstate));
52 | Object.assign(updatedstate, newInsertion);
53 | return updatedstate;
54 | });
55 | break;
56 | }
57 | case 'update' :
58 | {
59 | const update = data.updateDescription.updatedFields;
60 | setstate((previousstate) =>
61 | {
62 | const updatedstate = JSON.parse(JSON.stringify(previousstate));
63 | Object.assign(updatedstate[id], update);
64 | return updatedstate;
65 | });
66 | break;
67 | }
68 | case 'delete':
69 | {
70 | setstate((previousstate) =>
71 | {
72 | const updatedstate = JSON.parse(JSON.stringify(previousstate));
73 | delete updatedstate[id];
74 | return updatedstate;
75 | });
76 | break;
77 | }
78 | }
79 | })
80 |
81 | return () => {
82 | // Unsubscribe from event stream
83 | endSubscription();
84 | }
85 |
86 |
87 | }, []);
88 |
89 | useEffect(() => {
90 | if (didMount.current) {
91 | currSocket.current.emit('depChange', {
92 | database: database,
93 | collection: collection,
94 | query: stringifiedQuery,
95 | subscriptionId: subscriptionId
96 | })
97 | //event emitter for letting the server know something has changed for subscription
98 | }else didMount.current = true;
99 | }, [database, collection, stringifiedQuery])
100 |
101 | //write function that stops subscriptions
102 | const endSubscription = async () => {
103 | await currSocket.current.close();
104 | }
105 |
106 | return [ state, endSubscription ];
107 | }
108 | return useSubscribe;
109 | }
110 |
111 | export const useSubscribe = Subscribe();
112 |
--------------------------------------------------------------------------------
/libraries/clientlib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@livestatedb/client",
3 | "version": "1.0.0",
4 | "description": "React custom hook for client connection to livestate API",
5 | "main": "customHook.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@livestatedb/client": "^1.0.0",
13 | "react": "^18.2.0",
14 | "react-uuid": "^1.0.3",
15 | "socket.io-client": "^4.5.3"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/libraries/serverlib/README.md:
--------------------------------------------------------------------------------
1 | # Using the back-end library
2 |
3 | useSubscribe won't work until you pass information about your MongoDB and Redis into a function that opens a web socket and connects everything. Require the module in wherever you instantiate an http server, for example server.js. Feel free to give it a label or to to simply invoke the required-in function with the necessary arguments.
4 |
5 | ```js
6 | const liveStateDB = require('@livestatedb/server');
7 | liveStateDB(httpServer, databaseInfo);
8 | ```
9 | OR
10 | ```js
11 | require('@livestatedb/server')(httpServer, databaseInfo);
12 | ```
13 |
14 | The second paramater, databaseInfo, must be an object with two key-value pairs - these are for passing in information about your MongoDB database and Redis, respectively. The values are both objects that contain connection information for each database. See example below.
15 |
16 | ```js
17 | const databaseInfo =
18 | {
19 | mongoDbOptions:
20 | {
21 | uri: "mongodb+srv://name:12345678910abcde@cluster0.aabbcc.mongodb.net/?retryWrites=true&w=majority"
22 | },
23 | redisDbOptions:
24 | {
25 | host: 'redis-00000.c00.us-east-0-0.ec2.cloud.redislabs.com',
26 | port: 15711,
27 | password: 'lkajsdf092j3jlsdfmop3jfspdkgpoi',
28 | family: 4
29 | },
30 | }
31 | ```
--------------------------------------------------------------------------------
/libraries/serverlib/database/databaseConnect.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const fs = require('fs')
3 | const path = require('path')
4 | const Redis = require('ioredis')
5 | const { MongoClient } = require('mongodb');
6 |
7 | module.exports = async (changeStreamOptions) => {
8 | const { mongoDbOptions, redisDbOptions } = changeStreamOptions;
9 |
10 | try {
11 | const client = new MongoClient(mongoDbOptions.uri);
12 | await client.connect();
13 | const redis = new Redis(redisDbOptions);
14 |
15 |
16 | return {redis, client}
17 | } catch (err) {
18 |
19 | if (err) {
20 | console.log(`Error occured when connecting to MongoDB or Redis. errName: ${err.name}, errMessage: ${err.message}, errStack: ${err.stack}`)
21 |
22 | } else {
23 | let dbError = new Error('An unknown error occured when connecting to MongoDB or Redis')
24 | console.log(dbError)
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/libraries/serverlib/events/eventHelperFuncs.js:
--------------------------------------------------------------------------------
1 | // * REDIS LEGEND - the strings below are added to the begining of the doc to create unique ID's
2 | // * SD = Subscription based on Document
3 | // * SC = Subscription based on Subcription ID's
4 | // * SQD = Subscription based on Query
5 | // * SQS = Subscription Query String
6 |
7 | //! File originally written without const declaration, but didn't error -> WHY?
8 | const eventRouteHelperFuncs = {}
9 |
10 | //find documents that are querried
11 | eventRouteHelperFuncs.initialDbQuery = async (dbCollection, query, redis, subscriptionId, io, websocketObj) => {
12 | try {
13 | const data = await dbCollection.find(JSON.parse(query)).toArray()
14 | //iterate through the array of objects from the db
15 | for(let objs of data){
16 | //check if the object's id has an entry in the subscription db
17 | //'SD'+
18 | const redisClientIsSubscribedToDocument = await redis.sismember('SD' + objs._id, subscriptionId);
19 | if(redisClientIsSubscribedToDocument === 0){
20 | await redis.sadd('SD' + objs._id, [subscriptionId]);
21 | }
22 | //check if the document is in the clients subscriptions
23 | const redisDocumentIsSubscribedToClient = await redis.sismember('SC' + subscriptionId, objs._id);
24 | if(redisDocumentIsSubscribedToClient === 0){
25 | await redis.sadd('SC' + subscriptionId, [objs._id]);
26 | }
27 | }
28 | io.to(websocketObj[subscriptionId]).emit(subscriptionId, JSON.stringify({type: 'get', data: data}))
29 | } catch (err) {
30 | if (err) {
31 | console.log(`Error occured while subscribing or unsubscribing to the DB query. errName: ${err.name}, errMessage: ${err.message}, errStack: ${err.stack}`)
32 | } else {
33 | let initialDbQueryError = new Error('An unknown error occured while subscribing or unsubscribing to the DB query')
34 | console.log(initialDbQueryError)
35 | }
36 | }
37 | }
38 |
39 | //write helper function here for checking our redis databases for subscribed clients to send response to
40 | const sendReplyToSubscribers = async (setOfSubscriptionIds, redis, changeStreamObj, websocketObj, io) => {
41 | for(const subscriptionId of setOfSubscriptionIds) {
42 | try {
43 | // if we fail to write to a client we want to mark the client for removal
44 | io.to(websocketObj[subscriptionId]).emit(subscriptionId, JSON.stringify({type: changeStreamObj.operationType, data: changeStreamObj}))
45 | } catch (err) {
46 | if (err) {
47 | console.log(`Error occured in sending reply to subscribers. errName: ${err.name}, errMessage: ${err.message}, errStack: ${err.stack}`)
48 | } else {
49 | let subscriberReplyError = new Error('An unknown error occured in sending reply to subscribers')
50 | console.log(subscriberReplyError)
51 | }
52 | }
53 | }
54 | }
55 |
56 | eventRouteHelperFuncs.monitorListingsUsingEventEmitter = async (client, redis, websocketObj, io, timeInMs = 2147483647, pipeline = []) => {
57 | const changeStream = client.watch(pipeline);
58 | //listen for changes
59 | changeStream.on('change', async (changeStreamObj) => {
60 | //check if it's an insertion event
61 | if(changeStreamObj.operationType === 'insert'){
62 | try {
63 | const { db, coll } = changeStreamObj.ns;
64 | // store set of Subscription Ids for collection
65 | const redisSubscriptionIdsSubscribedToCollection = await redis.smembers('DB' + db + 'COL' + coll);
66 | // iterate through subscription IDs
67 | for(let subscriptionId of redisSubscriptionIdsSubscribedToCollection){
68 | // for each subscription ID check to see if it is in the set stored at subscribed Doc
69 | const redisClientIsSubscribedToDocument = await redis.sismember('SD' + changeStreamObj.documentKey._id.toString(), subscriptionId);
70 | // if not subscribed
71 | if(redisClientIsSubscribedToDocument === 0){
72 | // add subscription ID to doc ID key
73 | await redis.sadd('SD' + changeStreamObj.documentKey._id.toString(), [subscriptionId]);
74 | }
75 | // store set of Documents subscribed to Client
76 | const redisDocumentIsSubscribedToClient = await redis.sismember('SC' + subscriptionId, changeStreamObj.documentKey._id.toString());
77 | // if not subscribed
78 | if(redisDocumentIsSubscribedToClient === 0){
79 | // add docID to subscription ID key
80 | await redis.sadd('SC' + subscriptionId, [changeStreamObj.documentKey._id.toString()]);
81 | }
82 | }
83 | sendReplyToSubscribers(redisSubscriptionIdsSubscribedToCollection, redis, changeStreamObj, websocketObj, io);
84 | } catch (err) {
85 | if (err) {
86 | console.log(`Unable to save new subscriptions in Redis. errName: ${err.name}, errMessage: ${err.message}, errStack: ${err.stack}`)
87 |
88 | } else {
89 | let initialDbQueryError = new Error('An unknown error occured while attempting to save new subscriptions in Redis')
90 | console.log(initialDbQueryError)
91 | }
92 | }
93 | }
94 | try {
95 | // store all subscribers for a particular document
96 | const redisSubscriptionIdsSubscribedToDocument = await redis.smembers('SD' + changeStreamObj.documentKey._id.toString());
97 | sendReplyToSubscribers(redisSubscriptionIdsSubscribedToDocument, redis, changeStreamObj, websocketObj, io);
98 | } catch (err) {
99 | if (err) {
100 | console.log(`Unable to access subscriptions in Redis. errName: ${err.name}, errMessage: ${err.message}, errStack: ${err.stack}`)
101 |
102 | } else {
103 | let initialDbQueryError = new Error('An unknown error occured while attempting to access subscriptions in Redis')
104 | console.log(initialDbQueryError)
105 | }
106 | }
107 | });
108 | await closeChangeStream(timeInMs, changeStream);
109 | }
110 |
111 | eventRouteHelperFuncs.unsubscribe = async(redis, websocketId, websocketObj, subscriptionIdObj, changeStreams) => {
112 | for(const subscriptionId of subscriptionIdObj[websocketId]){
113 | const docs = await redis.smembers('SC' + subscriptionId)
114 | for(const doc of docs){
115 | redis.srem('SD' + doc, subscriptionId)
116 | }
117 | const changeStreams = await redis.smembers('DBCOL' + websocketId)
118 | for(const changeStream of changeStreams){
119 | await redis.srem(changeStream, websocketId)
120 | }
121 | redis.del('DBCOL' + websocketId);
122 | redis.del('SC' + subscriptionId);
123 | }
124 | }
125 |
126 | eventRouteHelperFuncs.changeSubscribe = async(redis, websocketId, websocketObj) => {
127 |
128 | }
129 |
130 | //this function will close the stream after a specified amount of time
131 | function closeChangeStream(timeInMs, changeStream) {
132 | return new Promise((resolve) => {
133 | setTimeout(() => {
134 | console.log("Closing the change stream");
135 | changeStream.close();
136 | resolve();
137 | }, timeInMs)
138 | })
139 | }
140 |
141 | module.exports = eventRouteHelperFuncs;
--------------------------------------------------------------------------------
/libraries/serverlib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@livestatedb/server",
3 | "version": "1.0.1",
4 | "description": "LivestateDB API server package that will hook into an http server",
5 | "main": "setupWebsocket.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "ioredis": "^5.2.3",
13 | "mongodb": "^4.10.0",
14 | "socket.io": "^4.5.3"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/libraries/serverlib/setupWebsocket.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const { initialDbQuery, monitorListingsUsingEventEmitter, unsubscribe } = require('./events/eventHelperFuncs')
4 | //holds current changeStream's open by DB & Collection
5 | const changeStreams = {};
6 | //keeps track of corresponding clients and their respective response objects
7 | const websocketObj = {};
8 | const subscriptionIdObj = {};
9 |
10 | const changeStreamOptionsType = {
11 | database: 'string',
12 | collection: 'string',
13 | query: 'string',
14 | subscriptionId: 'string'
15 | }
16 |
17 | module.exports = async (server, changeStreamOptions) => {
18 |
19 | const {redis, client} = await require('./database/databaseConnect')(changeStreamOptions);
20 | const io = require('socket.io')(server, {
21 | path: '/websocket',
22 | transports: ["websocket"],
23 | })
24 | io.on('connection', async (socket) => {
25 | socket.on('setup', async (changeStreamOptions) => {
26 | for (let prop in changeStreamOptions) {
27 | if (typeof changeStreamOptions[prop] !== changeStreamOptionsType[prop]) {
28 | throw new Error(`\x1b[36m${prop} changeStreamOptions should be ${changeStreamOptionsType[prop]}\x1b[0m`)
29 | }
30 | }
31 | try {
32 | const { subscriptionId, collection, database, query } = changeStreamOptions;
33 | //keep track of connection/reply object by clientsubscriptionId
34 | if(!websocketObj[subscriptionId]) websocketObj[subscriptionId] = socket.id;
35 | //!! can use redis for this later
36 | (subscriptionIdObj[socket.id]) ?
37 | subscriptionIdObj[socket.id].add(subscriptionId) :
38 | subscriptionIdObj[socket.id] = new Set([subscriptionId])
39 | //connect to the current collection
40 | const dbCollection = client.db(database).collection(collection);
41 |
42 | //add add clients to the DB & Collection's they are subscribed too -- for 'insert' updates
43 | const subscriptionDbCollectionKeyDbString = 'DB' + database + 'COL' + collection;
44 | const redisClientIsSubscribedToCollection = await redis.sismember(subscriptionDbCollectionKeyDbString, subscriptionId);
45 | if(redisClientIsSubscribedToCollection === 0){
46 | await redis.sadd(subscriptionDbCollectionKeyDbString, [socket.id])
47 | }
48 | await redis.sadd('DBCOL' + socket.id, [subscriptionDbCollectionKeyDbString])
49 | initialDbQuery(dbCollection, query, redis, subscriptionId, io, websocketObj);
50 |
51 | //check if there is already a changestream for the current collection
52 | if(!changeStreams[database]?.has(collection)) {
53 | //if database exists in object, add collection to set, if not make an entry with key database equal to new set with collection
54 | (changeStreams[database]) ? changeStreams[database].add(collection) : changeStreams[database] = new Set([collection])
55 | await monitorListingsUsingEventEmitter(dbCollection, redis, websocketObj, io);
56 | }
57 | } catch (err) {
58 | if (err) {
59 | if (err) {
60 | console.error('setupWebsocket err:', err)
61 | } else {
62 | let initialDbQueryError = new Error('An unknown error occured while setting up the websocket')
63 | console.error(initialDbQueryError)
64 | }
65 | }
66 | }
67 | })
68 | socket.on('depChange', async ({database, collection, query, subscriptionId}) => {
69 | const currChangeStreamSub = await redis.smembers('DBCOL' + socket.id);
70 | if('DB' + database + 'COL' + collection != currChangeStreamSub){
71 | //check to make sure the current open changestream is needed
72 |
73 | }
74 | const dbCollection = client.db(database).collection(collection);
75 | //check if the new database/collection have a change streams open
76 | await unsubscribe(redis, socket.id, websocketObj, subscriptionIdObj, changeStreams);
77 | //call a function that unsubscribes from all docs for the current subscriptionId
78 | //add add clients to the DB & Collection's they are subscribed too -- for 'insert' updates
79 | const subscriptionDbCollectionKeyDbString = 'DB' + database + 'COL' + collection;
80 | await redis.sadd(subscriptionDbCollectionKeyDbString, [socket.id])
81 | await redis.sadd('DBCOL' + socket.id, [subscriptionDbCollectionKeyDbString])
82 | initialDbQuery(dbCollection, query, redis, subscriptionId, io, websocketObj)
83 | })
84 | socket.on('disconnect', () => {
85 | unsubscribe(redis, socket.id, websocketObj, subscriptionIdObj, changeStreams);
86 | })
87 | })
88 | }
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "livestate",
3 | "version": "1.0.0",
4 | "description": "A real time state management library that allows for state to subscribe and update based on changes from a specific portion of a database.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon server/server.js & NODE_ENV=development webpack serve --open",
8 | "build": "NODE_ENV=production webpack",
9 | "watch": "webpack --watch",
10 | "dev": "NODE_ENV=development webpack serve --open"
11 | },
12 | "nodemonConfig": {
13 | "ignore": [
14 | "build",
15 | "client",
16 | "__tests__"
17 | ]
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/seadubs/nerm-template.git"
22 | },
23 | "author": "",
24 | "license": "ISC",
25 | "bugs": {
26 | "url": "https://github.com/seadubs/nerm-template/issues"
27 | },
28 | "homepage": "https://github.com/seadubs/nerm-template#readme",
29 | "dependencies": {
30 | "@livestatedb/client": "^1.0.0",
31 | "express": "^4.18.1",
32 | "fastify-sse": "^1.0.0",
33 | "fastify-sse-v2": "^2.2.1",
34 | "mongodb": "^4.10.0",
35 | "mongoose": "^6.4.7",
36 | "react": "^18.2.0",
37 | "react-dom": "^18.2.0",
38 | "react-icons": "^4.6.0",
39 | "react-redux": "^8.0.2",
40 | "react-uuid": "^1.0.3",
41 | "socket.io-client": "^4.5.3"
42 | },
43 | "devDependencies": {
44 | "@babel/core": "^7.18.9",
45 | "@babel/preset-env": "^7.18.9",
46 | "@babel/preset-react": "^7.18.6",
47 | "@fastify/cors": "^8.1.0",
48 | "@fastify/redis": "^6.0.1",
49 | "@livestatedb/server": "^1.0.1",
50 | "babel-jest": "^29.2.1",
51 | "babel-loader": "^8.2.5",
52 | "cors": "^2.8.5",
53 | "css-loader": "^6.7.1",
54 | "fastify": "^4.8.1",
55 | "html-webpack-plugin": "^5.5.0",
56 | "ioredis": "^5.2.3",
57 | "jest": "^29.2.1",
58 | "nodemon": "^2.0.19",
59 | "redux-devtools-extension": "^2.13.9",
60 | "sass": "^1.54.0",
61 | "sass-loader": "^13.0.2",
62 | "socket.io": "^4.5.3",
63 | "spdy": "^4.0.2",
64 | "style-loader": "^3.3.1",
65 | "webpack": "^5.74.0",
66 | "webpack-cli": "^4.10.0",
67 | "webpack-dev-server": "^4.9.3"
68 | },
69 | "keywords": []
70 | }
71 |
--------------------------------------------------------------------------------
/server/controllers/inventoryController.js:
--------------------------------------------------------------------------------
1 | const Inventory = require('../models/inventoryModel');
2 | const { createErr } = require('../utils/utils');
3 |
4 | const inventoryController = {};
5 |
6 | // fetches all inventory from the db
7 | inventoryController.getAll = async (req, res, next) => {
8 | try {
9 | const dbRes = await Inventory.find({});
10 | res.locals.inventory = dbRes;
11 | } catch (err) {
12 | return next(
13 | createErr({
14 | method: 'getAllInventory',
15 | type: 'db query error',
16 | err,
17 | })
18 | );
19 | }
20 |
21 | return next();
22 | };
23 |
24 | // creates a new entry in the db
25 | inventoryController.create = async (req, res, next) => {
26 | const required = ['item', 'quantity', 'description', 'price'];
27 | const { item, quantity, description, price } = req.body;
28 |
29 | if (required.some((key) => req.body[key] === undefined)) {
30 | return next(
31 | createErr({
32 | method: 'createInventoryItem',
33 | type: 'data validation error',
34 | err: 'request body did not include all required fields',
35 | })
36 | );
37 | }
38 |
39 | if (
40 | typeof item !== 'string' ||
41 | typeof quantity !== 'number' ||
42 | typeof price !== 'number' ||
43 | typeof description !== 'string'
44 | ) {
45 | return next(
46 | createErr({
47 | method: 'createInventoryItem',
48 | type: 'data validation error',
49 | err: 'request body contained invalid data',
50 | })
51 | );
52 | }
53 |
54 | try {
55 | const dbRes = await Inventory.create(
56 | { item: item,
57 | quantity: quantity,
58 | price: price,
59 | description: description,
60 | });
61 | res.locals.newExample = dbRes;
62 | } catch (err) {
63 | return next(
64 | createErr({
65 | method: 'createExample',
66 | type: 'db insert error',
67 | err,
68 | })
69 | );
70 | }
71 |
72 | return next();
73 | };
74 |
75 | //changes a single field in the db only works for incrementing and decrementing
76 | inventoryController.changeSingleField = async (req, res, next) => {
77 | const required = ['id', 'field', 'value'];
78 | const { id, field, value } = req.body;
79 |
80 | if (required.some((key) => req.body[key] === undefined)) {
81 | return next(
82 | createErr({
83 | method: 'changeSingleField',
84 | type: 'data validation error',
85 | err: 'request body did not include all required fields',
86 | })
87 | );
88 | }
89 |
90 | if (
91 | typeof id !== 'string' ||
92 | typeof field !== 'string' ||
93 | typeof value !== 'number'
94 | ) {
95 | return next(
96 | createErr({
97 | method: 'changeSingleField',
98 | type: 'data validation error',
99 | err: 'request body contained invalid data',
100 | })
101 | );
102 | }
103 |
104 | try {
105 | const updateObj = {}
106 | updateObj.$inc = {};
107 | updateObj.$inc[field] = value;
108 |
109 | const dbRes = await Inventory.updateOne(
110 | { _id: id },
111 | updateObj);
112 | res.locals.updatedInventory = dbRes;
113 | } catch (err) {
114 | return next(
115 | createErr({
116 | method: 'changeSingleField',
117 | type: 'db insert error',
118 | err,
119 | })
120 | );
121 | }
122 |
123 | return next();
124 | };
125 |
126 | //gets a single entry from the db
127 | inventoryController.getOne = async (req, res, next) => {
128 | const { id } = req.params;
129 | try {
130 | const dbRes = await Inventory.find({ _id: id});
131 | res.locals.inventory = dbRes;
132 | } catch (err) {
133 | return next(
134 | createErr({
135 | method: 'getOne',
136 | type: 'db query error',
137 | err,
138 | })
139 | );
140 | }
141 | return next();
142 | };
143 |
144 |
145 | module.exports = inventoryController;
146 |
--------------------------------------------------------------------------------
/server/keys/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDNDCCAhwCCQCvxf5F8fs6kTANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJV
3 | UzERMA8GA1UECAwITmV3IFlvcmsxEjAQBgNVBAcMCVBhdGNob2d1ZTESMBAGA1UE
4 | CgwJTGl2ZVN0YXRlMRIwEAYDVQQLDAlMaXZlU3RhdGUwHhcNMjIxMDA2MDMyNzQ0
5 | WhcNMjMxMDA2MDMyNzQ0WjBcMQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlv
6 | cmsxEjAQBgNVBAcMCVBhdGNob2d1ZTESMBAGA1UECgwJTGl2ZVN0YXRlMRIwEAYD
7 | VQQLDAlMaXZlU3RhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2
8 | 6BREcYUeFWgfH0NhPVqRne+x8LsWPabo633uXXT+V3l0hqcchzKYlLHbxiOLJ8QY
9 | noc/ocUEXN2+hN7e3ncxMz6Pdn/+Q7nqM9WqwaBMQz3gULVDgK2mV86bZElJbH92
10 | eicij6BsSCEYwSe/k9WNikT85nlrN1D9lQP4vOpy5xnhKrb1X4Gn3jzaGugGpsq7
11 | Kbh9N25x8Hu37sWQGu3WYwX218ZlqQIAEP98R/WdiGDS1vEdrBak+cCo7sNL1Pta
12 | wtQvbaUVgETOSgiMWtOoaHBDTtAbsx/p+5U9ImTr/aMbF4OfmIENhTY71kalTTZj
13 | Pg9yi1piv5lcBikNpsflAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJH/rxrh3pXj
14 | QEBXa88oZocv3BwYJ3F8O5x7+1BTz2S7kdUXOlGpDHjZ0cZSO2ZwCFvMVj0c/CsP
15 | iasvDASoOBcQJ8FYwrUxKqeGoVCsoDHCPsVG96u/vnRUSBhRc+uyVuyqPxHOHyPy
16 | BnEmYeYW4cUPHWma+hana5BzSIH2wE3yVyLvMA9/AqKVy8/2gme/Z4Bo6kuq48p/
17 | moaBkRNLxPlM5BBLdu9JtCyFboNP8uuLQjRFguk6xxfHyLWVT7W8pcPyt7Qp+hPv
18 | FVygpINWTlEA7uELgaQr/BfXtIBy+RqI4soD1Aw62JH8JtS+Udd59rFa/I7gfCgN
19 | eu/W0Q55I4o=
20 | -----END CERTIFICATE-----
21 |
--------------------------------------------------------------------------------
/server/keys/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC26BREcYUeFWgf
3 | H0NhPVqRne+x8LsWPabo633uXXT+V3l0hqcchzKYlLHbxiOLJ8QYnoc/ocUEXN2+
4 | hN7e3ncxMz6Pdn/+Q7nqM9WqwaBMQz3gULVDgK2mV86bZElJbH92eicij6BsSCEY
5 | wSe/k9WNikT85nlrN1D9lQP4vOpy5xnhKrb1X4Gn3jzaGugGpsq7Kbh9N25x8Hu3
6 | 7sWQGu3WYwX218ZlqQIAEP98R/WdiGDS1vEdrBak+cCo7sNL1PtawtQvbaUVgETO
7 | SgiMWtOoaHBDTtAbsx/p+5U9ImTr/aMbF4OfmIENhTY71kalTTZjPg9yi1piv5lc
8 | BikNpsflAgMBAAECggEBAKHSRkwfm0R4b/xI7pYTcBOxBaYKc5tDxG4CGxJOwPld
9 | buk3UJYezxbLR2hHrp6o+gdGCiaz+tjXSKAMchn//LgcvFa04pyUUuHwJjPCxw1x
10 | 2EajYRtttzSaLwKwAV7ijQrHG+Sp6aajUhTSn1YRdziYgly1k+rVr+QKeM8SZOJu
11 | MJpkvcWbFnjMpt6TggOt8HK4hk5HzQQ9tIULpFmkYuuIhURuHmPKwxABuyQvz+Qs
12 | z+J3yBF5oDOS1o1kfgX1zOZBdORkyShZ4nP0+xUaE7VgHx6Zwqd3azBAX1ynDJ2z
13 | iGn9MEy/jURXf5rUPL+qlWQolC8rTVnxz59CH9DTpAECgYEA5YXQys/csQ9mx7oD
14 | ckHxzwamCgFeado46f47etzDennZjlp4n/fs3Zyn3e7PFNl56Eu0/6Ne6ty4Bijy
15 | 67Qc1wyGyGkLSLZSViMBElA6ZdGFsTGWFLmb5ieavjDeaeV7FK39Hn3jFBHSgcjL
16 | asQH2UWsIbaXEy1DV0YSp12b14ECgYEAzAGcjiwzWbIa3Tq/rNaAEkOCJj72T60+
17 | AfJa4p7jJxpmelpVFrhLCh+gJdOtiq3WGQWwt+zJuWXSDQJt98b2W0PrTiTzGcb7
18 | 8LWngOovWUam5GjUMpQNaYhmebl4eKRwkYcZ8YQ4cPLPtnYUq9fp68ioYfXHB34c
19 | FIQ7+OtbwmUCgYBGMNgbTdoF49ngtogbRwN19miMosKGyI+jL3ZtKlo1eFIJ8Kz2
20 | 7tDnLONBQajejWt0mMJvczyxwnIcoCU548j2EwSdAVLMF8WyRd4sBZD2Vk72U128
21 | VM7SYcY256b854ruVg8UZhctqg2gVxGuQujLjz0GyloFKVBwviMyP7RLAQKBgCgt
22 | bRLG+7n/jLDjQBqfCAwQxZIYtlPMz7h8bY/SMPRji6kvHzDhTM4KmnS4sqXsRI0b
23 | BMnEVZowxmSPhO7WfXBN+Qqj1kjOWpH2hK2r8XVIp9e2GYx4gge1uFDiySohYJYY
24 | oHBjVOZRu7y4lqudU1F/bEIWsIL2QDt+K32DhMfNAoGASJjL+uCNaaXbAGw9dEZp
25 | JOHuoIiQ3eD4X/JAgZe/1kG3iLHlDWMFNKCjeWwntWhdUWUFpuS8jFkaNvRVNjDZ
26 | 47IGZ9wPWnP8lOmJa+H/bYMpKX2+3W9Pd8Z3tbd9kgN+xRBUB5oSQ+wo3g12CkL4
27 | 1ZKTYnkJ++i7iGLK8C2ijT0=
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/server/models/inventoryModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const path = require('path');
3 | const fs = require('fs');
4 |
5 | //Kevin's MongoDB (I did a few things to make Change Stream work -- only works with sharded online URI...local for current dev is fine)
6 | const MONGO_URI = '';
7 | // const MONGO_URI = 'mongodb://localhost:27017';
8 |
9 | mongoose.connect(MONGO_URI, {
10 | // options for the connect method to parse the URI
11 | useNewUrlParser: true,
12 | useUnifiedTopology: true,
13 | // sets the name of the DB that our collections are part of
14 | dbName: 'inventoryDemo'
15 | })
16 | .then(() => console.log('Mongoose connected to Mongo DB.'))
17 | .catch(err => console.log(err));
18 |
19 | const Schema = mongoose.Schema;
20 |
21 | // inventory tracker using just item name and quantity
22 | const inventorySchema = new mongoose.Schema({
23 | item: { type: String, required: true},
24 | quantity: { type: Number, default: 0 },
25 | description: { type: String, require: true},
26 | price: { type: Number, required: true}
27 | });
28 |
29 | const inventory = mongoose.model('inventoryItem', inventorySchema);
30 |
31 | // You must export your model through module.exports
32 | // The collection name should be 'student'
33 | module.exports = inventory;
34 |
--------------------------------------------------------------------------------
/server/routes/inventory.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const inventoryController = require('../controllers/inventoryController')
3 |
4 | const router = Router();
5 |
6 |
7 | router.get('/all', inventoryController.getAll, (req, res) => {
8 | res.status(200).json(res.locals.inventory);
9 | });
10 |
11 | router.get('/getOne/:id', inventoryController.getOne, (req, res) => {
12 | res.status(200).json(res.locals.inventory);
13 | });
14 |
15 | router.post('/create', inventoryController.create, (req, res) => {
16 | res.status(200).json({message: 'created a new entry'});
17 | })
18 |
19 | router.patch('/changeSingleField', inventoryController.changeSingleField, (req, res) => {
20 | res.status(200).json(res.locals.updatedInventory);
21 | })
22 |
23 | //general handeler if request not found/handled
24 | router.use((req, res) => {
25 | console.log(`server/routes/api.js: handler not found for request ${req.method} ${req.url}`);
26 | res
27 | .status(404)
28 | .json({
29 | message: `Inventory handler for ${req.method} ${req.url} not found`,
30 | });
31 | });
32 |
33 | module.exports = router;
34 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 | const inventory = require('./routes/inventory');
4 | const fs = require('fs');
5 | const http = require('http');
6 |
7 |
8 | const PORT = process.env.EXPRESS_PORT || 3000;
9 | const app = express();
10 |
11 | app.use(express.json());
12 |
13 | const corsOptions = {
14 | origin: true,
15 | credentials: true,
16 | };
17 | const cors = require('cors')(corsOptions);
18 |
19 | // Serve the client build
20 | app.use('/build', express.static(path.resolve(__dirname, '../storedemo/build')));
21 |
22 | // Handle router calls
23 | app.use('/inventory', inventory);
24 |
25 | // Serve index.html
26 | app.get('/', (req, res) => {
27 | res.sendFile(path.resolve(__dirname, '../storedemo/public/', 'index.html'));
28 | });
29 |
30 | // Default 404 handler
31 | app.use((req, res) => {
32 | res
33 | .status(404)
34 | .send(
35 | 'Page not found'
36 | );
37 | });
38 |
39 | // Global error handler
40 | app.use((err, req, res, next) => {
41 | const defaultErr = {
42 | message: { err: 'An error occurred' },
43 | log: 'Express error handler caught unknown middleware error',
44 | status: 400,
45 | };
46 | const errObj = Object.assign(defaultErr, err);
47 | console.log('ErrorObject Log: ', errObj.log);
48 | res.status(errObj.status).send(errObj.message);
49 | });
50 |
51 | const server = http.Server(app);
52 | server.listen(3000, () => console.log('listening on port 3000'));
53 |
54 | const changeStreamOptions =
55 | {
56 | // mongoDbOptions:
57 | // {
58 | // uri: ""
59 | // },
60 | // redisDbOptions:
61 | // { host: ,
62 | // port: ,
63 | // password: ,
64 | // family:
65 | // },
66 | }
67 |
68 | require('../libraries/serverlib/setupWebsocket')(server, changeStreamOptions)
69 | .catch(console.error)
70 |
--------------------------------------------------------------------------------
/server/utils/utils.js:
--------------------------------------------------------------------------------
1 | const {basename} = require('path')
2 |
3 | const utils = {
4 | createErr: (errInfo) => {
5 | const { method, type, err } = errInfo;
6 | return {
7 | log: `${basename(__filename)}.${method} ${type}: ERROR: ${
8 | typeof err === 'object' ? JSON.stringify(err) : err
9 | }`,
10 | message: {
11 | err: `Error occurred in apiRouter.${method}. Check server logs for more details.`,
12 | },
13 | };
14 | },
15 | };
16 |
17 | module.exports = utils;
18 |
--------------------------------------------------------------------------------
/storedemo/README.md:
--------------------------------------------------------------------------------
1 | # demo
2 |
3 | A template repository for a MongoDB - Express - React (w/ Redux) - Node project.
4 |
5 |
--------------------------------------------------------------------------------
/storedemo/build/bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
2 |
3 | /**
4 | * @license React
5 | * react-dom.production.min.js
6 | *
7 | * Copyright (c) Facebook, Inc. and its affiliates.
8 | *
9 | * This source code is licensed under the MIT license found in the
10 | * LICENSE file in the root directory of this source tree.
11 | */
12 |
13 | /**
14 | * @license React
15 | * react.production.min.js
16 | *
17 | * Copyright (c) Facebook, Inc. and its affiliates.
18 | *
19 | * This source code is licensed under the MIT license found in the
20 | * LICENSE file in the root directory of this source tree.
21 | */
22 |
23 | /**
24 | * @license React
25 | * scheduler.production.min.js
26 | *
27 | * Copyright (c) Facebook, Inc. and its affiliates.
28 | *
29 | * This source code is licensed under the MIT license found in the
30 | * LICENSE file in the root directory of this source tree.
31 | */
32 |
33 | //! Can pass props to Display {database, collection, query}, then just pass those variables as args into useSubscribe(database, collection, query)
34 |
--------------------------------------------------------------------------------
/storedemo/build/images/50.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/50.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/Free.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/Free.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/Portrait.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/Portrait.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/beautiful life.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/beautiful life.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/body & arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/body & arms.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/hold me in your arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/hold me in your arms.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/keep it turned on.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/keep it turned on.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/logo.png
--------------------------------------------------------------------------------
/storedemo/build/images/my red book.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/my red book.jpeg
--------------------------------------------------------------------------------
/storedemo/build/images/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/transparent.png
--------------------------------------------------------------------------------
/storedemo/build/images/whenever you need.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/build/images/whenever you need.jpeg
--------------------------------------------------------------------------------
/storedemo/build/index.html:
--------------------------------------------------------------------------------
1 | inventory Demo
--------------------------------------------------------------------------------
/storedemo/client/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Display from './components/Display.jsx'
3 |
4 | const App = () => {
5 |
6 | return (
7 |
8 |
13 | );
14 | };
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/storedemo/client/components/Cart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo } from "react"
2 | import { FaShoppingCart } from "react-icons/fa";
3 | import { IconContext } from "react-icons";
4 |
5 |
6 |
7 | const Cart = ({ quantity, handleCartClick }) => {
8 | console.log('quantity', quantity)
9 | console.log(typeof quantity)
10 | if(quantity > 0){
11 | return(
12 |
13 |
handleCartClick()}>
14 |
15 | {quantity}
16 |
17 |
18 | )
19 | }else {
20 | return(
21 |
22 |
23 |
24 | )
25 | }
26 |
27 |
28 | }
29 |
30 | export default Cart;
--------------------------------------------------------------------------------
/storedemo/client/components/Display.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo, useRef } from "react"
2 | import InventoryList from './InventoryList.jsx'
3 | import { changeSingleInventoryField } from '../services/inventory'
4 | // import { handleDecrementClickHelper } from '../services/events'
5 | import { useSubscribe } from "../../../libraries/clientlib/customHook";
6 | import { FaShoppingCart } from "react-icons/fa";
7 | import Cart from './Cart.jsx'
8 |
9 |
10 | //! Can pass props to Display {database, collection, query}, then just pass those variables as args into useSubscribe(database, collection, query)
11 | const Display = () => {
12 |
13 | const [ inventoryHookOptions, setInventoryHookOptions] = useState({
14 | database: 'inventoryDemo',
15 | collection: 'inventoryitems',
16 | query: {},
17 | })
18 |
19 | const [ inventoryList, endSubscription ] = useSubscribe(inventoryHookOptions);
20 | // const [ cart, setCart ] = useState(0);
21 | const [ shoppingCart, setShoppingCart ] = useState({length: 0});
22 | const [ pressedCart, setPressedCart ] = useState(false);
23 | const counter = useRef(0);
24 |
25 |
26 | const addToCard = (id) => {
27 | setShoppingCart((oldCart) => {
28 | console.log(counter.current);
29 | const newCart = JSON.parse(JSON.stringify(oldCart));
30 | (counter.current === 0 || newCart[id] === 'sound') ? newCart[id] = 'sound' : newCart[id] = true
31 | counter.current++;
32 | return newCart;
33 | })
34 | }
35 |
36 |
37 |
38 | //increment/decrement click function
39 | const handleIncDecClick = (id, field, value) => {
40 | changeSingleInventoryField(id, field, value)
41 | // .then(setCart((prev) => prev + 1))
42 | .then(addToCard(id))
43 | }
44 |
45 | const handleCartClick = () => {
46 | setPressedCart(true);
47 | }
48 |
49 | return (
50 |
51 |
52 |
53 |
54 | Rick Rollin' Records
55 |
56 |
57 |
58 |
59 |
62 | Record Shop
63 |
69 |
70 |
71 | );
72 | }
73 |
74 | export default Display;
--------------------------------------------------------------------------------
/storedemo/client/components/InventoryList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react"
2 | import Item from './Item.jsx'
3 |
4 |
5 | const InventoryList = ({ inventoryList, handleIncDecClick, pressedCart, shoppingCart }) => {
6 |
7 |
8 | const inventory = [];
9 | let i = 0;
10 |
11 | // inventory.push(
12 | //
21 | // )
22 |
23 | //for every item in the database we add an item component for rendering
24 | for(let items in inventoryList){
25 | const path = 'build/images/'
26 | inventory.push(
27 |
39 | )
40 | i++;
41 | }
42 |
43 | return (
44 |
45 | { inventory }
46 |
47 | );
48 | }
49 |
50 | export default InventoryList;
--------------------------------------------------------------------------------
/storedemo/client/components/Item.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 |
3 | const Item = (
4 | { item,
5 | description,
6 | quantity,
7 | price,
8 | handleIncDecClick,
9 | id,
10 | imageUrl,
11 | pressedCart,
12 | shoppingCart
13 | }) => {
14 |
15 | if(quantity > 0){
16 | return(
17 |
18 | {pressedCart === false &&
19 |
e.currentTarget.src = './images/logo.png'}
22 | />
23 | }
24 | {pressedCart === true && shoppingCart === false &&
25 |
e.currentTarget.src = './images/logo.png'}
28 | />
29 | }
30 | {pressedCart === true && shoppingCart === true &&
31 |
VIDEO
37 | }
38 | {pressedCart === true && shoppingCart === 'sound' &&
39 |
VIDEO
45 | }
46 |
47 |
48 | {item}
49 |
50 |
51 | {description}
52 |
53 | {/*
54 | {quantity}
55 |
*/}
56 |
57 | ${price}
58 |
59 |
60 | handleIncDecClick(id, 'quantity', -1)}>
61 | Add to Cart
62 |
63 |
64 |
65 | )}else {
66 | return ( <>>)
67 | }
68 | }
69 |
70 | export default Item;
--------------------------------------------------------------------------------
/storedemo/client/index.css:
--------------------------------------------------------------------------------
1 | /* ul > li {
2 | display: inline-block;
3 | }
4 |
5 | li {
6 | padding-left: 20px;
7 | } */
8 |
9 | * {
10 | color: #0D0D0D;
11 | padding: 0px;
12 | margin: 0px;
13 | font-family: sans-serif;
14 | }
15 |
16 | h1 {
17 | font-size: 8vw;
18 | text-align: center;
19 | }
20 |
21 | h2 {
22 | font-size: 5vw;
23 | text-align: center;
24 | margin-bottom: 5px;
25 | }
26 |
27 | button {
28 | background-color: transparent;
29 | border: 1px solid rgb(168, 166, 166);
30 | border-radius: 2px;
31 | color: #232323;
32 | margin: 3px;
33 | padding: 3px;
34 | box-shadow: 0 1px rgb(225, 224, 224);
35 | }
36 |
37 | button:hover {
38 | background-color: rgb(247, 244, 244);
39 | }
40 |
41 | button:active {
42 | box-shadow: 0 .2px #666;
43 | transform: translateY(.4px);
44 | }
45 |
46 | .rickPicRight {
47 | width: 5%;
48 | margin-left: 5px;
49 | }
50 |
51 | .cart {
52 | display: flex;
53 | flex-direction: row;
54 | position: relative;
55 | float: right;
56 | top: 10px;
57 | margin-right: 5px;
58 | padding: 0px;
59 | }
60 |
61 | .cartButton {
62 | width: 25px;
63 | display: flex;
64 | }
65 |
66 | .quantity {
67 | position: relative;
68 | font-size: .5em;
69 | margin: 1px;
70 | }
71 |
72 | .rickPicLeft {
73 | width: 5%;
74 | margin-right: 5px;
75 | transform: scaleX(-1);
76 | }
77 |
78 | .header {
79 | margin-top: 10px;
80 | display: flex;
81 | }
82 |
83 | .display {
84 | background-color: #F8F8F8;
85 | border: solid 1px #e0dcdc;
86 | border-radius: 5px;
87 | margin: auto;
88 | width: 90%;
89 | height: 90%;
90 | padding: 5px;
91 | }
92 |
93 | .card {
94 | color: #232323;
95 | display: flex;
96 | flex-direction: column;
97 | background-color:white;
98 | border: 1px solid white;
99 | border-radius: 3px;
100 | margin: 5px;
101 | box-shadow: 5px 5px 5px rgba(26,115,232,0.06);
102 | /* width: 95%; */
103 | text-align: center;
104 | }
105 |
106 | /* .rickRoll {
107 | position: absolute;
108 | top: 150;
109 | left: 0;
110 | width: 100%;
111 | height: 100%;
112 | } */
113 |
114 | .cardLayout{
115 | display: grid;
116 | grid-template-columns: repeat(auto-fit, minmax(250px, 20%));
117 | margin: auto;
118 | /* flex-wrap: wrap;
119 | justify-content: space-evenly; */
120 | }
121 |
122 | /* .values{
123 | color: #232323;
124 | border: solid 1px #808080;
125 | border-radius: 0px;
126 | width: 100%;
127 | text-align: center;
128 | margin-left: -1px;
129 | }
130 |
131 | .label {
132 | display: flex;
133 | justify-content: space-evenly;
134 | margin-bottom: 5px;
135 | font-weight: bold;
136 | } */
137 |
138 |
139 |
140 | /*
141 | #quantity:hover > .buttonContainer {
142 | display: inline-block;
143 | }
144 |
145 | #quantity {
146 | display: inline-block;
147 | } */
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/storedemo/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import App from './App.jsx';
4 | import './index.css';
5 |
6 | const container = document.getElementById('root');
7 | const root = createRoot(container);
8 |
9 | root.render (
10 |
11 | )
--------------------------------------------------------------------------------
/storedemo/client/services/events.js:
--------------------------------------------------------------------------------
1 | import { changeSingleInventoryField, getSingleInventory } from './inventory'
2 |
3 | //helper functions for events
4 |
5 | const handleIncremementClickHelper = async (id, field, value) => {
6 | //wait for result of calling function to increment DB
7 | await changeSingleInventoryField(id, field, value)
8 | //then call the fucntion that gets inventory of single item from DB (update)
9 | // return getSingleInventory(id)
10 | }
11 |
12 | const handleDecrementClickHelper = async (id, field, value) => {
13 | //wait for result of calling function to decrement DB
14 | return changeSingleInventoryField(id, field, value)
15 | //then call the fucntion that gets inventory of single item from DB (update)
16 | // return getSingleInventory(id)
17 | }
18 |
19 | export {
20 | handleIncremementClickHelper,
21 | handleDecrementClickHelper,
22 | }
23 |
--------------------------------------------------------------------------------
/storedemo/client/services/inventory.js:
--------------------------------------------------------------------------------
1 | //helper functions for all requests to inventory router
2 | module.exports = inventoryFunctions = {
3 |
4 | getAllInventory: () => {
5 | return fetch('/inventory/all')
6 | .then(data => data.json())
7 | },
8 |
9 | getSingleInventory: (id) => {
10 | return fetch('/inventory/getOne/' + id)
11 | .then(data => data.json())
12 | },
13 |
14 | createInventory: (item, quantity, description, price) => {
15 | return fetch('/inventory/create', {
16 | method: 'POST',
17 | headers: { 'Content-Type': 'application/json' },
18 | body: JSON.stringify({
19 | item: item,
20 | quantity: quantity,
21 | description: description,
22 | price: price,
23 | })
24 | });
25 | },
26 |
27 | changeSingleInventoryField: (id, field, value) => {
28 | return fetch('/inventory/changeSingleField', {
29 | method: 'PATCH',
30 | headers: { 'Content-Type': 'application/json' },
31 | body: JSON.stringify({
32 | id: id,
33 | field: field,
34 | value: value,
35 | })
36 | })
37 | }
38 | }
--------------------------------------------------------------------------------
/storedemo/client/services/ytapi.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/client/services/ytapi.js
--------------------------------------------------------------------------------
/storedemo/public/images/50.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/50.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/Free.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/Free.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/Portrait.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/Portrait.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/beautiful life.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/beautiful life.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/body & arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/body & arms.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/hold me in your arms.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/hold me in your arms.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/keep it turned on.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/keep it turned on.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/logo.png
--------------------------------------------------------------------------------
/storedemo/public/images/my red book.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/my red book.jpeg
--------------------------------------------------------------------------------
/storedemo/public/images/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/transparent.png
--------------------------------------------------------------------------------
/storedemo/public/images/whenever you need.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/LiveStateDB/50e2797239bb8898f14645ab1df6623f12f8084b/storedemo/public/images/whenever you need.jpeg
--------------------------------------------------------------------------------
/storedemo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | inventory Demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const fs = require('fs');
4 |
5 | console.log('NODE_ENV: ', process.env.NODE_ENV)
6 |
7 | module.exports = {
8 | entry: path.resolve(__dirname, './storedemo/client/index.js'),
9 |
10 | output: {
11 | path: path.resolve(__dirname, './storedemo/build/'),
12 | filename: 'bundle.js',
13 | publicPath: '/build',
14 | },
15 |
16 | mode: process.env.NODE_ENV,
17 |
18 | plugins: [
19 | new HtmlWebpackPlugin({
20 | title: 'Development',
21 | template: path.resolve(__dirname, './storedemo/public/index.html')
22 | }),
23 | ],
24 | devServer: {
25 | static: {
26 | directory: path.resolve(__dirname, './storedemo/public/'),
27 | },
28 | // server: {
29 | // type: 'https',
30 | // options: {
31 | // key: fs.readFileSync(path.resolve(__dirname, './server/keys/server.key')),
32 | // cert: fs.readFileSync(path.resolve(__dirname, './server/keys/server.crt'))
33 | // }
34 | // },
35 | compress: false,
36 | port: 8082,
37 | proxy: {
38 | '/api/**': {
39 | target: 'http://localhost:3000/',
40 | secure: false,
41 | },
42 | '/inventory/': {
43 | target: 'http://localhost:3000/',
44 | secure: false,
45 | },
46 | // '/event/**': {
47 | // target: 'https://localhost:3000/',
48 | // secure: true,
49 | // ws: true,
50 | // },
51 | '/websocket': {
52 | target: 'http://localhost:3000/',
53 | secure: false,
54 | ws: true,
55 | },
56 | },
57 | },
58 |
59 | module: {
60 | rules: [
61 | {
62 | test: /\.jsx?/,
63 | exclude: /node_modules/,
64 | use: {
65 | loader: 'babel-loader',
66 | options: {
67 | presets: ['@babel/preset-env', '@babel/preset-react'],
68 | },
69 | },
70 | },
71 | {
72 | test: /\.css$/i,
73 | use: [
74 | // Compiles Sass to CSS
75 | 'style-loader',
76 | 'css-loader',
77 | 'sass-loader',
78 | ],
79 | },
80 | ],
81 | },
82 | resolve: {
83 | extensions: ['.js','.jsx','.json']
84 | }
85 | };
86 |
--------------------------------------------------------------------------------