From e62b79f84eaa22be40907eaa3217d0d55cf0c56f Mon Sep 17 00:00:00 2001 From: cv <34649812+cpvalente@users.noreply.github.com> Date: Sat, 10 Apr 2021 11:39:53 +0200 Subject: [PATCH] feat: eventsAPI --- client/package.json | 2 + client/src/app/api/eventsApi.js | 8 + client/src/features/editors/Editor.jsx | 17 +- .../src/features/editors/list/EventList.jsx | 189 ++++-------------- .../editors/list/EventListWrapper.jsx | 81 ++++++++ client/yarn.lock | 90 ++++++++- server/app.js | 3 +- server/controllers/eventsController.js | 102 +++++++++- server/data/eventsData.json | 32 +-- server/data/eventsDefinition.js | 23 +++ server/package.json | 3 +- server/routes/eventsRouter.js | 22 +- server/yarn.lock | 5 + 13 files changed, 384 insertions(+), 193 deletions(-) create mode 100644 client/src/app/api/eventsApi.js create mode 100644 client/src/features/editors/list/EventListWrapper.jsx create mode 100644 server/data/eventsDefinition.js diff --git a/client/package.json b/client/package.json index bb40c6a48..da7b48116 100644 --- a/client/package.json +++ b/client/package.json @@ -10,11 +10,13 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "axios": "^0.21.1", "date-fns": "^2.19.0", "framer-motion": "^4", "react": "^17.0.1", "react-dom": "^17.0.1", "react-icons": "^4.2.0", + "react-query": "^3.13.5", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", diff --git a/client/src/app/api/eventsApi.js b/client/src/app/api/eventsApi.js new file mode 100644 index 000000000..2fa80fd1d --- /dev/null +++ b/client/src/app/api/eventsApi.js @@ -0,0 +1,8 @@ +import { serverURL } from './apiConstants'; +export const eventsURL = serverURL + 'events/'; + +export const fetchAllEvents = async () => { + const res = await fetch(eventsURL + 'all'); + // TODO: Safe json convert + return res.json(); +}; \ No newline at end of file diff --git a/client/src/features/editors/Editor.jsx b/client/src/features/editors/Editor.jsx index 63416cfc7..36d3f682d 100644 --- a/client/src/features/editors/Editor.jsx +++ b/client/src/features/editors/Editor.jsx @@ -2,9 +2,9 @@ import { IconButton } from '@chakra-ui/button'; import { SettingsIcon } from '@chakra-ui/icons'; import { Grid, GridItem, Heading } from '@chakra-ui/layout'; import { Box } from '@chakra-ui/layout'; -import { useContext } from 'react'; -import { useEffect, useState } from 'react'; -import { EventContext } from '../../app/context/eventContext'; +import { useContext, useEffect } from 'react'; +import { useQuery } from 'react-query'; +import { useState } from 'react'; import { EventListContext } from '../../app/context/eventListContext'; import NumberedText from '../../common/components/text/NumberedText'; import PlaybackControl from '../control/PlaybackControl'; @@ -12,26 +12,22 @@ import MessageForm from '../form/MessageForm'; import PreviewContainer from '../viewers/PreviewContainer'; import styles from './Editor.module.css'; import EventList from './list/EventList'; +import EventListWrapper from './list/EventListWrapper'; export default function Editor() { const [formMode, setFormMode] = useState(null); - const [events] = useContext(EventListContext); const [webEvents, setWebEvents] = useState(null); - const [event, setEvent] = useContext(EventContext); const [playback, setPlayback] = useState({ current: null, next: null, currentTimer: null, numEvents: 0, state: 'pause', - prevState: 'pause', }); const updatePlayback = (vals) => { setPlayback({ ...playback, ...vals }); }; - console.log('events here', webEvents); - return (
- diff --git a/client/src/features/editors/list/EventList.jsx b/client/src/features/editors/list/EventList.jsx index 0b0426b9e..87f0a9b03 100644 --- a/client/src/features/editors/list/EventList.jsx +++ b/client/src/features/editors/list/EventList.jsx @@ -1,160 +1,55 @@ -import { useContext, useEffect } from 'react'; -import { EventContext } from '../../../app/context/eventContext'; -import { EventListContext } from '../../../app/context/eventListContext'; import EventListItem from './EventListItem'; import style from './List.module.css'; import DelayBlock from './DelayBlock'; import BlockBlock from './BlockBlock'; -import EventListMenu from '../../menu/EventListMenu'; - export default function EventList(props) { - const [events, setEvents] = useContext(EventListContext); - const [event] = useContext(EventContext); + const { events, selected, eventsHandler } = props; - // update number of events - useEffect(() => { - const f = events.filter((e) => e.type === 'event'); - props.updatePlayback({ numEvents: f.length }); - }, [events]); - - const insertItemAt = (item, index) => { - // handle insert at beggining of array - if (index === -1) { - // move all items one element down, starting from new position - events.forEach((e) => { - e.order = e.order + 1; - }); - return [item, ...events]; - } else { - let before = events.slice(0, index + 1); - let after = events.slice(index + 1); - - // move all items one element down, starting from new position - after.forEach((e) => { - e.order = e.order + 1; - }); - return [...before, item, ...after]; - } - }; - - const createEvent = (itemIndex = -1) => { - // make an event - // TODO: Replace this with global def somewhere - // TODO: handle random ids better - let newEvent = { - id: Math.random(), - order: itemIndex + 1, - title: '', - subtitle: '', - presenter: '', - timeStart: new Date(), - timeEnd: new Date(), - clockStarted: null, - timerDuration: 0, - type: 'event', - }; - - // set to state - setEvents(insertItemAt(newEvent, itemIndex)); - }; - - const createDelay = (itemIndex = 0) => { - // make an event - // TODO: Replace this with global def somewhere - // TODO: handle random ids better - let newEvent = { - id: Math.random(), - order: itemIndex + 1, - timerDuration: 0, - type: 'delay', - }; - - // set to state - setEvents(insertItemAt(newEvent, itemIndex)); - }; - - const createBlock = (itemIndex = 0) => { - // make an event - // TODO: Replace this with global def somewhere - // TODO: handle random ids better - let newEvent = { - id: Math.random(), - order: itemIndex + 1, - type: 'block', - }; - - // set to state - setEvents(insertItemAt(newEvent, itemIndex)); - }; - - const deleteEvent = (index) => { - // TODO: This feels weird? - let e = events.splice(index, 1); - setEvents([...events]); - }; - - const replaceAt = (array, index, value) => { - const ret = array.slice(0); - ret[index] = value; - return ret; - }; - - const updateData = (itemIndex, data) => { - const newEvents = replaceAt(events, itemIndex, data); - setEvents(newEvents); - }; - - console.log('events in event list', events); + console.log('EventList: events in event list', events); let cumulativeDelay = 0; let eventCount = -1; + // Torbjorn: is this very dirty code? + // -- map and cumulative delay thing + // -- should i skip the order value and just use the array as order? (checkout dnd) return ( - <> - -
- {events.map((e, index) => { - if (e.type === 'event') { - eventCount = eventCount + 1; - return ( - - ); - } else if (e.type === 'block') { - cumulativeDelay = 0; - return ( - - ); - } else if (e.type === 'delay') { - cumulativeDelay = cumulativeDelay + e.timerDuration; - return ( - - ); - } - })} -
- +
+ {events.map((e, index) => { + if (e.type === 'event') { + eventCount = eventCount + 1; + return ( + + ); + } else if (e.type === 'block') { + cumulativeDelay = 0; + return ( + + ); + } else if (e.type === 'delay') { + cumulativeDelay = cumulativeDelay + e.timerDuration; + return ( + + ); + } + })} +
); } diff --git a/client/src/features/editors/list/EventListWrapper.jsx b/client/src/features/editors/list/EventListWrapper.jsx new file mode 100644 index 000000000..3577148a0 --- /dev/null +++ b/client/src/features/editors/list/EventListWrapper.jsx @@ -0,0 +1,81 @@ +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useEffect } from 'react'; +import { fetchAllEvents } from '../../../app/api/eventsApi.js'; +import EventList from './EventList'; +import EventListMenu from '../../menu/EventListMenu.jsx'; +import axios from 'axios'; +import { showErrorToast } from '../../../common/helpers/toastManager'; +import { eventsURL } from '../../../app/api/eventsApi'; +import { Skeleton } from '@chakra-ui/skeleton'; +import style from './List.module.css'; + +export default function EventListWrapper() { + const { data, status, isError } = useQuery('events', fetchAllEvents); + const queryClient = useQueryClient(); + // TODO: Move to events API? + const addEvent = useMutation((data) => axios.post(eventsURL, data)); + const updateEvent = useMutation((data) => axios.put(eventsURL, data)); + const deleteEvent = useMutation((eventId) => + axios.delete(eventsURL + eventId) + ); + + // Show toasts on errors + useEffect(() => { + if (isError) { + showErrorToast('Error fetching data'); + } + }, [isError]); + + // Events API + const eventsHandler = async (action, payload) => { + // Torbjorn: is this a good way to do it? + // How do I handle the mutation thing + // https://react-query.tanstack.com/guides/invalidations-from-mutations + // https://react-query.tanstack.com/guides/updates-from-mutation-responses + switch (action) { + case 'add': + try { + const event = await addEvent + .mutateAsync(payload) + .then(queryClient.invalidateQueries('events')); + } catch (error) { + showErrorToast('Error creating event', error.message); + } + break; + case 'update': + try { + const event = await updateEvent + .mutateAsync(payload) + .then(queryClient.invalidateQueries('events')); + } catch (error) { + showErrorToast('Error updating event', error.message); + } + break; + case 'delete': + try { + const event = await deleteEvent + .mutateAsync(payload) + .then(queryClient.invalidateQueries('events')); + } catch (error) { + showErrorToast('Error deleting event', error.message); + } + break; + default: + showErrorToast('Unrecognised request', action); + break; + } + }; + + return ( + <> + + {status === 'success' ? ( + + ) : ( +
+ +
+ )} + + ); +} diff --git a/client/yarn.lock b/client/yarn.lock index 622156aff..35a4f1287 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1120,7 +1120,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== @@ -3219,6 +3219,13 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.3.tgz#64a4c85509e0991f5168340edc4bedd1ceea6966" integrity sha512-vwPpH4Aj4122EW38mxO/fxhGKtwWTMLDIJfZ1He0Edbtjcfna/R3YB67yVhezUMzqc3Jr3+Ii50KRntlENL4xQ== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -3467,6 +3474,11 @@ bfj@^7.0.2: hoopy "^0.1.4" tryer "^1.0.1" +big-integer@^1.6.16: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -3568,6 +3580,19 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +broadcast-channel@^3.4.1: + version "3.5.3" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.5.3.tgz#c75c39d923ae8af6284a893bfdc8bd3996d2dd2d" + integrity sha512-OLOXfwReZa2AAAh9yOUyiALB3YxBe0QpThwwuyRHLgpl8bSznSDmV6Mz7LeBJg1VZsMcDcNMy7B53w12qHrIhQ== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.0.4" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -5804,7 +5829,7 @@ focus-lock@^0.8.1: dependencies: tslib "^1.9.3" -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.10.0: version "1.13.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== @@ -7482,6 +7507,11 @@ jest@26.6.0: import-local "^3.0.2" jest-cli "^26.6.0" +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -7919,6 +7949,14 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +match-sorter@^6.0.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.0.tgz#454a1b31ed218cddbce6231a0ecb5fdc549fed01" + integrity sha512-efYOf/wUpNb8FgNY+cOD2EIJI1S5I7YPKsw0LBp7wqPh5pmMS6i/wr3ZWwfwrAw1NvqTA2KUReVRWDX84lUcOQ== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -8011,6 +8049,11 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -8213,6 +8256,13 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + nanoid@^3.1.20: version "3.1.22" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" @@ -9959,6 +10009,15 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-query@^3.13.5: + version "3.13.5" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.13.5.tgz#b59e2094014dae0461ed7aab68ae030be41eda8a" + integrity sha512-rL0NO2gVfaeMaVD0Z0yrxZT1oMGejGxGD5t+5d8eC9kpID6oa3r2bFBR3nJeuPhwdGvZtifbB1KJkKdPYaYqVg== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -10268,6 +10327,11 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -10473,6 +10537,13 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -10480,13 +10551,6 @@ rimraf@^2.5.4, rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -11841,6 +11905,14 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" diff --git a/server/app.js b/server/app.js index bac88584e..e963d2111 100644 --- a/server/app.js +++ b/server/app.js @@ -28,7 +28,8 @@ const app = express(); app.use(cors()); // Implement middleware -// --- +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); // Implement route endpoints app.use('/events', eventsRouter); diff --git a/server/controllers/eventsController.js b/server/controllers/eventsController.js index 7287d11ce..e96bbc0fd 100644 --- a/server/controllers/eventsController.js +++ b/server/controllers/eventsController.js @@ -1,5 +1,16 @@ +// utils +const { nanoid } = require('nanoid'); + // import json with initial data -const events = require('../data/eventsData.json'); +let events = require('../data/eventsData.json'); +const eventDefs = require('../data/eventsDefinition.js'); + +// aux +const replaceAt = (array, index, value) => { + const ret = array.slice(0); + ret[index] = value; + return ret; +}; // Create controller for GET request to '/events' // Returns ACK message @@ -8,7 +19,94 @@ exports.eventsGet = async (req, res) => { }; // Create controller for GET request to '/events/all' -// Returns playback state object +// Returns - exports.eventsGetAll = async (req, res) => { res.json(events); }; + +// Create controller for GET request to '/events/:Id' +// Returns - +exports.eventsGetById = async (req, res) => { + // TODO +}; + +// Create controller for POST request to '/events/' +// Returns - +exports.eventsPost = async (req, res) => { + // TODO: Validate event + if (req.body) { + // ensure structure + let newEvent = {}; + req.body.id = nanoid(10); + + switch (req.body.type) { + case 'event': + newEvent = { ...eventDefs.event, ...req.body }; + break; + case 'delay': + newEvent = { ...eventDefs.delay, ...req.body }; + break; + case 'block': + newEvent = { ...eventDefs.block, ...req.body }; + break; + + default: + res.sendStatus(400); + break; + } + + // Torbjorn: hmmmmmmm, can we look here? + if (newEvent.order <= 0) { + newEvent.order = 0; + // insert at top + events.forEach((e) => { + e.order = e.order + 1; + }); + events = [newEvent, ...events]; + } else if (newEvent.order >= events.length) { + newEvent.order = events.length; + events = [...events, newEvent]; + } else { + let before = events.slice(0, newEvent.order); + let after = events.slice(newEvent.order); + // move all items one element down, starting from new position + after.forEach((e) => { + e.order = e.order + 1; + }); + events = [...before, newEvent, ...after]; + } + console.log('added', events); + res.sendStatus(201); + } else { + console.log('nothing to add', req.body); + res.sendStatus(400); + } +}; + +// Create controller for PUT request to '/events/:id' +// Returns - +exports.eventsPut = async (req, res) => { + const itemIndex = events.findIndex((e) => e.id == req.body.id); + + if (!itemIndex) res.sendStatus(400); + const newEvents = replaceAt(events, itemIndex, req.body); + events = [...newEvents]; + res.sendStatus(200); +}; + +// Create controller for DELETE request to '/events/' +// Returns - +exports.eventsDelete = async (req, res) => { + if (req.params.id) { + const itemIndex = events.findIndex((e) => e.id == req.params.id); + + if (!itemIndex) res.sendStatus(400); + + // Torbjorn: this syntax is very bad + const e = events.splice(itemIndex, 1); + events = [...events]; + res.sendStatus(200); + } else { + res.sendStatus(400); + } +}; diff --git a/server/data/eventsData.json b/server/data/eventsData.json index 41512f420..33feaed6f 100644 --- a/server/data/eventsData.json +++ b/server/data/eventsData.json @@ -1,48 +1,54 @@ [ { - "id": "1", + "id":"5ce39a29-a70c-426a-9ce2-f97782049c71", + "order": 0, + "timerDuration": 5, + "type": "delay" + }, + { + "id": "5ce39a29-a70c-426a-9ce2-f97782049c72", "order": 1, "title": "Is the internet a fad?", "subtitle": "It is", "presenter": "Carlos Valente", - "timeStart": "", - "timeEnd":"", + "timeStart": "2020-04-09T11:25:43.511Z", + "timeEnd": "2020-04-09T12:25:43.511Z", "type": "event" }, { - "id": 0.4849093424577693, + "id":"5ce39a29-a70c-426a-9ce2-f97782049c73", "order": 2, "timerDuration": 25, "type": "delay" }, { - "id": "2", + "id": "5ce39a29-a70c-426a-9ce2-f97782049c74", "order": 3, "title": "Is reddit a dictatorship?", "subtitle": "It is", "presenter": "Carlos Valente", - "timeStart": "", - "timeEnd":"", + "timeStart": "2020-04-09T13:25:43.511Z", + "timeEnd": "2020-04-09T14:25:43.511Z", "type": "event" }, { - "id": "3", + "id": "5ce39a29-a70c-426a-9ce2-f97782049c75", "order": 4, "title": "Out of words", "subtitle": "", "presenter": "Carlos Valente", - "timeStart": "", - "timeEnd":"", + "timeStart": "2020-04-09T15:25:43.511Z", + "timeEnd": "2020-04-09T16:25:43.511Z", "type": "event" }, { - "id": "4", + "id": "5ce39a29-a70c-426a-9ce2-f97782049c76", "order": 5, "title": "Really", "subtitle": "", "presenter": "Carlos Valente", - "timeStart": "", - "timeEnd":"", + "timeStart": "2020-04-09T17:25:43.511Z", + "timeEnd": "2020-04-09T18:25:43.511Z", "type": "event" } ] \ No newline at end of file diff --git a/server/data/eventsDefinition.js b/server/data/eventsDefinition.js new file mode 100644 index 000000000..e0d942acc --- /dev/null +++ b/server/data/eventsDefinition.js @@ -0,0 +1,23 @@ +const event = { + order: 0, + title: '', + subtitle: '', + presenter: '', + timeStart: new Date(), + timeEnd: new Date(), + clockStarted: null, + type: 'event', +}; + +const delay = { + order: 0, + timerDuration: 0, + type: 'delay', +}; + +const block = { + order: 0, + type: 'block', +}; + +module.exports = { event, delay, block }; diff --git a/server/package.json b/server/package.json index bf391d93f..e8b645df4 100644 --- a/server/package.json +++ b/server/package.json @@ -6,6 +6,7 @@ "body-parser": "~1.19.0", "express": "~4.17.1", "express-session": "~1.17.1", + "nanoid": "^3.1.22", "passport": "~0.4.1", "passport-local": "~1.0.0", "socket.io": "^4.0.0" @@ -13,4 +14,4 @@ "scripts": { "start": "node app.js" } -} \ No newline at end of file +} diff --git a/server/routes/eventsRouter.js b/server/routes/eventsRouter.js index 50c6806e2..15357ea33 100644 --- a/server/routes/eventsRouter.js +++ b/server/routes/eventsRouter.js @@ -1,19 +1,25 @@ const express = require('express'); -const cors = require('cors'); const router = express.Router(); -// Cross origin stuff -const corsOptions = { - origin: 'http://localhost', -} - // import playback controller const eventsController = require('../controllers/eventsController'); // create route between controller and '/events' endpoint -router.get('/', cors(corsOptions), eventsController.eventsGet); +router.get('/', eventsController.eventsGet); // create route between controller and '/events/all' endpoint -router.get('/all', cors(corsOptions), eventsController.eventsGetAll); +router.get('/all', eventsController.eventsGetAll); + +// create route between controller and '/events/:id' endpoint +router.get('/:id', eventsController.eventsGetById); + +// create route between controller and '/events/' endpoint +router.post('/', eventsController.eventsPost); + +// create route between controller and '/events/:id' endpoint +router.put('/', eventsController.eventsPut); + +// create route between controller and '/events/:id' endpoint +router.delete('/:id', eventsController.eventsDelete); module.exports = router; diff --git a/server/yarn.lock b/server/yarn.lock index 182780d0c..0e26cbf62 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -339,6 +339,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +nanoid@^3.1.22: + version "3.1.22" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" + integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"