├── .DS_Store ├── .babelrc ├── .gitignore ├── .vscode └── launch.json ├── README.md ├── client ├── App.jsx ├── Sample.jsx ├── assets │ └── Codesmith.png ├── components │ ├── AddSearchEvent.jsx │ ├── Chatroom.jsx │ ├── Content.jsx │ ├── CreateEvent.jsx │ ├── Event.jsx │ ├── EventAttendees.jsx │ ├── EventsFeed.jsx │ ├── Fileupload.jsx │ ├── MainContainer.jsx │ ├── Navbar.jsx │ ├── Profile.jsx │ └── SearchEvent.jsx ├── index.html ├── index.js └── stylesheets │ └── styles.scss ├── iteration-project_postgres_create.sql ├── package.json ├── server ├── controllers │ ├── cookieController.js │ ├── eventController.js │ ├── fbController.js │ ├── fileController.js │ └── loginController.js ├── models │ └── models.js ├── routers │ └── api.js ├── server.js ├── server.test.js ├── upload.js └── utils │ ├── queries.js │ └── tempCodeRunnerFile.js ├── tests ├── enzyme.test.js └── jest.config.json └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-Source-Program/Be-Social/af922970e9034143a13d322943293a16aaef3cfc/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | package-lock.json 3 | node_modules 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/server/routers/api.js", 15 | "outFiles": [ 16 | "${workspaceFolder}/**/*.js" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scratch-project -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import MainContainer from "./components/MainContainer.jsx" 3 | 4 | export default function App() { 5 | return < MainContainer /> 6 | }; -------------------------------------------------------------------------------- /client/Sample.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect } from "react"; 2 | import { 3 | BrowserRouter as Router, 4 | Switch, 5 | Route, 6 | Link 7 | } from "react-router-dom"; 8 | 9 | import Modal from 'react-bootstrap/Modal'; 10 | import Button from 'react-bootstrap/Button'; 11 | import Form from 'react-bootstrap/Form'; 12 | import DatePicker from "react-datepicker"; 13 | import "react-datepicker/dist/react-datepicker.css"; 14 | import 'bootstrap/dist/css/bootstrap.min.css'; 15 | 16 | export default function App() { 17 | return ( 18 | 19 |
20 | 39 | 40 | {/* A looks through its children s and 41 | renders the first one that matches the current URL. */} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 |
61 | ); 62 | } 63 | 64 | function Home() { 65 | return

Home

; 66 | } 67 | 68 | function About() { 69 | return

About

; 70 | } 71 | 72 | function Users() { 73 | return

Users

; 74 | } 75 | 76 | function Example() { 77 | const [startDate, setDate] = useState(new Date()) 78 | const [show, setShow] = useState(false); 79 | 80 | const handleClose = () => setShow(false); 81 | const handleShow = () => setShow(true); 82 | const handleDate = date => { 83 | setDate(date); 84 | }; 85 | 86 | let month = []; 87 | for (let i = 1; i <= 12; i++) { 88 | month.push(); 89 | } 90 | 91 | let day = []; 92 | for (let i = 1; i <= 31; i++) { 93 | day.push(); 94 | } 95 | 96 | let year = []; 97 | for (let i = 2020; i <= 2030; i++) { 98 | year.push(); 99 | } 100 | 101 | return ( 102 |
103 | 106 | 107 | 108 | 109 | Create New Event 110 | 111 | 112 | 113 |
114 | 115 | Event Title 116 | 117 | 118 | 119 | 120 | Event Description 121 | 122 | 123 | 124 | 128 | 131 | 132 |
133 |
134 |
135 | ); 136 | } 137 | 138 | function Example2() { 139 | const [count, setCount] = useState(0); 140 | 141 | // Similar to componentDidMount and componentDidUpdate: 142 | useEffect(() => { 143 | // Update the document title using the browser API 144 | document.title = `You clicked ${count} times`; 145 | }); 146 | 147 | return ( 148 |
149 |

You clicked {count} times

150 | 153 |
154 | ); 155 | } -------------------------------------------------------------------------------- /client/assets/Codesmith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-Source-Program/Be-Social/af922970e9034143a13d322943293a16aaef3cfc/client/assets/Codesmith.png -------------------------------------------------------------------------------- /client/components/AddSearchEvent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CreateEvent from './CreateEvent.jsx'; 3 | import SearchEvent from './SearchEvent.jsx'; 4 | 5 | export default function AddSearchEvent(props) { 6 | return ( 7 |
8 | 9 | 10 |
11 | ) 12 | } 13 | 14 | -------------------------------------------------------------------------------- /client/components/Chatroom.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | 3 | class MessageList extends React.Component { 4 | render() { 5 | return ( 6 | 20 | ) 21 | } 22 | } 23 | 24 | export default MessageList; -------------------------------------------------------------------------------- /client/components/Content.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Media, Form, Button } from 'react-bootstrap'; 3 | 4 | export default function Content(props) { 5 | // console.log('=========> Content.jsx props: ', props) 6 | const [cont, setCont] = useState(props.content); 7 | // console.log('=========> Content.jsx cont: ', cont) 8 | // console.log('=========> Content.jsx cont.length: ', cont.length) 9 | const [comment, setComment] = useState("") 10 | // let [messages, setMessages] = useState([]); 11 | 12 | let messages = []; 13 | if (cont && cont.length) { 14 | messages = cont.map((message, index) => { 15 | return ( 16 |
17 |
18 | 19 |
20 |
21 |

{message.username}

22 |

{message.messagetext}

23 |

{message.messagedate}

24 |

{message.messagetime}

25 |
26 |
27 | ) 28 | }); 29 | } 30 | 31 | const handleChange = (e) => { 32 | setComment(e.target.value); 33 | } 34 | 35 | function handleCommentSubmit(e) { 36 | e.preventDefault(); 37 | const date = new Date(); 38 | // newContent is what we're posting back 39 | const newContent = { eventid: props.eventid, eventtitle: props.eventtitle, messagetext: comment, messagedate: date.toDateString(), messagetime: date.toTimeString() } 40 | console.log('Content.jsx newContent: ', newContent) 41 | //clear form data 42 | document.getElementsByName('comment-form')[0].reset(); 43 | 44 | props.handleCreateMessage(newContent); 45 | const updatedCont = [cont].concat(newContent) 46 | 47 | setCont(updatedCont) 48 | window.location.reload(true); 49 | } 50 | 51 | 52 | return ( 53 |
54 |

Comments

55 |
56 | {messages} 57 |
58 |
59 | 60 | Add a Comment: 61 | 62 | 63 | 66 |
67 |
68 | ); 69 | } -------------------------------------------------------------------------------- /client/components/CreateEvent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | 3 | import DateTimePicker from 'react-datetime-picker'; 4 | 5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 6 | import { faPlus, faSearchPlus } from '@fortawesome/free-solid-svg-icons' 7 | import { Modal, Button, Form, Card, Dropdown, ButtonGroup } from 'react-bootstrap'; 8 | 9 | export default function CreateEvent({ addEvent }) { 10 | /* Form data */ 11 | const initialFormData = Object.freeze({ 12 | eventtitle: "", 13 | eventlocation: "", 14 | eventdetails: "", 15 | eventtype: "calendar" 16 | }); 17 | 18 | const initialFormData2 = Object.freeze({ 19 | eventtitle: "", 20 | eventlocation: "", 21 | eventdetails: "", 22 | eventtype: "cooking" 23 | }); 24 | 25 | const [formData, updateFormData] = useState(initialFormData); 26 | const [formData2, updateFormData2] = useState(initialFormData2); 27 | const [dateTime, onChange] = useState(new Date()); 28 | const [dateTime2, onChange2] = useState(new Date()); 29 | const [show, setShow] = useState(false); 30 | const [show2, setShow2] = useState(false); 31 | 32 | console.log('CreateEvent.jsx: ', dateTime2); 33 | const handleChange = (e) => { 34 | updateFormData({ 35 | ...formData, 36 | 37 | // Trimming any whitespace 38 | [e.target.name]: e.target.value.trim() 39 | }); 40 | }; 41 | 42 | const handleChange2 = (e) => { 43 | updateFormData2({ 44 | ...formData2, 45 | 46 | // Trimming any whitespace 47 | [e.target.name]: e.target.value.trim() 48 | }); 49 | }; 50 | 51 | const handleSubmit = (e) => { 52 | e.preventDefault() 53 | const eventdate = dateTime.toDateString(); 54 | let time = dateTime.toTimeString(); 55 | let eventstarttime = time.split(" ")[0]; 56 | // ... submit to API or something 57 | addEvent({ ...formData, eventdate, eventstarttime }); 58 | handleClose(); 59 | }; 60 | 61 | const handleSubmit2 = (e) => { 62 | e.preventDefault() 63 | const eventdate = dateTime2.toDateString(); 64 | console.log('=========> CreateEvent.jsx eventdate: ', eventdate) 65 | let time = dateTime2.toTimeString(); 66 | console.log('=========> CreateEvent.jsx time: ', time) 67 | let eventstarttime = time.split(" ")[0]; 68 | console.log('=========> CreateEvent.jsx eventstarttime: ', eventstarttime) 69 | // ... submit to API or something 70 | addEvent({ ...formData2, eventdate, eventstarttime }); 71 | handleClose2(); 72 | }; 73 | 74 | const handleClose = () => setShow(false); 75 | const handleShow = () => setShow(true); 76 | 77 | const handleClose2 = () => setShow2(false); 78 | const handleShow2 = () => setShow2(true); 79 | 80 | // const handleDate = date => { 81 | // setDate(date); 82 | // }; 83 | 84 | return ( 85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | Cook Off 94 | Calendar Event 95 | 96 | 97 |
98 | 99 | 100 | 101 | Create Cooking Event 102 | 103 | 104 |
105 | 106 | Cooking Event Title 107 | 108 | 109 | 110 | 111 | Ingredients 112 | 113 | 114 | 115 | 116 | Event Description 117 | 118 | 119 | 120 | 121 | Start Date & Time 122 | 126 | 127 | 128 | 129 | Event Type 130 | 131 | 132 | 133 | 136 |
137 |
138 |
139 | 140 | 141 | 142 | Create Calendar Event 143 | 144 | 145 |
146 | 147 | Event Title 148 | 149 | 150 | 151 | 152 | Location 153 | 154 | 155 | 156 | 157 | Event Description 158 | 159 | 160 | 161 | 162 | Start Date & Time 163 | 167 | 168 | 169 | 170 | Event Type 171 | 172 | 173 | 174 | 177 |
178 |
179 |
180 |
181 | ); 182 | } -------------------------------------------------------------------------------- /client/components/Event.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from "react"; 2 | import EventAttendees from './EventAttendees.jsx'; 3 | import Content from './Content.jsx'; 4 | import FileUpload from './Fileupload.jsx'; 5 | import DateTimePicker from 'react-datetime-picker'; 6 | import { ListGroup, Container, Row, Jumbotron, Modal, Button, Form, Card, Dropdown, ButtonGroup } from 'react-bootstrap'; 7 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 8 | import { faLocationArrow } from '@fortawesome/free-solid-svg-icons' 9 | import axios from "axios"; 10 | 11 | export default function Event(props) { 12 | const [file, setFile] = useState(''); 13 | const [filename, setFilename] = useState('Choose File'); 14 | const [data, getFile] = useState({ name: "", path: "" }); 15 | const [progress, setProgess] = useState(0); 16 | const el = useRef(); 17 | 18 | const initialFormData = Object.freeze({ 19 | eventtitle: props.eventtitle, 20 | eventlocation: props.eventlocation, 21 | eventdetails: props.eventdetails, 22 | eventtype: "calendar" 23 | }); 24 | const [formData, updateFormData] = useState(initialFormData); 25 | const [dateTime, onChange] = useState(new Date()); 26 | const [show, setShow] = useState(false); 27 | 28 | const handleChange = (e) => { 29 | updateFormData({ 30 | ...formData, 31 | 32 | // Trimming any whitespace 33 | [e.target.name]: e.target.value.trim() 34 | }); 35 | windows.location.reload(true); 36 | }; 37 | 38 | const handleSubmit = (e) => { 39 | console.log('THIS IS HANDLE SUBMIT: ', formData) 40 | e.preventDefault() 41 | const eventdate = dateTime.toDateString(); 42 | let time = dateTime.toTimeString(); 43 | let eventstarttime = time.split(" ")[0]; 44 | // ... submit to API or something 45 | props.handleUpdateEvent({ ...formData, eventdate, eventstarttime, eventid: props.eventid }); 46 | handleClose(); 47 | console.log('state of show', show) 48 | windows.location.reload(true); 49 | }; 50 | 51 | const handleClose = () => setShow(false); 52 | const handleShow = () => setShow(true); 53 | 54 | return ( 55 | <> 56 | 57 |
58 | 59 | 60 | 61 |

{props.eventtitle}

62 |

{props.eventdate} - {props.starttime}

63 |
{props.eventlocation}
64 |
{props.eventdetails}
65 |
66 | {/* 70 | 73 |
74 | 75 |
76 | 80 |
81 | 82 |
83 | 84 |
85 | 86 |
87 |
88 | 89 | {/* Model Pop Up Box */} 90 | 91 | 92 | Create Calendar Event 93 | 94 | 95 |
96 | 97 | Event Title 98 | 99 | 100 | 101 | 102 | Location 103 | 104 | 105 | 106 | 107 | Event Description 108 | 109 | 110 | 111 | 112 | Start Date & Time 113 | 117 | 118 | 119 | 120 | Event Type 121 | 122 | 123 | 124 | 127 |
128 |
129 |
130 |
131 |
132 | 133 | ); 134 | } -------------------------------------------------------------------------------- /client/components/EventAttendees.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Image, Col, Row } from 'react-bootstrap'; 3 | 4 | export default function EventAttendees({ attendees, userUpdate }) { 5 | 6 | let attendeesList = []; 7 | if (attendees) { 8 | attendeesList = attendees.map((attendee, index) => { 9 | return ( 10 |
11 |
12 | { userUpdate(attendee.username) }} /> 13 |
14 |

{attendee.firstname} {attendee.lastname}

15 |
16 | ) 17 | }); 18 | } 19 | 20 | return ( 21 |
22 |
Attendees:
23 |
24 | {attendeesList} 25 |
26 |
27 | ); 28 | } -------------------------------------------------------------------------------- /client/components/EventsFeed.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Event from './Event.jsx'; 3 | import axios from 'axios'; 4 | 5 | export default function EventsFeed(props) { 6 | 7 | function handleDeleteEvent(evtID, index) { 8 | console.log(props) 9 | axios.delete(`/api/delete?eventid=${evtID}`); 10 | console.log('THIS IS THE OLD EVENTS ARRAY: ') 11 | console.log(props.events); 12 | const newState = props.events.splice(index, 1); 13 | console.log('THIS IS NEW EVENTS ARRAY: ') 14 | console.log(props.events); 15 | props.setEvents(newState); 16 | 17 | // const newEvents = [event].concat(events); 18 | // console.log("updated events:", newEvents); 19 | // setEvents(newEvents); 20 | windows.location.reload(true); 21 | } 22 | 23 | function handleUpdateEvent(data) { 24 | console.log('HANDLE UPDATE: ', data) 25 | axios.put(`/api/update`, data); 26 | 27 | 28 | // const newEvents = [event].concat(events); 29 | // console.log("updated events:", newEvents); 30 | // setEvents(newEvents); 31 | windows.location.reload(true); 32 | } 33 | 34 | console.log("Events Feed", props) 35 | let events = []; 36 | // useEffect(() => { 37 | // console.log('THIS IS USE EFFECT') 38 | if (props.events && Object.keys(props.events).length > 0) { 39 | events = props.events.map((event, index) => { 40 | return 49 | }) 50 | } 51 | // },[]) 52 | 53 | return ( 54 |
55 | {events} 56 |
57 | ); 58 | } -------------------------------------------------------------------------------- /client/components/Fileupload.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, Fragment } from 'react'; 2 | import axios from 'axios'; 3 | const reader = new FileReader(); 4 | 5 | function FileUpload(props) { 6 | const [file, setFile] = useState(''); // storing the uploaded file // storing the recived file from backend 7 | const [filename, setFilename] = useState('Choose File'); // traversy 8 | const [uploadedFiles, setUploadedFile] = useState([]); // traversey 9 | const [data, getFile] = useState({ name: "", path: "" }); 10 | const [progress, setProgess] = useState(0); // progess bar 11 | const el = useRef(); // accesing input element 12 | 13 | console.log('FileUpload.jsx props: ', props) 14 | 15 | const handleChange = (e) => { // consistent with traversey 16 | e.preventDefault(); 17 | setProgess(0); // medium 18 | // console.log('fileupload.jsx handlechange typeof target.files[0]: ', typeof e.target.files[0]) 19 | // console.log('fileupload.jsx handlechange target.files[0]: ', e.target.files[0]) 20 | // console.log('fileupload.jsx handlechange typeof target.files[0].name: ', typeof e.target.files[0].name) 21 | // console.log('fileupload.jsx handlechange target.files[0].name: ', e.target.files[0].name) 22 | // const convertedFile = reader.readAsDataURL(e.target.files); 23 | // const convertedName = reader.readAsDataURL(e.target.files[0]); 24 | // console.log('fileupload.jsx converted: ', converted) 25 | setFile(e.target.files[0]); // storing file 26 | setFilename(e.target.files[0].name); 27 | // these don't print anything, async 28 | console.log('Fileupload.jsx file: ', file); 29 | console.log('Fileupload.jsx filename: ', filename); 30 | } 31 | 32 | const uploadFile = (e) => { // TRAVERSY NEEDS ASYNC 33 | e.preventDefault(); 34 | const formData = new FormData(); 35 | // const imageFile = document.querySelector('.formInput'); 36 | // formData.append('file', imageFile.files[0]); // appending file 37 | console.log('typeof file in uploadFile: ', typeof file) 38 | console.log('file in uploadFile: ', file) 39 | console.log('filename in uploadFile: ', filename) 40 | console.log('typeof filename in uploadFile: ', typeof filename) 41 | formData.append('file', file, filename); // appending file 42 | // formData.append('filename', filename); // codeburst.io 43 | 44 | // ================== FETCH ================== 45 | // fetch("/api/upload", { 46 | // mode: 'no-cors', 47 | // method: "POST", 48 | // headers: { 49 | // "Content-Type": "multipart/form-data", 50 | // "Accept": "application/json", 51 | // "type": "formData" 52 | // }, 53 | // body: formData 54 | // }).then(function (res) { 55 | // console.log(res); 56 | // }) 57 | // .catch(err => console.log(err)); 58 | 59 | // ================== AXIOS ================== 60 | axios.post('/api/upload', formData, { 61 | // headers: formData.getHeaders(), 62 | headers: { 63 | "Content-Type": "multipart/form-data", 64 | }, 65 | content: formData, 66 | onUploadProgress: (ProgressEvent) => { 67 | let progress = Math.round( 68 | ProgressEvent.loaded / ProgressEvent.total * 100) + '%'; 69 | setProgess(progress); 70 | console.log('=======> progress updated in state') 71 | } 72 | }) 73 | .then(res => { 74 | console.log(res); 75 | // console.log('Fileupload.jsx res.data.path: ', res.data.path) 76 | // console.log('Fileupload.jsx localhost + res.data.path: ', 'http://localhost:8080' + res.data.path) 77 | // setUploadedFile({ name, path }) 78 | getFile({ 79 | name: res.data.name, 80 | // path: 'http://localhost:8080' + res.data.path 81 | path: '/' + res.data.path 82 | }); 83 | const updatedFiles = uploadedFiles.push({ name: res.data.name, path: '/' + res.data.path }) 84 | setUploadedFile(updatedFiles); 85 | }) 86 | .catch(err => { 87 | console.log('Fileupload.jsx error: ', err); 88 | // TRAVERSEY 89 | // try { 90 | // const res = await axios.post('/upload', formData, { 91 | // headers: { 92 | // "Content-Type": "multipart/form-data" 93 | // } 94 | // }); 95 | // const { fileName, filePath } = res.data; 96 | // setUploadedFile({ fileName, filePath }); 97 | // } catch (err) { 98 | // if (err.response.status === 500) { 99 | // console.log('There was a problem with the server') 100 | // } else { 101 | // console.log(err.response.data.msg) 102 | // } 103 | // } 104 | }) 105 | } 106 | 107 | let photoList = []; 108 | console.log('uploadedFiles :', uploadedFiles) 109 | if (uploadedFiles && uploadedFiles.length) { 110 | photoList = uploadedFiles.map((photo, index) => { 111 | return ( 112 |
113 | {photo.name} 114 |
115 | ) 116 | }) 117 | } 118 | 119 | return ( 120 |
121 |
122 | 123 |
124 | {progress} 125 |
126 | { handleChange(e) }} /> 127 |
128 |
129 | {data.path && {data.name}} 130 | {/* {photoList} */} 131 |
132 |
133 |

View Photos and Videos

134 |
135 |
136 | // TRAVERSY 137 | // 138 | //
139 | //
140 | // 141 | // 144 | // 145 | //
146 | //
147 | //
148 | ); 149 | } 150 | export default FileUpload; -------------------------------------------------------------------------------- /client/components/MainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Profile from './Profile.jsx'; 3 | import Chatroom from './Chatroom.jsx'; 4 | import EventsFeed from './EventsFeed.jsx'; 5 | import Notnav from './Navbar.jsx'; 6 | import axios from 'axios'; 7 | import { Card, Button, Col, Row, Container } from 'react-bootstrap'; 8 | import AddSearchEvent from './AddSearchEvent.jsx'; 9 | 10 | export default function MainContainer() { 11 | // const [messages, setMessages] = useState([]); 12 | const [userName, setUserName] = useState(""); // email? 13 | const [user, setUser] = useState({}); // actual name of user 14 | const [events, setEvents] = useState([]); 15 | const [status, setStatus] = useState(""); 16 | const [dummyData, setDummyData] = useState([ 17 | { 18 | senderId: "take out trash", 19 | text: "feed the dogs" 20 | }, 21 | { 22 | senderId: "fix back patio door", 23 | text: "water the plants, all 144" 24 | }, 25 | { 26 | senderId: "fix back patio door", 27 | text: "get haircut" 28 | }, 29 | { 30 | senderId: "fix back patio door", 31 | text: "catch up on correspondence" 32 | }, 33 | ]); 34 | 35 | useEffect(() => { 36 | axios.get(`/api/info?userName=${userName}`) // currently query is not in use (save for when we can visit other ppl's pages) 37 | .then((res) => { 38 | console.log('get request in mainContainer.jsx, res data: ', res.data); 39 | let userInfo = { 40 | userid: res.data.users.userid, 41 | username: res.data.users.username, 42 | firstname: res.data.users.firstname, 43 | lastname: res.data.users.lastname, 44 | profilephoto: res.data.users.profilephoto, 45 | } 46 | let eventsInfo = res.data.events; // content: already included in eventsInfo 47 | setUser(userInfo); 48 | setEvents(eventsInfo); 49 | setUserName(res.data.users.username); 50 | setStatus('loggedIn'); 51 | }) 52 | }, []); 53 | 54 | 55 | 56 | function handleUserPageChange(username) { 57 | console.log('username:', username) 58 | setUserName(username); 59 | } 60 | 61 | function handleCreateEvent(event) { 62 | let { eventtitle, eventlocation, eventdate, eventstarttime, eventdetails, eventtype } = event; 63 | console.log('MainContainer.jsx eventtitle: ', eventtitle) 64 | console.log('MainContainer.jsx eventlocation OR INGREDIENT: ', eventlocation) 65 | console.log('MainContainer.jsx eventtype: ', eventtype) 66 | console.log('MainContainer.jsx eventdate: ', eventdate) 67 | console.log('MainContainer.jsx eventstarttime: ', eventstarttime) 68 | console.log('MainContainer.jsx eventdetails: ', eventdetails) 69 | axios.post(`/api/create?userName=${userName}`, { eventtitle, eventlocation, eventdate, eventstarttime, eventdetails, eventtype }) 70 | .then((res) => { 71 | console.log(res.data); 72 | // let userInfo = { 73 | // userName: res.data.users.username, 74 | // firstName: res.data.users.firstname, 75 | // lastName: res.data.users.lastname, 76 | // profilePicture: res.data.users.profilephoto, 77 | // } 78 | // let eventsInfo = res.data.events; 79 | // setUser(userInfo); 80 | // setEvents(eventsInfo); 81 | }) 82 | event.attendees = [{ 83 | username: user.username, 84 | profilephoto: user.profilephoto 85 | }]; 86 | const newEvents = [event].concat(events); 87 | console.log("updated events:", newEvents); 88 | setEvents(newEvents); 89 | } 90 | 91 | function handleCreateMessage(content) { // bubble this down to Content.jsx 92 | let { userid, username } = user; 93 | console.log('=======> handleCreateMessage userid: ', userid) 94 | console.log('=======> handleCreateMessage userid: ', username) 95 | let { eventid, eventtitle, messagetext, messagedate, messagetime } = content; 96 | axios.post(`/api/message?eventtitle=${eventtitle}`, { userid, username, eventid, messagetext, messagedate, messagetime }) 97 | .then((res) => { 98 | console.log(res.data); 99 | // let userInfo = { 100 | // userName: res.data.users.username, 101 | // firstName: res.data.users.firstname, 102 | // lastName: res.data.users.lastname, 103 | // profilePicture: res.data.users.profilephoto, 104 | // } 105 | // let eventsInfo = res.data.events; 106 | // setUser(userInfo); 107 | // setEvents(eventsInfo); 108 | }) 109 | event.content = [{ 110 | username: user.username, 111 | profilephoto: user.profilephoto, 112 | messagetext: messagetext, 113 | messagedate: messagedate, 114 | messagetime: messagetime, 115 | }]; 116 | // const newEvents = [event].concat(events); 117 | // console.log("updated event with messages:", newEvents); 118 | // setEvents(newEvents); 119 | window.location.reload(true); 120 | } 121 | 122 | function handleSearchEvent(event) { 123 | console.log("Main Container:", event) 124 | // ADD 125 | axios.post(`/api/add?eventtitle=${event.eventtitle}`) 126 | .then((res) => { 127 | console.log(res.data); 128 | event.attendees.push( 129 | { 130 | username: user.username, 131 | firstname: user.firstname, 132 | lastname: user.lastname, 133 | profilephoto: user.profilephoto 134 | }); 135 | if (!events.content) event.content = []; 136 | else { 137 | event.content.push({ 138 | username: events.content.username, 139 | profilephoto: events.content.profilephoto, 140 | text: events.content.messagetext, 141 | time: events.content.messagetime, 142 | }); 143 | }; 144 | const newEvents = [event].concat(events); 145 | console.log("updated events:", newEvents); 146 | setEvents(newEvents); 147 | }) 148 | // END ADD 149 | // event.attendees.push( 150 | // { 151 | // username: user.username, 152 | // profilephoto: user.profilephoto 153 | // }); 154 | // const newEvents = [event].concat(events); 155 | // console.log("updated events:", newEvents); 156 | // setEvents(newEvents); 157 | } 158 | 159 | return ( 160 |
161 |
162 | 163 |
164 |
165 |
166 | 167 | 168 | 169 | 170 |

Scratch Pad

171 | 172 |
173 | 174 |
175 | 176 |
177 |
178 |
179 |
180 |
181 | 187 |
188 |
189 |
190 | ); 191 | } -------------------------------------------------------------------------------- /client/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { Navbar, Nav, Button } from 'react-bootstrap'; 2 | import React, { useState } from 'react'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faGoogle } from '@fortawesome/free-brands-svg-icons' 5 | import { faFacebook } from '@fortawesome/free-brands-svg-icons' 6 | // import { faFeatherAlt } from '@fortawesome/free-solid-svg-icons' 7 | import { faBlog } from '@fortawesome/free-solid-svg-icons' 8 | import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons' 9 | 10 | export default function Notnav(props) { 11 | 12 | console.log('status', props.status); 13 | console.log(props); 14 | 15 | const navOutput = []; 16 | 17 | if (props.status === '') { 18 | navOutput.push() 19 | navOutput.push() 20 | } else if (props.status === 'loggedIn') { 21 | navOutput.push() 22 | } 23 | 24 | return ( 25 |
26 | 27 | 36 | 49 | 52 | 53 |
54 | ) 55 | } -------------------------------------------------------------------------------- /client/components/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Card } from 'react-bootstrap'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faStar } from '@fortawesome/free-solid-svg-icons' 5 | import { faStarHalf } from '@fortawesome/free-solid-svg-icons' 6 | 7 | export default function Profile(props) { 8 | return ( 9 |
10 | {/*

Profile

*/} 11 | {/* */} 12 | 13 |
14 | 15 | 16 | {props.firstname} {props.lastname} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

26 | last logged in: {new Date().toDateString()}
27 | username: {props.username}
28 | status: gold 29 |

30 |
31 |
32 | {/* */} 33 |
34 |
35 | ); 36 | } -------------------------------------------------------------------------------- /client/components/SearchEvent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { 3 | BrowserRouter as Router, 4 | Switch, 5 | Route, 6 | Link 7 | } from "react-router-dom"; 8 | 9 | import DateTimePicker from 'react-datetime-picker'; 10 | 11 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 12 | import { faSearchPlus } from '@fortawesome/free-solid-svg-icons' 13 | import { Modal, Button, Form, Card } from 'react-bootstrap'; 14 | import axios from 'axios'; 15 | 16 | export default function SearchEvent({ searchEvent, events }) { 17 | /* Form data */ 18 | const initialFormData = Object.freeze({ 19 | title: "", 20 | description: "" 21 | }); 22 | 23 | const [formData, updateFormData] = React.useState(initialFormData); 24 | const [results, updateResults] = useState([]); 25 | const [show, setShow] = useState(false); 26 | 27 | let exampleEventData; 28 | 29 | // const exampleEventData = [ 30 | // { 31 | // title: 'Awesome Event', 32 | // eventOwner: { 33 | // userName: 'marc', 34 | // firstName: 'Marc', 35 | // lastName: 'Burnie', 36 | // profilePicture: 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/Pug_portrait.jpg/1599px-Pug_portrait.jpg' 37 | // }, 38 | // date: '08/15/2020', 39 | // time: '02:46 PM EST', 40 | // location: 'nowhere', 41 | // description: 'Enter a meaningful description here', 42 | // attendees: [ 43 | // { 44 | // userName: 'user1', 45 | // profilePicture: 'https://www.thesprucepets.com/thmb/sfuyyLvyUx636_Oq3Fw5_mt-PIc=/3760x2820/smart/filters:no_upscale()/adorable-white-pomeranian-puppy-spitz-921029690-5c8be25d46e0fb000172effe.jpg' 46 | // }, 47 | // { 48 | // userName: 'user2', 49 | // profilePicture: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/golden-retriever-royalty-free-image-506756303-1560962726.jpg' 50 | // }, 51 | // ], 52 | // content: [ 53 | // { 54 | // text: 'awesome!! can\'t wait', 55 | // time: '02:51 PM EST' // formatting not set in stone 56 | // }, 57 | // { 58 | // text: 'lets go', 59 | // time: '01:35 PM EST' 60 | // } 61 | // ] 62 | // }, 63 | // { 64 | // title: 'I Need Some Sleep', 65 | // description: 'please', 66 | // attendees: [ 67 | // { 68 | // userName: 'user3', 69 | // profilePicture: 'https://images.theconversation.com/files/319375/original/file-20200309-118956-1cqvm6j.jpg' 70 | // }, 71 | // { 72 | // userName: 'user4', 73 | // profilePicture: 'https://thedogtale.com/wp-content/uploads/2019/09/Yorkie-Weight-Chart_How-Big-Will-My-Yorkie-Get.jpg' 74 | // }, 75 | // ] 76 | // } 77 | // ]; 78 | 79 | useEffect(() => { 80 | axios.get('/api/events') 81 | .then(res => { 82 | exampleEventData = res.data; 83 | }) 84 | }); 85 | 86 | const handleChange = (e) => { 87 | const regex = new RegExp(e.target.value.trim(), "gi"); 88 | const eventTitles = events.map(event => event.eventtitle) 89 | updateResults(exampleEventData.filter((event) => event.eventtitle.match(regex) && !eventTitles.includes(event.eventtitle))) 90 | }; 91 | 92 | const handleSubmit = (e, event) => { 93 | e.preventDefault() 94 | searchEvent(event) 95 | handleClose(); 96 | }; 97 | 98 | const handleClose = () => setShow(false); 99 | const handleShow = () => setShow(true); 100 | 101 | //generates a list of events on load using fetch 102 | const btnResults = results.map(event => { 103 | return ( 104 | 105 | ); 106 | }) 107 | 108 | 109 | 110 | return ( 111 |
112 | 113 | {/* 114 |
115 | 116 | 117 | Search for Event 118 | 119 |
120 |
*/} 121 | 122 |
123 | 124 |

Search Events

125 |
126 | 127 | 128 | 129 | Search for an Event 130 | 131 | 132 | 133 |
134 | 135 | Please enter the event name below: 136 | 137 | 138 |
139 | {btnResults} 140 | 141 |
142 | 143 |
144 |
145 |
146 |
147 | ); 148 | } -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | be social 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.css'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import App from './App.jsx'; 6 | import styles from './stylesheets/styles.scss'; 7 | 8 | // import Sample from './Sample.jsx'; 9 | 10 | //

Can you see me?

, 11 | render( 12 | ( 13 | 14 | ), 15 | 16 | document.getElementById('root'), 17 | ); -------------------------------------------------------------------------------- /client/stylesheets/styles.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Nunito&family=Quicksand&family=Hi+Melody&family=Averia+Gruesa+Libre&family=Baloo+Bhai+2&family=Alata&family=Kulim+Park&family=Della+Respira&family=Stylish&family=Baumans&family=Abril+Fatface&family=Dancing+Script&family=Montserrat&family=Julius+Sans+One&family=Baloo+Tamma+2&family=Poiret+One&display=swap'); 2 | 3 | html, body, .col1, .col2 { 4 | height: 100%; 5 | font-family: 'Nunito'; 6 | } 7 | 8 | body { 9 | color:rgb(77, 77, 77); 10 | background-color: #f0f2f5; 11 | margin: 0; 12 | } 13 | 14 | img{ 15 | object-fit: contain; 16 | } 17 | 18 | .myContainer { 19 | display: flex; 20 | flex-direction: column; 21 | border-radius: 40px; 22 | } 23 | 24 | .topnav { 25 | width: 120px; 26 | } 27 | 28 | .columncontainer { 29 | display: flex; 30 | align-items: center; 31 | justify-items: center; 32 | justify-self: center; 33 | } 34 | 35 | .navcontainer { 36 | position: fixed; 37 | z-index: 100; 38 | width: 100%; 39 | } 40 | 41 | .container { 42 | top: 120; 43 | border-radius: 50px; 44 | margin: 0; 45 | } 46 | 47 | .col1 { 48 | position: fixed; 49 | margin: 30px; 50 | width: 28%; 51 | min-width: 400px; 52 | top: 120px; 53 | } 54 | 55 | .col2 { 56 | margin-top: 70px; 57 | background-color:#f0f2f5; 58 | margin-left: 30%; 59 | overflow: auto; 60 | min-width: 800px; 61 | width: 70%; 62 | height: 200vh; 63 | border-radius: 50px; 64 | } 65 | 66 | .navbar-light .navbar-brand { 67 | color: white; 68 | position: fixed; 69 | } 70 | 71 | .navbar-light:hover .navbar-brand:hover { 72 | color: #fcfcfc; 73 | outline: #fcfcfc solid 5px; 74 | cursor: pointer; 75 | } 76 | 77 | .myNavbar { 78 | background-color: #ffffff; 79 | // width: 90%; 80 | // background: linear-gradient(217deg, rgba(46, 112, 255, 0.8), rgba(255,0,0,0) 70.71%), 81 | // linear-gradient(127deg, rgba(10, 38, 82, 0.8), rgba(0,255,0,0) 70.71%), 82 | // linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); 83 | margin-bottom: 30px; 84 | height: 120px; 85 | display: flex; 86 | justify-items: center; 87 | } 88 | 89 | .navButton { 90 | color: #74a4f2; 91 | border: 1px solid #74a4f2; 92 | margin: 8px; 93 | border-radius: 20px; 94 | } 95 | 96 | .navButton:hover, .navButton:focus, .navButton:active { 97 | background: radial-gradient(ellipse at left, #1876f2, transparent), 98 | radial-gradient(ellipse at right, #74a4f2, transparent); 99 | border: none; 100 | outline: none; 101 | cursor: pointer; 102 | } 103 | 104 | .navSignOut { 105 | color: gray; 106 | border: 1px solid gray; 107 | margin: 8px; 108 | border-radius: 20px; 109 | } 110 | 111 | .navSignOut:hover, .navSignOut:focus, .navSignOut:active { 112 | background: radial-gradient(ellipse at left, gray, transparent), 113 | radial-gradient(ellipse at right, lightgray, transparent); 114 | border: none; 115 | outline: none; 116 | cursor: pointer; 117 | } 118 | 119 | .iconarea { 120 | display: flex; 121 | flex-direction: column; 122 | } 123 | .icon { 124 | color: white; 125 | margin-top: 25px; 126 | margin-left: 30px; 127 | font-size: 48px; 128 | background-color: transparent; 129 | position: fixed; 130 | } 131 | 132 | .brand:hover, .brand:focus, .brand:active { 133 | webkit-filter: blur(2px); 134 | filter: blur(2px); 135 | cursor: pointer; 136 | } 137 | 138 | .letter:hover, .letter:focus, .letter:active { 139 | webkit-filter: blur(4px); 140 | filter: blur(4px); 141 | cursor: pointer; 142 | } 143 | 144 | .brand { 145 | font-size: 40px; 146 | color: white; 147 | margin: auto 0; 148 | background: radial-gradient(ellipse at left, #1876f2, transparent), 149 | radial-gradient(ellipse at right, #74a4f2, transparent); 150 | border-radius: 50%; 151 | line-height: 40px; 152 | margin-left: 8px; 153 | width: 100px; 154 | height: 100px; 155 | align-self: center; 156 | justify-self: center; 157 | } 158 | 159 | .projectName { 160 | font-family: 'Baumans'; 161 | font-size: 50px; 162 | background: -webkit-linear-gradient(rgb(255, 255, 255), rgb(77, 77, 77)); 163 | -webkit-background-clip: text; 164 | -webkit-text-fill-color: transparent; 165 | } 166 | span{ 167 | margin: 0 15px; 168 | line-height: 40px; 169 | text-shadow: 0 0 2px rgba(0, 0, 0, .45); 170 | animation: span 4s ease-in infinite alternate; 171 | align-items: center; 172 | align-content: center; 173 | align-self: center; 174 | justify-items: center; 175 | justify-content: center; 176 | justify-self: center; 177 | text-align: center; 178 | // font-family: 'Baumans'; 179 | color: black; 180 | 181 | } 182 | .main{ 183 | display: flex; 184 | align-items: center; 185 | align-content: center; 186 | align-self: center; 187 | justify-items: center; 188 | justify-content: center; 189 | justify-self: center; 190 | vertical-align: middle; 191 | text-align: center; 192 | } 193 | .letter{ 194 | display: inline-flex; 195 | align-items: center; 196 | align-content: center; 197 | align-self: center; 198 | justify-items: center; 199 | justify-content: center; 200 | justify-self: center; 201 | vertical-align: middle; 202 | text-align: center; 203 | margin: 4px; 204 | margin-bottom: 8px; 205 | height: 30px; 206 | width: 27px; 207 | /* border: 2.5px solid #FF1EAD; */ 208 | border: 3px dotted; 209 | border-color: -webkit-linear-gradient(#eee, #333); 210 | border-radius: 14px; 211 | box-shadow: 212 | 0 0 2px rgba(0, 0, 0, .25), 213 | inset 0 0 2px rgba(0, 0, 0, .25); 214 | 215 | animation: letter 4s ease-in-out infinite alternate; 216 | webkit-filter: blur(2px); 217 | filter: blur(2px); 218 | } 219 | @keyframes span { 220 | 0%,30%{ margin: 0 2px; } 221 | 70%,100%{ margin: 0 2px; } 222 | } 223 | @keyframes letter { 224 | 0%,30%{ width: 27px; } 225 | 70%,100%{ width: 5vw; } 226 | } 227 | 228 | .myCol { 229 | display: flex; 230 | justify-content: center; 231 | } 232 | 233 | .card { 234 | border: 0px; 235 | display: flex; 236 | justify-content: space-between; 237 | } 238 | 239 | .card-body { 240 | display: flex; 241 | flex-direction: column; 242 | } 243 | 244 | .profilecard { 245 | width: 100%; 246 | justify-content: space-between; 247 | } 248 | 249 | .profile { 250 | display: flex; 251 | justify-content: space-between; 252 | justify-items: center; 253 | align-content: space-between; 254 | align-items: center; 255 | align-self: center; 256 | padding-top: 20px; 257 | padding-left: 20px; 258 | padding-bottom: 13px; 259 | border-radius: 25px; 260 | box-shadow: 1px 1px 2px 1px #ced0d4; 261 | background-color: rgb(250, 250, 250); 262 | font-family: 'Quicksand'; 263 | } 264 | 265 | .profile:hover { 266 | box-shadow: 2px 3px 6px #6d6d6d; 267 | cursor: pointer; 268 | } 269 | 270 | .profile img { 271 | border-radius: 22px;; 272 | box-shadow: 1px 1px 2px 1px #ced0d4; 273 | width: 200px; 274 | margin-left: 20px; 275 | margin-right: 10px; 276 | } 277 | 278 | .profile img:hover { 279 | box-shadow: 2px 3px 6px #6d6d6d; 280 | cursor: pointer; 281 | } 282 | 283 | .name:hover { 284 | font-weight: 700; 285 | color: #1876f2; 286 | cursor: pointer; 287 | } 288 | 289 | .staricon { 290 | line-height: 12px; 291 | height: 20px; 292 | display: inline-flex; 293 | margin: 0 3px; 294 | padding: 0; 295 | animation: starColor 5s infinite; 296 | } 297 | 298 | @keyframes starColor { 299 | from {color: rgb(230, 214, 40);} 300 | to {color: silver} 301 | } 302 | 303 | 304 | .staricon:hover { 305 | color: rgb(230, 214, 40); 306 | cursor: pointer; 307 | } 308 | 309 | .users { 310 | display: flex; 311 | flex-direction: column; 312 | min-height: 650px; 313 | margin-top: 33px; 314 | background-color: rgb(250, 250, 250); 315 | border-radius: 25px; 316 | box-shadow: 1px 1px 2px 1px #ced0d4; 317 | font-family: 'Quicksand'; 318 | height: 500px; 319 | } 320 | 321 | .users:hover { 322 | box-shadow: 2px 3px 6px #6d6d6d; 323 | cursor: pointer; 324 | } 325 | 326 | #usersTitle { 327 | padding: 0px; 328 | margin: 0px; 329 | } 330 | 331 | .profileText { 332 | font-size: 12px; 333 | } 334 | #dropdowntoggle { 335 | height: 40px; 336 | line-height: 1.5; 337 | font-size: 1rem; 338 | background: radial-gradient(ellipse at right, #1876f2, transparent), 339 | radial-gradient(ellipse at left, #74a4f2, transparent); 340 | border: 0.5px dotted rgb(203, 203, 203); 341 | color: white; 342 | } 343 | 344 | #dropdowntoggle:hover, #dropdowntoggle:focus, #dropdowntoggle:active { 345 | height: 40px; 346 | line-height: 1.5; 347 | font-size: 1rem; 348 | color: white; 349 | outline: none; 350 | background-color: transparent; 351 | border: 0.5px dotted rgb(203, 203, 203); 352 | } 353 | 354 | #dropdownbutton { 355 | height: 40px; 356 | line-height: 1.5; 357 | font-size: 1rem; 358 | background: radial-gradient(ellipse at right, #1876f2, transparent), 359 | radial-gradient(ellipse at left, #74a4f2, transparent); 360 | border: 0.5px dotted rgb(203, 203, 203); 361 | color: white; 362 | margin-bottom: 20px; 363 | } 364 | 365 | #dropdownbutton:hover { 366 | background-color: transparent; 367 | color: white; 368 | margin-bottom: 20px; 369 | outline: white; 370 | cursor: pointer; 371 | } 372 | 373 | #dropdownbutton:focus { 374 | background-color: transparent; 375 | color: white; 376 | margin-bottom: 20px; 377 | outline: white; 378 | cursor: pointer; 379 | } 380 | 381 | #dropdownbutton:active { 382 | background-color: transparent; 383 | color: white; 384 | margin-bottom: 20px; 385 | outline: white; 386 | cursor: pointer; 387 | } 388 | 389 | .cardContainer { 390 | display: flex; 391 | flex-direction: column; 392 | justify-content: center; 393 | align-items: center; 394 | border: 1px gainsboro solid; 395 | background: radial-gradient(ellipse at right, #1876f2, transparent), 396 | radial-gradient(ellipse at left, #74a4f2, transparent); 397 | border: 0.5px dotted rgb(203, 203, 203); 398 | color: white; 399 | width: 200px; 400 | height: 200px; 401 | border-radius: 50%; 402 | box-shadow: 2px 1px 2px 1px gainsboro; 403 | text-align: center; 404 | padding-top: 25px; 405 | margin-top: 40px; 406 | margin-right: 25px; 407 | font-family: 'Alata' 408 | } 409 | .cardContainer:hover { 410 | border: none; 411 | // outline: none; 412 | color: rgb(255, 255, 255); 413 | cursor: pointer; 414 | box-shadow: 2px 3px 6px #6d6d6d; 415 | } 416 | 417 | .cardContainer p { 418 | padding-top: 20px; 419 | } 420 | 421 | /* Event.jsx */ 422 | 423 | .event { 424 | border: 1px solid gainsboro; 425 | border-radius: 40px; 426 | margin: 1em; 427 | margin-left: 30px; 428 | padding: 1em; 429 | box-shadow: 1px 1px 1px 1px gainsboro; 430 | border-radius: 5px; 431 | background-color: rgb(250, 250, 250); 432 | } 433 | 434 | .jumbotron { 435 | // background-color: rgb(253, 238, 205); 436 | // border: 1px solid rgb(184, 184, 184); 437 | padding: 10px; 438 | border: none; 439 | box-shadow: 2px 2px 2px 2px gainsboro; 440 | // background-color: transparent; 441 | border-radius: 25px; 442 | margin-top: 10px; 443 | min-width: 100%; 444 | display: flex; 445 | flex-direction: column; 446 | justify-content: space-between; 447 | align-items: flex-end; 448 | height: 300px; 449 | } 450 | 451 | .jumbotron:hover { 452 | box-shadow: 2px 3px 6px #6d6d6d; 453 | cursor: pointer; 454 | } 455 | 456 | .eventJumbotron { 457 | text-align: center; 458 | font-family: 'Quicksand'; 459 | } 460 | 461 | .eventJumbotron h1 { 462 | font-family: 'Alata'; 463 | letter-spacing: 3px; 464 | font-size: 60px; 465 | } 466 | 467 | // .float-right { 468 | // margin: 10px; 469 | // margin-right: 15px; 470 | // } 471 | 472 | #submit { 473 | color: white; 474 | background-color: #5e9cff; 475 | width: 80px; 476 | border: none; 477 | border-radius: 10px; 478 | } 479 | 480 | #submit:hover, #submit:focus, #submit:active { 481 | background: radial-gradient(ellipse at right, #1876f2, transparent), 482 | radial-gradient(ellipse at left, #74a4f2, transparent); 483 | border: 0; 484 | outline: none; 485 | cursor: pointer; 486 | } 487 | 488 | #update { 489 | color: white; 490 | background-color: #a8b4c4; 491 | margin: 0; 492 | padding: 6px 9px; 493 | width: 100px; 494 | border: none; 495 | border-radius: 10px; 496 | } 497 | 498 | #update:hover { 499 | background: radial-gradient(ellipse at right, #8a9fba, transparent), 500 | radial-gradient(ellipse at left, #454b56, transparent); 501 | border: none; 502 | cursor: pointer; 503 | } 504 | 505 | #delete { 506 | color: rgb(181, 181, 181); 507 | // margin-top: 6px; 508 | margin: 0; 509 | padding: 5px 8px; 510 | width: 100px; 511 | background-color: white; 512 | border: none; 513 | border-radius: 10px; 514 | cursor: pointer; 515 | } 516 | 517 | #delete:hover { 518 | background: radial-gradient(ellipse at right, white), 519 | radial-gradient(ellipse at left, #dfdfdf); 520 | border: none; 521 | webkit-filter: blur(1px); 522 | filter: blur(1px); 523 | } 524 | 525 | 526 | /* EventAttendees.jsx */ 527 | 528 | .attendees { 529 | background: #ffffff; 530 | margin-top: 10px; 531 | padding-top: 20px; 532 | font-family: 'Alata'; 533 | box-shadow: 1px 1px 1px 1px gainsboro; 534 | border-radius: 25px; 535 | } 536 | 537 | .attendees:hover { 538 | box-shadow: 2px 3px 6px #6d6d6d; 539 | cursor: pointer; 540 | } 541 | 542 | .attendeelist { 543 | background: #ffffff; 544 | z-index: 10; 545 | margin-top: 10px; 546 | padding-top: 20px; 547 | font-family: 'Alata'; 548 | box-shadow: 1px 1px 1px 1px gainsboro; 549 | border-radius: 15px; 550 | width: 100%; 551 | } 552 | .circular { 553 | width: 85px; 554 | height: 85px; 555 | border-radius: 50%; 556 | position: relative; 557 | overflow: hidden; 558 | border: 2px solid rgb(204, 204, 204); 559 | box-shadow: 2px 3px 6px #cacaca; 560 | margin: 10px; 561 | } 562 | .circular img { 563 | // width: 50px; 564 | height: 85px; 565 | object-fit: contain; 566 | position: absolute; 567 | left: 50%; 568 | top: 50%; 569 | -webkit-transform: translate(-50%, -50%); 570 | -moz-transform: translate(-50%, -50%); 571 | -ms-transform: translate(-50%, -50%); 572 | transform: translate(-50%, -50%); 573 | } 574 | 575 | .circular:hover { 576 | box-shadow: 2px 3px 6px #6d6d6d; 577 | cursor: pointer; 578 | } 579 | 580 | 581 | .attendeesContainer h5 { 582 | text-align: center; 583 | } 584 | 585 | .attendees { 586 | display: flex; 587 | justify-content: center; 588 | margin-bottom: 20px; 589 | } 590 | 591 | .attendeeInfo { 592 | text-align: center; 593 | font-size: 10px; 594 | } 595 | 596 | .eventBottom { 597 | display: flex; 598 | justify-content: space-between; 599 | } 600 | .eventContent { 601 | background: #ffffff; 602 | // margin-top: 10px; 603 | // padding-top: 20px; 604 | font-family: 'Alata'; 605 | box-shadow: 1px 1px 1px 1px gainsboro; 606 | border-radius: 25px; 607 | display: flex; 608 | flex-direction: column; 609 | align-items: flex-start; 610 | width: 100%; 611 | padding-right: 15px; 612 | padding-left: 25px; 613 | margin-right: 25px; 614 | margin-bottom: 10px; 615 | // justify-content: center; 616 | // justify-items: center; 617 | align-content: center; 618 | // align-items: center; 619 | // rows so justify-content goes horizontal 620 | // justify-content: space-around; 621 | } 622 | 623 | .eventContent:hover { 624 | box-shadow: 2px 3px 6px #6d6d6d; 625 | cursor: pointer; 626 | } 627 | 628 | .eventMessages { 629 | display: flex; 630 | flex-direction: column; 631 | width: 65%; 632 | font-family: 'Alata'; 633 | box-shadow: 1px 1px 1px 1px gainsboro; 634 | border-radius: 25px; 635 | } 636 | h4 { 637 | font-family: 'Alata'; 638 | } 639 | .mediaContainer { 640 | width: 60%; 641 | height: 100%; 642 | display: flex; 643 | flex-direction: column; 644 | background: white; 645 | font-family: 'Alata'; 646 | box-shadow: 1px 1px 1px 1px gainsboro; 647 | border-radius: 25px; 648 | margin-bottom: 13px; 649 | } 650 | 651 | .mediaContainer:hover { 652 | box-shadow: 2px 3px 6px #6d6d6d; 653 | cursor: pointer; 654 | } 655 | 656 | .mediaTitleContainer { 657 | display: flex; 658 | justify-content: center; 659 | align-content: center; 660 | } 661 | 662 | .mediaTitle { 663 | padding-top: 15px; 664 | } 665 | 666 | .mediaContent { 667 | width: 80%; 668 | height: 80%; 669 | border-radius: 20px; 670 | background-color: #f0f2f5; 671 | display: flex; 672 | // display: grid; 673 | // grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); 674 | // grid-auto-rows: 1fr; 675 | // column-gap: 5px; 676 | // row-gap: 5px; 677 | // grid-template-areas: 678 | // " currentPhoto photo photo photo photo " 679 | // " photo photo photo photo photo " 680 | // " photo photo photo photo photo " 681 | // " photo photo photo photo photo " 682 | // ; 683 | border: none; 684 | padding: 0; 685 | margin: 0; 686 | } 687 | 688 | .mediaContent::before { 689 | content: ''; 690 | width: 0; 691 | padding-bottom: 100%; 692 | grid-row: 1 / 1; 693 | grid-column: 1 / 1; 694 | } 695 | 696 | .mediaContent > *:first-child { 697 | grid-row: 1 / 1; 698 | grid-column: 1 / 1; 699 | } 700 | 701 | .mediaContent > * { 702 | background: rgba(0,0,0,0.1); 703 | border: 1px white solid; 704 | } 705 | 706 | .photo img { 707 | object-fit: contain; 708 | grid-area: photo; 709 | background-color: blue; 710 | } 711 | 712 | #currentPhoto { 713 | width: 100px; 714 | height: 100px; 715 | border-radius: 50%; 716 | position: relative; 717 | overflow: hidden; 718 | border: 2px solid rgb(204, 204, 204); 719 | box-shadow: 2px 3px 6px #cacaca; 720 | margin: 10px; 721 | object-fit: fill; 722 | // transform: scaleY(-1); 723 | } 724 | 725 | #currentPhoto:hover { 726 | box-shadow: 2px 3px 6px #6d6d6d; 727 | cursor: pointer; 728 | } 729 | 730 | .fileloadContainer { 731 | display: flex; 732 | flex-direction: column-reverse; 733 | align-items: flex-start; 734 | width: 100%; 735 | padding: 0; 736 | justify-content: center; 737 | justify-items: center; 738 | align-content: center; 739 | align-items: center; 740 | // rows so justify-content goes horizontal 741 | // justify-content: space-around; 742 | } 743 | 744 | .file-upload { 745 | width: 100%; 746 | font-family: 'Nunito'; 747 | font-size: 14px; 748 | display: flex; 749 | margin: 10px; 750 | padding: 0px; 751 | padding-left: 3rem; 752 | display: flex; 753 | flex-direction: column; 754 | justify-content: center; 755 | justify-items: center; 756 | align-content: center; 757 | align-items: flex-start; 758 | width: 100%; 759 | } 760 | 761 | 762 | // choose file button 763 | .formInput { 764 | margin: 0px; 765 | margin-bottom: 5px; 766 | } 767 | 768 | .upbutton{ 769 | color: white; 770 | background-color: #b1b5bc;; 771 | margin-top: 5px; 772 | padding: 4px 1px; 773 | width: 70px; 774 | border: none; 775 | border-radius: 3px; 776 | cursor: pointer; 777 | } 778 | .upbutton:hover, .upbutton:focus, .upbutton:active { 779 | background: radial-gradient(ellipse at right, #a0aab7, transparent), 780 | radial-gradient(ellipse at left, #d9dfea, transparent); 781 | border: none; 782 | outline: none; 783 | cursor: pointer; 784 | } 785 | 786 | 787 | #fileButton { 788 | font-family: 'Nunito'; 789 | margin-bottom: 5px; 790 | margin-top: 5px; 791 | padding: 4px 1px; 792 | border: none; 793 | border-radius: 5px; 794 | cursor: pointer; 795 | } 796 | 797 | .progessBar{ 798 | display: flex; 799 | border-radius: 15px; 800 | justify-content: center; 801 | align-items: center; 802 | font-family: 'Alata'; 803 | margin: 8px 0px; 804 | height: 1rem; 805 | max-width: 80%; 806 | background: linear-gradient( 807 | to right, 808 | #d2d2d2 5%, 809 | #9f9f9f 15%, 810 | #595959 25%, 811 | #d0d0d0 35%, 812 | #a7a7a7 45%, 813 | #484848 55%, 814 | #747474 65%, 815 | #3a3a3a 75%, 816 | #6a6a6a 85%, 817 | #1a1a1a 95%); 818 | color: white; 819 | padding: 2px; 820 | } 821 | 822 | #upload { 823 | color: white; 824 | background-color: #c9d0dc; 825 | padding: 6px 9px; 826 | width: 125px; 827 | border: none; 828 | border-radius: 10px; 829 | margin-bottom: 10px; 830 | padding: 10px; 831 | display: inline-block; 832 | } 833 | 834 | #upload:hover { 835 | background: radial-gradient(ellipse at right, #97a0ac, transparent), 836 | radial-gradient(ellipse at left, #74a4f2, transparent); 837 | border: none; 838 | cursor: pointer; 839 | } 840 | 841 | /* SearchEvent.jsx */ 842 | 843 | .searchResults { 844 | display: grid; 845 | } 846 | 847 | .searchResult { 848 | margin: 0.3em; 849 | } 850 | 851 | /* Content.jsx */ 852 | #exampleForm.ControlTextarea1 { 853 | width: 650px; 854 | } 855 | .modal-body, .modal-dialog { 856 | min-width: 675px; 857 | } 858 | .form-control { 859 | border-radius: 15px; 860 | // min-width: 90%; 861 | min-width: 635px; 862 | text-decoration: none; 863 | font-size: 12px; 864 | color: #e1e1e1; 865 | } 866 | 867 | .form-control:hover, .form-control:focus, .form-control:active { 868 | outline: none; 869 | border: none; 870 | box-shadow: 2px 3px 6px #cacaca; 871 | } 872 | 873 | .userMessage { 874 | width: 50px; 875 | height: 50px; 876 | border-radius: 50%; 877 | position: relative; 878 | overflow: hidden; 879 | border: 1px solid rgb(204, 204, 204); 880 | box-shadow: 2px 3px 6px #cacaca; 881 | margin: 10px; 882 | } 883 | 884 | .userMessage img { 885 | height: 50px; 886 | object-fit: contain; 887 | position: absolute; 888 | left: 50%; 889 | top: 50%; 890 | -webkit-transform: translate(-50%, -50%); 891 | -moz-transform: translate(-50%, -50%); 892 | -ms-transform: translate(-50%, -50%); 893 | transform: translate(-50%, -50%); 894 | } 895 | .messageBox { 896 | display: flex; 897 | justify-content: left; 898 | } 899 | 900 | .messages p { 901 | font-size: 12px; 902 | padding: 0; 903 | margin: 0; 904 | } 905 | .messageName { 906 | font-weight: bold; 907 | } 908 | 909 | .message { 910 | padding: 10px; 911 | } 912 | 913 | 914 | /* rainbow divider */ 915 | 916 | $bg: white; 917 | $barsize: 15px; 918 | 919 | .hr { 920 | width: 80%; 921 | margin: 0 auto; 922 | height: 1px; 923 | display: block; 924 | position: relative; 925 | margin-bottom: 0em; 926 | padding: 2em 0; 927 | 928 | &:after, 929 | &:before { 930 | content: ""; 931 | position: absolute; 932 | width: 100%; 933 | height: 1px; 934 | bottom: 50%; 935 | left: 0; 936 | } 937 | 938 | &:before { 939 | 940 | background: linear-gradient( 90deg, $bg 0%, $bg 50%, transparent 50%, transparent 100% ); 941 | background-size: $barsize; 942 | background-position: center; 943 | z-index: 1; 944 | 945 | } 946 | 947 | &:after { 948 | transition: opacity 0.3s ease, animation 0.3s ease; 949 | background: linear-gradient( 950 | to right, 951 | #d2d2d2 5%, 952 | #9f9f9f 15%, 953 | #595959 25%, 954 | #d0d0d0 35%, 955 | #a7a7a7 45%, 956 | #484848 55%, 957 | #747474 65%, 958 | #3a3a3a 75%, 959 | #6a6a6a 85%, 960 | #1a1a1a 95%); 961 | background-size: 200%; 962 | background-position: 0%; 963 | animation: bar 15s linear infinite; 964 | } 965 | 966 | @keyframes bar { 967 | 0% { background-position: 0%; } 968 | 100% { background-position: 200%; } 969 | } 970 | } 971 | 972 | .hr.anim { 973 | &:before { 974 | background: linear-gradient( 975 | 90deg, 976 | $bg 0%, $bg 5%, 977 | transparent 5%, transparent 10%, 978 | $bg 10%, $bg 15%, 979 | transparent 15%, transparent 20%, 980 | $bg 20%, $bg 25%, 981 | transparent 25%, transparent 30%, 982 | $bg 30%, $bg 35%, 983 | transparent 35%, transparent 40%, 984 | $bg 40%, $bg 45%, 985 | transparent 45%, transparent 50%, 986 | $bg 50%, $bg 55%, 987 | transparent 55%, transparent 60%, 988 | $bg 60%, $bg 65%, 989 | transparent 65%, transparent 70%, 990 | $bg 70%, $bg 75%, 991 | transparent 75%, transparent 80%, 992 | $bg 80%, $bg 85%, 993 | transparent 85%, transparent 90%, 994 | $bg 90%, $bg 95%, 995 | transparent 95%, transparent 100% ); 996 | 997 | background-size: $barsize * 10; 998 | background-position: center; 999 | z-index: 1; 1000 | 1001 | animation: bar 120s linear infinite; 1002 | 1003 | } 1004 | 1005 | &:hover { 1006 | &:before { 1007 | animation-duration: 20s; 1008 | } 1009 | &:after { 1010 | animation-duration: 2s; 1011 | } 1012 | } 1013 | } 1014 | 1015 | #formEventTitle { 1016 | max-width: 95% 1017 | } 1018 | 1019 | #formEventDescription { 1020 | width: 90%; 1021 | } 1022 | 1023 | .btn-primary { 1024 | border-radius: 15px; 1025 | 1026 | 1027 | } 1028 | 1029 | .message-list { 1030 | margin-top: 10px; 1031 | font-size: 14px; 1032 | } -------------------------------------------------------------------------------- /iteration-project_postgres_create.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users 2 | ( 3 | "userid" serial PRIMARY KEY, 4 | "username" varchar NOT NULL CHECK ( username <> ''), 5 | "firstname" varchar NOT NULL CHECK ( firstname <> ''), 6 | "lastname" varchar NOT NULL CHECK ( lastname <> ''), 7 | "profilephoto" varchar NOT NULL, 8 | UNIQUE ( username ) 9 | ); 10 | 11 | SELECT setval('users_userid_seq', 1, false); 12 | -- SELECT setval('users_userid_seq', max(userid)) 13 | -- FROM users; 14 | 15 | CREATE TABLE events 16 | ( 17 | "eventid" SERIAL PRIMARY KEY, 18 | "eventtitle" varchar NOT NULL CHECK ( eventtitle <> ''), 19 | "eventdate" date NOT NULL, 20 | "eventstarttime" time NOT NULL, 21 | "eventendtime" time NOT NULL, 22 | -- EVENTLOCATION INCLUDES INGREDIENTS 23 | "eventlocation" varchar NOT NULL CHECK ( eventlocation <> ''), 24 | "eventdetails" varchar NOT NULL CHECK ( eventdetails <> ''), 25 | "eventownerid" bigint NOT NULL, 26 | "eventownerusername" varchar NOT NULL, 27 | -- "eventattendees" varchar ARRAY, 28 | "eventmessages" varchar 29 | ARRAY, 30 | "eventtype" varchar NOT NULL, 31 | UNIQUE 32 | ( eventtitle ), 33 | FOREIGN KEY 34 | (eventownerid) REFERENCES users 35 | (userid), 36 | FOREIGN KEY 37 | (eventownerusername) REFERENCES users 38 | (username) 39 | ); 40 | SELECT setval('events_eventid_seq', 1, false); 41 | -- SELECT setval('events_eventid_seq', max(eventid)) 42 | -- FROM events; 43 | 44 | 45 | CREATE TABLE usersandevents 46 | ( 47 | "uselessid" serial PRIMARY KEY, 48 | "userid" bigint NOT NULL, 49 | "username" varchar NOT NULL, 50 | "eventid" bigint NOT NULL, 51 | "eventtitle" varchar NOT NULL, 52 | "eventdate" varchar NOT NULL, 53 | "eventstarttime" varchar NOT NULL, 54 | "eventendtime" varchar NOT NULL, 55 | "eventdetails" varchar NOT NULL, 56 | "eventlocation" varchar NOT NULL, 57 | UNIQUE (username, eventtitle), 58 | FOREIGN KEY ( userid ) REFERENCES users ( userid ), 59 | FOREIGN KEY ( username ) REFERENCES users( username ), 60 | FOREIGN KEY ( eventid ) REFERENCES events ( eventid ), 61 | FOREIGN KEY ( eventtitle ) REFERENCES events ( eventtitle ) ON UPDATE CASCADE 62 | ); 63 | SELECT setval('usersandevents_uselessid_seq', 1, false); 64 | 65 | 66 | CREATE TABLE eventsandmessages 67 | ( 68 | "uselessid" serial PRIMARY KEY, 69 | "userid" bigint NOT NULL, 70 | "username" varchar NOT NULL, 71 | "eventid" bigint NOT NULL, 72 | "eventtitle" varchar NOT NULL, 73 | "messagetext" varchar, 74 | "messagedate" date NOT NULL, 75 | "messagetime" time NOT NULL, 76 | FOREIGN KEY ( userid ) REFERENCES users ( userid ), 77 | FOREIGN KEY ( username ) REFERENCES users ( username ), 78 | FOREIGN KEY ( eventid ) REFERENCES events ( eventid ), 79 | FOREIGN KEY ( eventtitle ) REFERENCES events ( eventtitle ) ON UPDATE CASCADE 80 | ); 81 | SELECT setval('eventsandmessages_uselessid_seq', 1, false); 82 | 83 | -- CREATE TABLE recipes 84 | -- ( 85 | -- "uselessid" serial PRIMARY KEY, 86 | -- "recipename" varchar NOT NULL, 87 | -- "recipeid" bigint NOT NULL, 88 | -- "recipeimage" varchar, 89 | -- UNIQUE (recipeid) 90 | -- ); 91 | -- SELECT setval('recipes_uselessid_seq', 1, false); 92 | 93 | 94 | -- CREATE TABLE ingredients 95 | -- ( 96 | -- "uselessid" serial PRIMARY KEY, 97 | -- "ingredientname" varchar NOT NULL, 98 | -- "ingredientid" bigint NOT NULL, 99 | -- "ingredientimage" varchar, 100 | -- "recipeid" bigint NOT NULL, 101 | -- UNIQUE (ingredientid), 102 | -- FOREIGN KEY ( recipeid ) REFERENCES recipes (recipeid) 103 | -- ); 104 | -- SELECT setval('ingredients_uselessid_seq', 1, false); 105 | 106 | 107 | -- CREATE TABLE recipesandingredients 108 | -- ( 109 | -- "uselessid" serial PRIMARY KEY, 110 | -- "recipeid" bigint NOT NULL, 111 | -- "recipename" varchar NOT NULL, 112 | -- "recipeimage" varchar, 113 | -- "ingredientid" bigint NOT NULL, 114 | -- "ingredientname" varchar NOT NULL, 115 | -- "ingredientimage" varchar, 116 | -- UNIQUE (recipeid, ingredientid), 117 | -- FOREIGN KEY ( recipeid ) REFERENCES recipes ( recipeid ), 118 | -- FOREIGN KEY ( ingredientid ) REFERENCES ingredients ( ingredientid ), 119 | -- ); 120 | -- SELECT setval('recipesandingredients_uselessid_seq', 1, false); 121 | 122 | -- DROP TABLE EVENTSANDMESSAGES; 123 | -- DROP TABLE USERSANDEVENTS; 124 | -- DROP TABLE EVENTS; 125 | -- DROP TABLE USERS; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scratch-project", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "NODE_ENV=production node server/server.js", 8 | "build": "NODE_ENV=production webpack", 9 | "dev-win": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open ./\" \"nodemon ./server/server.js\"", 10 | "dev-mac": "NODE_ENV=development webpack-dev-server --open --hot & nodemon server/server.js", 11 | "test": "jest --verbose" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Tasseled-Wobbegong/Scratch-project.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/Tasseled-Wobbegong/Scratch-project/issues" 21 | }, 22 | "homepage": "https://github.com/Tasseled-Wobbegong/Scratch-project#readme", 23 | "devDependencies": { 24 | "@babel/core": "^7.11.1", 25 | "@babel/preset-env": "^7.11.0", 26 | "@babel/preset-react": "^7.10.4", 27 | "babel-loader": "^8.1.0", 28 | "concurrently": "^5.3.0", 29 | "cross-env": "^7.0.2", 30 | "css-loader": "^4.2.1", 31 | "enzyme": "^3.11.0", 32 | "enzyme-adapter-react-16": "^1.15.3", 33 | "file-loader": "^6.0.0", 34 | "file-system": "^2.2.2", 35 | "html-webpack-plugin": "^4.3.0", 36 | "jest": "^26.4.0", 37 | "node-sass": "^4.14.1", 38 | "nodemon": "^2.0.4", 39 | "sass": "^1.26.10", 40 | "sass-loader": "^9.0.3", 41 | "style-loader": "^1.2.1", 42 | "url-loader": "^4.1.0", 43 | "webpack": "^4.44.1", 44 | "webpack-cli": "^3.3.12", 45 | "webpack-dev-server": "^3.11.0", 46 | "webpack-hot-middleware": "^2.25.0" 47 | }, 48 | "dependencies": { 49 | "@fortawesome/fontawesome-svg-core": "^1.2.30", 50 | "@fortawesome/free-brands-svg-icons": "^5.14.0", 51 | "@fortawesome/free-regular-svg-icons": "^5.14.0", 52 | "@fortawesome/free-solid-svg-icons": "^5.14.0", 53 | "@fortawesome/react-fontawesome": "^0.1.11", 54 | "axios": "^0.19.2", 55 | "bcrypt": "^5.0.0", 56 | "bcryptjs": "^2.4.3", 57 | "bootstrap": "^4.5.2", 58 | "cookie-parser": "^1.4.5", 59 | "cors": "^2.8.5", 60 | "dotenv": "^8.2.0", 61 | "express": "^4.17.1", 62 | "express-fileupload": "^1.2.0", 63 | "fb": "^2.0.0", 64 | "formidable": "^1.2.2", 65 | "googleapis": "^59.0.0", 66 | "jsonwebtoken": "^8.5.1", 67 | "jwt-decode": "^2.2.0", 68 | "mini-css-extract-plugin": "^0.10.0", 69 | "multer": "^1.4.2", 70 | "node-fetch": "^2.6.0", 71 | "pg": "^8.3.0", 72 | "react": "^16.13.1", 73 | "react-bootstrap": "^1.3.0", 74 | "react-datetime-picker": "^3.0.2", 75 | "react-dom": "^16.13.1", 76 | "react-hot-loader": "^4.12.21", 77 | "react-router": "^5.2.0", 78 | "react-router-dom": "^5.2.0", 79 | "supertest": "^4.0.2" 80 | }, 81 | "proxy": "http://localhost:8080" 82 | } 83 | -------------------------------------------------------------------------------- /server/controllers/cookieController.js: -------------------------------------------------------------------------------- 1 | const cookieController = {}; 2 | 3 | cookieController.setGoogleCookie = (req, res, next) => { 4 | res.cookie('provider', 'google', { httpOnly: true }) 5 | res.cookie('user', res.locals.token, { httpOnly: true }); 6 | return next(); 7 | }; 8 | 9 | cookieController.setFacebookCookie = (req, res, next) => { 10 | res.cookie('provider', 'facebook', { httpOnly: true }) 11 | res.cookie('user', res.locals.token, { httpOnly: true }); 12 | return next(); 13 | }; 14 | 15 | cookieController.isLoggedIn = (req, res, next) => { 16 | if (req.cookies.user) { 17 | return next(); 18 | } else { 19 | return next({ 20 | log: `User is not logged in`, 21 | code: 401, 22 | message: { err: "User is not logged in." }, 23 | }); 24 | } 25 | }; 26 | 27 | cookieController.removeCookie = (req, res, next) => { 28 | res.clearCookie('provider'); 29 | res.clearCookie('user'); 30 | return next(); 31 | } 32 | 33 | module.exports = cookieController; 34 | -------------------------------------------------------------------------------- /server/controllers/eventController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models"); 2 | const queries = require("../utils/queries"); 3 | const fetch = require("node-fetch") 4 | const eventController = {}; 5 | 6 | // DELETE EVENT 7 | eventController.deleteEvent = (req, res, next) => { 8 | const eventid = req.query.eventid 9 | const queryString = ` 10 | DELETE FROM usersandevents WHERE eventid=${eventid}; 11 | DELETE FROM eventsandmessages WHERE eventid=${eventid}; 12 | DELETE FROM events WHERE eventid = ${eventid}; 13 | ` 14 | db.query(queryString) 15 | .catch(err => { 16 | return next({ 17 | log: `Error occurred with queries.deleteEvent OR eventController.deleteEvent middleware: ${err}`, 18 | message: { err: "An error occured with SQL when retrieving events information." }, 19 | }); 20 | }) 21 | next(); 22 | } 23 | 24 | // UPDATE EVENT 25 | eventController.updateEvent = (req, res, next) => { 26 | let { eventid, eventtitle, eventlocation, eventdate, eventstarttime, eventdetails } = req.body; 27 | const queryString = ` 28 | UPDATE events 29 | SET eventtitle = '${eventtitle}', 30 | eventlocation = '${eventlocation}', 31 | eventdetails = '${eventdetails}', 32 | eventstarttime = '${eventstarttime}', 33 | eventdate = '${eventdate}' 34 | WHERE eventid = ${eventid} 35 | RETURNING *; 36 | 37 | UPDATE usersandevents 38 | SET eventlocation = '${eventlocation}', 39 | eventdetails = '${eventdetails}', 40 | eventstarttime = '${eventstarttime}', 41 | eventdate = '${eventdate}' 42 | WHERE eventid = ${eventid}; 43 | ` 44 | 45 | 46 | // const queryValues = [eventtitle, eventdate, eventstarttime, eventstarttime, eventlocation, eventdetails]; 47 | db.query(queryString) 48 | .then(response => { 49 | res.locals.updateduser = response[0].rows; 50 | return next(); 51 | }) 52 | .catch(err => { 53 | console.log('I AM THE ERRORR: ', err); 54 | return next({ 55 | log: `Error occurred with queries.updateEvent OR eventController.updateEvent middleware: ${err}`, 56 | message: { err: "An error occured with SQL when retrieving events information." }, 57 | }); 58 | }); 59 | } 60 | 61 | 62 | 63 | eventController.getFullEvents = (req, res, next) => { 64 | 65 | const queryString = queries.userEvents; 66 | const queryValues = [res.locals.allUserInfo.userid]; //user will have to be verified Jen / Minchan 67 | db.query(queryString, queryValues) 68 | .then(data => { 69 | if (!data.rows[0]) { 70 | res.locals.allEventsInfo = []; 71 | } else { 72 | res.locals.allEventsInfo = data.rows; 73 | } 74 | return next(); 75 | }) 76 | .catch(err => { 77 | return next({ 78 | log: `Error occurred with queries.userEvents OR eventController.getFullEvents middleware: ${err}`, 79 | message: { err: "An error occured with SQL when retrieving events information." }, 80 | }); 81 | }) 82 | }; 83 | 84 | eventController.getAllAttendees = async (req, res, next) => { 85 | const allEvents = res.locals.allEventsInfo; // ALL EVENTS FOR THAT USER 86 | const arrayOfEventTitles = []; // ['marc birthday', 'minchan birthday' ... ] 87 | for (const event of allEvents) { 88 | arrayOfEventTitles.push(event.eventtitle); 89 | } 90 | 91 | res.locals.attendees = []; 92 | const queryString = queries.selectEventAttendees; 93 | 94 | const promises = []; 95 | 96 | for (let i = 0; i < arrayOfEventTitles.length; i++) { 97 | const result = new Promise((resolve, reject) => { 98 | try { 99 | const queryResult = db.query(queryString, [arrayOfEventTitles[i]]); 100 | return resolve(queryResult) 101 | } catch (err) { 102 | return reject(err); 103 | } 104 | }) 105 | promises.push(result); 106 | } 107 | 108 | const resolvedPromises = Promise.all(promises) 109 | .then(data => { 110 | for (let i = 0; i < data.length; i++) { 111 | const container = []; 112 | data[i].rows.forEach(obj => { 113 | container.push(obj.username); 114 | }) 115 | res.locals.attendees.push(container); 116 | } 117 | return next(); 118 | }) 119 | .catch(err => console.log('promise.all err: ', err)); 120 | 121 | // const resolvedPromises = Promise.all(promises) 122 | // .then(data => { 123 | // for(let i = 0; i < data.length; i++ ) { 124 | // console.log('data: ', data[i].rows); 125 | // res.locals.attendees.push(data[i].rows); 126 | // console.log('RES.LOCALS.ATTENDEES: ', res.locals.attendees); 127 | // } 128 | // return next(); 129 | // }) 130 | // .catch(err => console.log('promise.all err: ', err)); 131 | } 132 | 133 | eventController.createEvent = (req, res, next) => { 134 | const { userid, username } = res.locals.allUserInfo; 135 | 136 | // const queryValues = [ eventtitle, eventdate, eventstarttime, eventendtime, eventlocation, eventdetails, userid, username, {} ]; 137 | // const queryValues = ['minchan birthday', '9/15/2020', '06:00 PM', '09:00 PM', 'golf course', 'play minigolf birthday', userid, username, "{'hey when is it again', 'happy birthday!', 'sorry can\'t make it'}"] 138 | // db.query(queryString, queryValues) 139 | // .then(data => { 140 | let { eventid, eventtitle, eventlocation, eventdate, eventstarttime, eventdetails, eventtype } = req.body; 141 | // console.log('eventController.createEvent: eventtype: ', eventtype); 142 | if (eventtype == "cooking") return next(); 143 | if (eventtype == "calendar") { 144 | // console.log('eventController.createEvent ', req.body); 145 | const queryString = queries.createEvent; 146 | const queryValues = [eventtitle, eventdate, eventstarttime, eventstarttime, eventlocation, eventdetails, userid, username, "{}", eventtype]; 147 | db.query(queryString, queryValues) 148 | .then(data => { 149 | // console.log('>>> eventController.createEvent DATA ', data); 150 | // data.rows: [ { eventid: 11 } ], 151 | res.locals.eventTitle = data.rows[0]; 152 | return next(); 153 | }) 154 | .catch(err => { 155 | // console.log('>>> eventController.createEvent ERR ', err); 156 | return next({ 157 | log: `Error occurred with queries.createEvent OR eventController.createEvent middleware: ${err}`, 158 | message: { err: "An error occured with SQL when creating event." }, 159 | }); 160 | }) 161 | } 162 | }; 163 | 164 | // ==================== CREATE NEW COOKING EVENT 165 | // ==================== CREATE NEW COOKING EVENT 166 | // ==================== CREATE NEW COOKING EVENT 167 | 168 | /** 169 | [{ 170 | "id": 143419, 171 | "title": ""Barbecued" Tofu", 172 | "image": "https://spoonacular.com/recipeImages/143419-312x231.png", 173 | "imageType": "png", 174 | "usedIngredientCount": 2, 175 | "missedIngredientCount": 2, 176 | "missedIngredients": [ 177 | { 178 | "id": 6150, 179 | "amount": 0.5, 180 | "unit": "cup", 181 | "unitLong": "cups", 182 | "unitShort": "cup", 183 | "aisle": "Condiments", 184 | "name": "barbecue sauce", 185 | "original": "1/2 cup barbecue sauce", 186 | "originalString": "1/2 cup barbecue sauce", 187 | "originalName": "barbecue sauce", 188 | "metaInformation": [], 189 | "meta": [], 190 | "image": "https://spoonacular.com/cdn/ingredients_100x100/barbecue-sauce.jpg" 191 | }, 192 | { 193 | "id": 2009, 194 | "amount": 1.0, 195 | "unit": "tablespoon", 196 | "unitLong": "tablespoon", 197 | "unitShort": "Tbsp", 198 | "aisle": "Spices and Seasonings", 199 | "name": "chile powder", 200 | "original": "1 tablespoon pure chile powder", 201 | "originalString": "1 tablespoon pure chile powder", 202 | "originalName": "pure chile powder", 203 | "metaInformation": [ 204 | "pure" 205 | ], 206 | "meta": [ 207 | "pure" 208 | ], 209 | "image": "https://spoonacular.com/cdn/ingredients_100x100/chili-powder.jpg" 210 | } 211 | ], 212 | "usedIngredients": [ 213 | { 214 | "id": 98940, 215 | "amount": 4.0, 216 | "unit": "", 217 | "unitLong": "", 218 | "unitShort": "", 219 | "aisle": "Bakery/Bread", 220 | "name": "sub rolls", 221 | "original": "4 rolls, split (optional)", 222 | "originalString": "4 rolls, split (optional)", 223 | "originalName": "rolls, split (optional)", 224 | "metaInformation": [ 225 | "split" 226 | ], 227 | "meta": [ 228 | "split" 229 | ], 230 | "extendedName": "split sub rolls", 231 | "image": "https://spoonacular.com/cdn/ingredients_100x100/french-rolls.jpg" 232 | }, 233 | { 234 | "id": 16213, 235 | "amount": 14.0, 236 | "unit": "ounces", 237 | "unitLong": "ounces", 238 | "unitShort": "oz", 239 | "aisle": "Refrigerated;Produce;Ethnic Foods", 240 | "name": "tofu", 241 | "original": "1 package (14 ounces) regular tofu, firm or extra-firm, drained", 242 | "originalString": "1 package (14 ounces) regular tofu, firm or extra-firm, drained", 243 | "originalName": "package regular tofu, firm or extra-firm, drained", 244 | "metaInformation": [ 245 | "firm", 246 | "drained" 247 | ], 248 | "meta": [ 249 | "firm", 250 | "drained" 251 | ], 252 | "image": "https://spoonacular.com/cdn/ingredients_100x100/tofu.png" 253 | } 254 | ], 255 | "unusedIngredients": [ 256 | { 257 | "id": 18064, 258 | "amount": 1.0, 259 | "unit": "serving", 260 | "unitLong": "serving", 261 | "unitShort": "serving", 262 | "aisle": "Bakery/Bread", 263 | "name": "bread", 264 | "original": "bread", 265 | "originalString": "bread", 266 | "originalName": "bread", 267 | "metaInformation": [], 268 | "meta": [], 269 | "image": "https://spoonacular.com/cdn/ingredients_100x100/white-bread.jpg" 270 | }, 271 | { 272 | "id": 9266, 273 | "amount": 1.0, 274 | "unit": "serving", 275 | "unitLong": "serving", 276 | "unitShort": "serving", 277 | "aisle": "Produce", 278 | "name": "pineapples", 279 | "original": "pineapples", 280 | "originalString": "pineapples", 281 | "originalName": "pineapples", 282 | "metaInformation": [], 283 | "meta": [], 284 | "image": "https://spoonacular.com/cdn/ingredients_100x100/pineapple.jpg" 285 | } 286 | ], 287 | "likes": 11 288 | }] 289 | */ 290 | 291 | // ==================== RECIPE API START ==================== 292 | 293 | 294 | /*query search to postgresQL database to get the ingredients for each user. 295 | Needed to use the req.cookie.user_id in order to determine which user is currently on the site */ 296 | // const queryString4 = `SELECT name FROM ingredients WHERE user_id=${req.cookies.user_id}`; 297 | 298 | // await db.query(queryString4).then((data) => { 299 | 300 | // //queryString is created to insert in to the api request below called get recipes. Ingredients come back as an array. 301 | // //example --> ['bread', 'chicken', 'cheese', 'lettuce'] 302 | // //queryString example --> 'bread,+chicken,+cheese,+lettuce' 303 | // let ingredientString = ""; 304 | // for (let i = 0; i < data.rows.length; i++) { 305 | // ingredientString = ingredientString + ",+" + data.rows[i].name; 306 | // } 307 | 308 | // //API request string labeled getRecipes used below in Fetch to get information from spoonacular API. 309 | // //Always need an API key at the end query (getRecipes) You'll have to request one from the main request. 310 | // /*Important!!!!!!!!: Only 150 request can be made per day to the API*/ 311 | // let getRecipes = `https://api.spoonacular.com/recipes/findByIngredients?ingredients=${ingredientString}&apiKey=`; 312 | 313 | // //FETCH 314 | // fetch(getRecipes, { 315 | // headers: { 316 | // "Content-Type": "application/json", 317 | // }, 318 | // credentials: "include", 319 | // }) 320 | // .then((response) => response.json()) 321 | // .then((data) => { 322 | // console.log("result dataaa", data); 323 | // res.locals.data = data; 324 | // return next(); 325 | // }) 326 | // .catch((err) => console.log(err)); 327 | // }); 328 | 329 | 330 | // ==================== RECIPE API END ==================== 331 | 332 | eventController.createCooking = async (req, res, next) => { 333 | // console.log('eventController.createCooking: req.body: ', req.body); 334 | // console.log('eventController.createCooking: req.query: ', req.query); 335 | const { eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventlocation, eventdetails, eventtype } = req.body; 336 | // console.log('eventController.createCooking: eventtitle ', eventtitle); 337 | // console.log('eventController.createCooking: eventtype: ', eventtype); 338 | // console.log('eventController.createCooking: ingredient: ', eventlocation); 339 | // console.log('eventController.createCooking: eventdate: ', eventdate); 340 | // console.log('eventController.createCooking: eventstarttime: ', eventstarttime); 341 | // console.log('eventController.createCooking: eventdetails: ', eventdetails); 342 | 343 | res.locals.eventTitle = {}; 344 | res.locals.eventTitle.eventtitle = eventtitle; 345 | 346 | if (eventtype == "calendar") return next(); 347 | if (eventtype == "cooking") { 348 | const ingredient = eventlocation.trim(); 349 | const apiKey = 'a65230a9135c40f4a3d7a5c7b6685cc6'; // Bon-Jay's 350 | // const apiKey = '4335e4647b4f4cc1b7a027fd1d3b1975'; // Qwen's 351 | const { userid, username } = res.locals.allUserInfo; 352 | try { 353 | let recipeURL = `https://api.spoonacular.com/recipes/findByIngredients?ingredients=${ingredient}&apiKey=${apiKey}`; 354 | await fetch(recipeURL, { 355 | headers: { 356 | "Content-Type": "application/json", 357 | }, 358 | credentials: "include", 359 | }) 360 | .then((data) => data.json()) 361 | .then((data) => { 362 | console.log("result data.length", data.length); 363 | if (data.length === 0) return res.send('NOT A VALID INGREDIENT') 364 | console.log("result data", data); 365 | const num = Math.floor(Math.random() * data.length); 366 | console.log('createCooking number randomizer: ', num) 367 | res.locals.recipe = data[num]; 368 | 369 | }) 370 | .catch((err) => console.log(err)); 371 | // console.log('======> eventController.createCooking res.locals.recipe: ', res.locals.recipe); 372 | const { id, title, image, usedIngredients } = res.locals.recipe; 373 | const recipeid = id; 374 | let ingredientList = []; 375 | 376 | usedIngredients.forEach(ingredientObj => { 377 | const { id, name, image } = ingredientObj; 378 | let eachIngredient = { name, id, image }; 379 | // push this into the container from above 380 | ingredientList.push(eachIngredient); 381 | }); 382 | 383 | // FIRST QUERY: INSERT RECIPES INTO RECIPES TABLE 384 | const queryString1 = queries.saveRecipe; 385 | const queryValues1 = [title, recipeid, image]; // (recipename, recipeid, recipeimage) 386 | await db.query(queryString1, queryValues1) 387 | .then(data => console.log('========> eventController.createCooking query1 data: ', data)) 388 | .catch(err => console.log('========> eventController.createCooking query1 err: ', err)); 389 | 390 | for (let i = 0; i < ingredientList.length; i++) { 391 | const { name, id, image } = ingredientList[i]; 392 | // SECOND QUERY: INSERT INGREDIENTS INTO INGREDIENTS TABLE 393 | const queryString2 = queries.saveIngredients; 394 | const queryValues2 = [name, id, image, recipeid]; // (ingredientname, ingredientid, ingredientimage, recipeid) 395 | await db.query(queryString2, queryValues2) 396 | .then(data => console.log('========> eventController.createCooking query2 data: ', data)) 397 | .catch(err => console.log('========> eventController.createCooking query2 err: ', err)); 398 | } 399 | 400 | const queryString3 = queries.createEvent; 401 | 402 | 403 | // (eventtitle, eventdate, eventstarttime, eventendtime, eventlocation, eventdetails, eventownerid, eventownerusername, eventmessages, eventtype) 404 | const queryValues3 = [eventtitle, eventdate, eventstarttime, eventstarttime, eventlocation, eventdetails, userid, username, "{}", eventtype]; 405 | await db.query(queryString3, queryValues3) 406 | .then(data => { 407 | console.log('>>> eventController.createCooking DATA ', data); 408 | }) 409 | .catch(err => { 410 | console.log('>>> eventController.createEvent ERR ', err); 411 | return next({ 412 | log: `Error occurred with queries.createEvent OR eventController.createCooking middleware: ${err}`, 413 | message: { err: "An error occured with SQL when creating event." }, 414 | }); 415 | }) 416 | return next(); 417 | } catch (err) { 418 | console.log('>>> eventController.createCooking ERR', err); 419 | return next({ 420 | log: `Error occurred with queries.addtoUsersAndEvents OR eventController.createCooking middleware: ${err}`, 421 | message: { err: "An error occured with SQL when adding to cooking event table." }, 422 | }) 423 | } 424 | } 425 | }; 426 | 427 | eventController.addNewEventToJoinTable = (req, res, next) => { 428 | // console.log('eventController.addNewEventToJoinTable') 429 | const queryString = queries.addNewEventToJoinTable; 430 | // const queryValues = [res.locals.eventID.eventid] // changed this to eventtitle 431 | // console.log('eventController.addNewEventToJoinTable res.locals.eventTitle.eventtitle', res.locals.eventTitle.eventtitle) // changed this to eventtitle 432 | const queryValues = [res.locals.eventTitle.eventtitle] // changed this to eventtitle 433 | db.query(queryString, queryValues) 434 | .then(data => { 435 | // WE DON'T ACTUALLY USE THIS ANYWHERE - FOR DEBUGGING AND TESTING ONLY 436 | res.locals.usersandevents = data.rows[0]; 437 | return next(); 438 | }) 439 | .catch(err => { 440 | console.log('>>> eventController.addNewEventToJoinTable ERR', err); 441 | return next({ 442 | log: `Error occurred with queries.addtoUsersAndEvents OR eventController.addNewEventToJoinTable middleware: ${err}`, 443 | message: { err: "An error occured with SQL when adding to addtoUsersAndEvents table." }, 444 | }); 445 | }) 446 | }; 447 | 448 | eventController.verifyAttendee = (req, res, next) => { 449 | const title = req.query.eventtitle; 450 | const { username } = res.locals.allUserInfo 451 | const queryString = queries.selectEventAttendees; 452 | const queryValues = [title]; 453 | db.query(queryString, queryValues) 454 | .then(data => { 455 | console.log('data: ', data); 456 | const attendees = []; 457 | for (const attendeeObj of data.rows) { 458 | attendees.push(attendeeObj.username); 459 | } 460 | // console.log('eventController.verifyAttendee attendees: ', attendees); 461 | if (attendees.includes(username)) { 462 | return next({ 463 | log: `Error: User is already an attendee`, 464 | message: { err: "User is already an attendee" }, 465 | }); 466 | } else { 467 | res.locals.eventID = data.rows[0].eventid; 468 | res.locals.eventTitle = data.rows[0].eventtitle; 469 | res.locals.eventDate = data.rows[0].eventdate; 470 | res.locals.eventStartTime = data.rows[0].eventstarttime; 471 | res.locals.eventEndTime = data.rows[0].eventendtime; 472 | res.locals.eventDetails = data.rows[0].eventdetails; 473 | res.locals.eventLocation = data.rows[0].eventlocation; 474 | return next(); 475 | } 476 | }) 477 | .catch(err => { 478 | return next({ 479 | log: `Error occurred with queries.selectEventAttendees OR eventController.verifyAttendee middleware: ${err}`, 480 | message: { err: "An error occured with SQL when verifying if user attended said event." }, 481 | }); 482 | }) 483 | } 484 | 485 | // (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 486 | eventController.addAttendee = (req, res, next) => { 487 | const title = req.query.eventtitle 488 | 489 | const { userid, username } = res.locals.allUserInfo 490 | // eventsID is saved in res.locals.eventID 491 | 492 | const queryString = queries.addUserToEvent; 493 | const queryValues = [ 494 | userid, 495 | username, 496 | res.locals.eventID, 497 | title, 498 | res.locals.eventDate, 499 | res.locals.eventStartTime, 500 | res.locals.eventEndTime, 501 | res.locals.eventDetails, 502 | res.locals.eventLocation, 503 | ]; 504 | 505 | db.query(queryString, queryValues) 506 | .then(data => { 507 | console.log('data from addAttendee: ', data); 508 | return next(); 509 | }) 510 | .catch(err => { 511 | return next({ 512 | log: `Error occurred with queries.addUserToEvent OR eventController.addAttendee middleware: ${err}`, 513 | message: { err: "An error occured with SQL adding a user to an existing event as an attendee." }, 514 | }); 515 | }) 516 | }; 517 | 518 | // eventController.allEvents = (req, res, next) => { 519 | 520 | // const queryString = queries.getAllEvents; 521 | 522 | // db.query(queryString) 523 | // .then(data => { 524 | // if (!data.rows) { 525 | // res.locals.allEventsInfo = []; 526 | // } else { 527 | // console.log('eventData', data.rows) 528 | // const eventAndUserDataQueryString = queries.getAttendeeEvents; 529 | // db.query(eventAndUserDataQueryString).then(eventAndUserData => { 530 | // console.log('eventAndUserData', eventAndUserData.rows) 531 | // const mergedTable = data.rows.map(e => { 532 | // const attendees = eventAndUserData.rows.filter(entry => entry.eventid == e.eventid) 533 | // e.attendees = attendees; 534 | // return e; 535 | // }) 536 | // res.locals.allEventsInfo = mergedTable 537 | // console.log("merged table", res.locals.allEventsInfo) 538 | // return next(); 539 | // }) 540 | // } 541 | 542 | // }) 543 | // .catch(err => { 544 | // return next({ 545 | // log: `Error occurred with queries.getAllEvents OR eventController.allEvents middleware: ${err}`, 546 | // message: { err: "An error occured with SQL when retrieving all events information." }, 547 | // }); 548 | // }) 549 | // }; 550 | 551 | eventController.allEvents = async (req, res, next) => { 552 | try { 553 | // console.log('==========> eventController.allEvents req.query: ', req.query); // EMPTY {} 554 | // console.log('==========> eventController.allEvents req.body:', req.body); // EMPTY {} 555 | const queryString1 = queries.getAllEvents; 556 | const queryString2 = queries.getEventAllAttendees; 557 | const queryString3 = queries.getEventMessages; 558 | 559 | const events = await db.query(queryString1) 560 | const attendees = await db.query(queryString2) 561 | const messages = await db.query(queryString3) 562 | 563 | // console.log('========> events.rows: ', events.rows); 564 | // console.log('========> attendees.rows: ', attendees.rows); 565 | // console.log('========> messages.rows: ', messages.rows); 566 | 567 | if (!events.rows) res.locals.allEventsInfo = []; 568 | else { 569 | events.rows.forEach((eventObj, i) => { 570 | const eventAttendeeList = attendees.rows.filter(userObj => userObj.eventid == eventObj.eventid); 571 | // console.log('eventAttendeeList: ', eventAttendeeList) 572 | eventObj.attendees = eventAttendeeList; 573 | // console.log('eventObj: ', eventObj) 574 | 575 | const eventMessageList = messages.rows.filter(messageObj => messageObj.eventtitle == eventObj.eventtitle); 576 | // console.log('eventMessageList: ', eventMessageList) 577 | eventObj.content = eventMessageList 578 | // console.log('eventObj: ', eventObj) 579 | }) 580 | res.locals.allEventsInfo = events.rows; 581 | // console.log('events after insertion of attendees & messages: ', events.rows); 582 | // console.log('eventController.allEvents hit and moving on!') 583 | } 584 | return next(); 585 | } catch (err) { 586 | return next({ 587 | log: `Error occurred with queries.getAllEvents OR eventController.allEvents middleware: ${err}`, 588 | message: { err: "An error occured with SQL when retrieving all events information." }, 589 | }); 590 | }; 591 | } 592 | 593 | 594 | eventController.getUserDetail = (req, res, next) => { 595 | 596 | const countObj = []; // each element should how many attendees are for each event in succession; 597 | res.locals.attendees.forEach(arr => { 598 | countObj.push(arr.length); 599 | }) 600 | 601 | const allUsernames = res.locals.attendees.flat(Infinity); 602 | // console.log('FLATTENED USERNAMES', allUsernames); 603 | 604 | const queryString = queries.userInfo; 605 | 606 | const promises = []; 607 | 608 | for (let i = 0; i < allUsernames.length; i++) { 609 | const result = new Promise((resolve, reject) => { 610 | try { 611 | const queryResult = db.query(queryString, [allUsernames[i]]); 612 | return resolve(queryResult) 613 | } catch (err) { 614 | return reject(err); 615 | } 616 | }) 617 | promises.push(result); 618 | } 619 | 620 | const resolvedPromises = Promise.all(promises) 621 | .then(data => { 622 | 623 | res.locals.userDetail = []; 624 | 625 | for (let i = 0; i < countObj.length; i += 1) { 626 | let turns = countObj[i] 627 | let count = 0; 628 | const container = []; 629 | while (count < turns) { 630 | const minchan = data.shift() 631 | container.push(minchan.rows[0]); 632 | count++; 633 | } 634 | res.locals.userDetail.push(container); 635 | } 636 | return next(); 637 | }) 638 | .catch(err => console.log('promise.all err: ', err)); 639 | } 640 | 641 | eventController.consolidation = (req, res, next) => { 642 | const consolidatedEvents = { ...res.locals.allEventsInfo }; 643 | res.locals.userDetail.forEach((arr, i) => { 644 | consolidatedEvents[i].attendees = arr; 645 | }) 646 | return next(); 647 | } 648 | 649 | eventController.filterForUser = (req, res, next) => { 650 | const { userid } = res.locals.allUserInfo 651 | 652 | const filtered = res.locals.allEventsInfo.filter(event => event.attendees.some(attendee => attendee.userid === userid)) 653 | // console.log("eventController.filterForUser, filtered", filtered) 654 | res.locals.allEventsInfo = filtered; 655 | return next(); 656 | } 657 | 658 | eventController.addMessage = (req, res, next) => { 659 | // console.log('eventController.addMessage first line hit, req.body and req.query: ', req.body, req.query) 660 | // console.log('eventController.addMessage first line hit!'); 661 | // console.log('eventController.addMessage req.body: ', req.body) 662 | // console.log('eventController.addMessage req.query: ', req.query) 663 | const eventtitle = req.query.eventtitle; 664 | // console.log('eventController.addMessage eventtitle: ', eventtitle) 665 | const { userid, username, eventid, messagetext, messagedate, messagetime } = req.body; 666 | // console.log('hihihi'); 667 | const time2 = new Date(Date.now()).toISOString().replace('T', ' ').replace('Z', ''); 668 | // console.log(time2); 669 | // const dateObj = new Date(messagetime); 670 | // const converted = dateObj.toISOString(); 671 | // console.log('ACTUAL TIME CONVERTED: ', time2); 672 | const queryString = queries.addMessageToEvent; 673 | const queryValues = [userid, username, eventid, eventtitle, messagetext, messagedate, time2]; 674 | 675 | // console.log('I AM JENNIFER: ', queryValues); 676 | 677 | db.query(queryString, queryValues) 678 | .then(data => { 679 | console.log('data from addMessage: ', data); 680 | return next(); 681 | }) 682 | .catch(err => { 683 | return next({ 684 | log: `Error occurred with queries.addMessageToEvent OR eventController.addMessage middleware: ${err}`, 685 | message: { err: "An error occured with SQL adding a comment to an existing event." }, 686 | }); 687 | }) 688 | }; 689 | 690 | 691 | 692 | module.exports = eventController; 693 | -------------------------------------------------------------------------------- /server/controllers/fbController.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | const FB = require('fb'); 3 | require('dotenv').config(); 4 | 5 | const fbController = {}; 6 | 7 | fbController.oAuth = (req, res, next) => { 8 | 9 | const redirect_uri = 'http://localhost:8080/api/login/FB' 10 | const app_id = 2445427332416583 11 | // const app_id = process.env.FB_ID; 12 | const scope = 'public_profile,email' 13 | 14 | res.locals.url = `https://www.facebook.com/v8.0/dialog/oauth?client_id=${app_id}&redirect_uri=${redirect_uri}&scope=${scope}` 15 | 16 | return next(); 17 | }; 18 | 19 | fbController.afterConsent = (req, res, next) => { 20 | 21 | const code = req.query.code; 22 | const redirect_uri = 'http://localhost:8080/api/login/FB' 23 | const app_id = 2445427332416583 24 | // const app_id = process.env.FB_ID; 25 | const client_secret = '6bd189482d27459c46d73ee1417a3126'; 26 | // const client_secret = process.env.CLIENT_SECRET; 27 | const url = `https://graph.facebook.com/v8.0/oauth/access_token?client_id=${app_id}&redirect_uri=${redirect_uri}&client_secret=${client_secret}&code=${code}` 28 | 29 | fetch(url, { 30 | method: 'GET', 31 | }) 32 | .then(res => res.json()) 33 | .then(response => { 34 | res.locals.provider = 'facebook'; 35 | res.locals.token = response.access_token; 36 | return next() 37 | }) 38 | .catch(err => console.log(err)); 39 | }; 40 | 41 | module.exports = fbController; 42 | -------------------------------------------------------------------------------- /server/controllers/fileController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models.js"); 2 | const queries = require("../utils/queries"); 3 | const fileController = {}; 4 | const FB = require('fb') 5 | const jwtDecode = require('jwt-decode'); 6 | // const { serviceusage } = require("googleapis/build/src/apis/serviceusage"); 7 | // const e = require("express"); 8 | // const fetch = require('node-fetch'); 9 | 10 | fileController.createUser = async (req, res, next) => { 11 | 12 | const { provider } = res.locals 13 | 14 | let email; 15 | let given_name; 16 | let family_name; 17 | let picture;; 18 | 19 | if (provider === 'facebook') { 20 | FB.api( 21 | `/me?access_token=${res.locals.token}`, 22 | 'GET', 23 | {"fields":"id,name,email"}, 24 | function(response) { 25 | email = response.email; 26 | given_name = response.name.split(' ')[0]; 27 | family_name = response.name.split(' ')[1]; 28 | picture = '../../client/assets/Codesmith.png'; 29 | 30 | const queryString1 = queries.userInfo; 31 | const queryValues1 = [email]; 32 | 33 | const queryString2 = queries.addUser; 34 | const queryValues2 = [email, given_name, family_name, picture]; 35 | 36 | db.query(queryString1, queryValues1) // Does the user exist? 37 | .then(data => { 38 | if (!data.rows.length) { 39 | db.query(queryString2, queryValues2) // If not, create user 40 | .then(data => { 41 | console.log('NEW USER: ', data.rows[0].username); 42 | return next(); 43 | }) 44 | .catch(err => { 45 | return next({ 46 | log: `Error occurred with queries.addUser OR fileController.createUser middleware: ${err}`, 47 | message: { err: "An error occurred with adding new user to the database." }, 48 | }); 49 | }) 50 | } else { 51 | return next(); 52 | } 53 | }) 54 | .catch(err => { 55 | return next({ 56 | log: `Error occurred with queries.userInfo OR fileController.createUser middleware: ${err}`, 57 | message: { err: "An error occurred when checking user information from database." }, 58 | }); 59 | }); 60 | 61 | } 62 | ); 63 | } else if (provider === 'google') { 64 | 65 | const decoded = jwtDecode(res.locals.token); 66 | email = decoded.email; 67 | given_name = decoded.given_name; 68 | family_name = decoded.family_name; 69 | picture = decoded.picture; 70 | 71 | const queryString1 = queries.userInfo; 72 | const queryValues1 = [email]; 73 | 74 | const queryString2 = queries.addUser; 75 | const queryValues2 = [email, given_name, family_name, picture]; 76 | 77 | db.query(queryString1, queryValues1) // does user exist? 78 | .then(data => { 79 | if (!data.rows.length) { 80 | db.query(queryString2, queryValues2) // create user 81 | .then(data => { 82 | res.locals.username = data.rows[0].username; // is this superfluous? 83 | console.log('NEW USER: ', res.locals.username); 84 | return next(); 85 | }) 86 | .catch(err => { 87 | return next({ 88 | log: `Error occurred with queries.addUser OR fileController.createUser middleware: ${err}`, 89 | message: { err: "An error occurred with adding new user to the database." }, 90 | }); 91 | }) 92 | } else { 93 | return next(); 94 | } 95 | }) 96 | .catch(err => { 97 | return next({ 98 | log: `Error occurred with queries.userInfo OR fileController.createUser middleware: ${err}`, 99 | message: { err: "An error occurred when checking user information from database." }, 100 | }); 101 | }); 102 | } 103 | }; 104 | 105 | fileController.getUser = (req, res, next) => { 106 | 107 | const { provider } = req.cookies; 108 | const token = req.cookies.user; 109 | let decoded; 110 | let email; 111 | 112 | if (provider === 'facebook') { 113 | FB.api( 114 | `/me?access_token=${token}`, 115 | 'GET', 116 | {"fields":"id,name,email"}, 117 | function(response) { 118 | 119 | email = response.email; 120 | 121 | const queryString = queries.userInfo; 122 | const queryValues = [email]; 123 | 124 | db.query(queryString, queryValues) 125 | .then(data => { 126 | console.log('data.rows[0]', data.rows[0]); 127 | res.locals.allUserInfo = data.rows[0]; 128 | return next(); 129 | }) 130 | .catch(err => { 131 | return next({ 132 | log: `Error occurred with queries.userInfo OR fileController.getUser middleware: ${err}`, 133 | message: { err: "An error occured with SQL or server when retrieving user information." }, 134 | }); 135 | }) 136 | }) 137 | } else if (provider === 'google') { 138 | 139 | decoded = jwtDecode(token); 140 | email = decoded.email; 141 | 142 | const queryString = queries.userInfo; 143 | const queryValues = [email]; 144 | 145 | db.query(queryString, queryValues) 146 | .then(data => { 147 | console.log('data.rows[0]', data.rows[0]); 148 | res.locals.allUserInfo = data.rows[0]; 149 | return next(); 150 | }) 151 | .catch(err => { 152 | return next({ 153 | log: `Error occurred with queries.userInfo OR fileController.getUser middleware: ${err}`, 154 | message: { err: "An error occured with SQL or server when retrieving user information." }, 155 | }); 156 | }) 157 | } 158 | }; 159 | 160 | fileController.verifyUser = (req, res, next) => { 161 | 162 | return next(); 163 | 164 | // DEPRECATED 165 | 166 | // const decoded = jwtDecode(req.cookies.user); 167 | // const { email } = decoded; 168 | 169 | // if (email == req.query.userName) { 170 | // return next(); 171 | // } else { 172 | // return next({ 173 | // log: `Error occurred with fileController.verifyUser`, 174 | // code: 401, 175 | // message: { err: "Unauthorized Access." }, 176 | // }) 177 | // } 178 | } 179 | 180 | module.exports = fileController; 181 | -------------------------------------------------------------------------------- /server/controllers/loginController.js: -------------------------------------------------------------------------------- 1 | const { google } = require('googleapis'); 2 | 3 | const loginController = {}; 4 | 5 | loginController.oAuth = async (req, res, next) => { 6 | 7 | const oauth2Client = new google.auth.OAuth2( 8 | '231739595536-t9fshjinu1djuiu6mu31410um2q7gf1a.apps.googleusercontent.com', 9 | '1dxP3-vAFCESbebjlMw6SaSL', 10 | 'http://localhost:8080/api/login/G' 11 | ); 12 | 13 | const scopes = [ 14 | 'https://www.googleapis.com/auth/userinfo.profile', 15 | 'https://www.googleapis.com/auth/classroom.profile.photos', 16 | 'https://www.googleapis.com/auth/userinfo.email' 17 | ]; 18 | 19 | const url = oauth2Client.generateAuthUrl({ 20 | access_type: 'offline', 21 | scope: scopes, 22 | response_type: 'code', 23 | prompt: 'consent', 24 | }) 25 | 26 | res.locals.url = url; 27 | return next(); 28 | }; 29 | 30 | loginController.afterConsent = (req, res, next) => { 31 | 32 | const oauth2Client = new google.auth.OAuth2( 33 | '231739595536-t9fshjinu1djuiu6mu31410um2q7gf1a.apps.googleusercontent.com', 34 | '1dxP3-vAFCESbebjlMw6SaSL', 35 | 'http://localhost:8080/api/login/G' 36 | ); 37 | 38 | oauth2Client.getToken(req.query.code) 39 | .then(data => { 40 | const { tokens } = data; 41 | oauth2Client.setCredentials(tokens); 42 | res.locals.provider = 'google'; 43 | res.locals.token = tokens.id_token; 44 | return next(); 45 | }) 46 | .catch(err => { 47 | if (err) console.log('afterConsent .catch block: ', err) 48 | }) 49 | }; 50 | 51 | module.exports = loginController; 52 | -------------------------------------------------------------------------------- /server/models/models.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const pg = require('pg'); 3 | pg.defaults.poolSize = 100; 4 | 5 | // const patty = process.env.PATTY_SQL 6 | // const jen = process.env.JEN_SQL 7 | 8 | // const patty = 'postgres://wleysufm:pXsLZ76bqW-jjRNUkgLJHtxPWR2Dk002@raja.db.elephantsql.com:5432/wleysufm'; 9 | // const jen = 'postgres://kyvtwizd:fVABvmKeENO7jTd3IBKj1PiIcNyVylqD@raja.db.elephantsql.com:5432/kyvtwizd' 10 | // const PG_URI = jen; 11 | 12 | const patty = 'postgres://wleysufm:pXsLZ76bqW-jjRNUkgLJHtxPWR2Dk002@raja.db.elephantsql.com:5432/wleysufm'; 13 | const jen = 'postgres://kyvtwizd:fVABvmKeENO7jTd3IBKj1PiIcNyVylqD@raja.db.elephantsql.com:5432/kyvtwizd' 14 | const PG_URI = jen; 15 | 16 | const pool = new pg.Pool({ 17 | connectionString: PG_URI, 18 | }); 19 | 20 | module.exports = { 21 | query: (text, params, callback) => { 22 | console.log('executed query', text); 23 | return pool.query(text, params, callback); 24 | }, 25 | }; 26 | 27 | // const { Pool } = require('pg'); 28 | 29 | // const PG_URI = 'postgres://opkmyovf:jlzeGeCdKnUTuAX0p15MbMj7v1LqFpFg@rajje.db.elephantsql.com:5432/opkmyovf'; 30 | 31 | // const pool = new Pool({ 32 | // connectionString: PG_URI, 33 | // }); 34 | 35 | // module.exports = { 36 | // query: (text, params, callback) => { 37 | // console.log('executed query', text); 38 | // return pool.query(text, params, callback); 39 | // }, 40 | // }; 41 | 42 | 43 | // psql -d postgres://wleysufm:pXsLZ76bqW-jjRNUkgLJHtxPWR2Dk002@raja.db.elephantsql.com:5432/wleysufm -f iteration-project_postgres_create.sql -------------------------------------------------------------------------------- /server/routers/api.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const path = require('path'); 4 | const fileController = require('../controllers/fileController'); 5 | const cookieController = require('../controllers/cookieController'); 6 | const eventController = require('../controllers/eventController'); 7 | const loginController = require('../controllers/loginController'); 8 | const fbController = require('../controllers/fbController'); 9 | 10 | //DELETE EVENT 11 | router.delete('/delete', 12 | eventController.deleteEvent, 13 | (req, res) => { 14 | res.status(200).send('event deleted successfully!! yay.') 15 | } 16 | ) 17 | 18 | //UPDATE EVENT 19 | router.put('/update', 20 | eventController.updateEvent, 21 | (req, res) => { 22 | // res.status(200).json(res.locals.updatedEvent); 23 | res.status(200).send('success'); 24 | } 25 | ); 26 | 27 | // EXISING USER LOGIN 28 | /* FACEBOOK OAUTH LOGIN */ 29 | 30 | router.get('/loginFB', 31 | fbController.oAuth, 32 | (req, res) => { 33 | return res.redirect(res.locals.url); 34 | }); 35 | 36 | router.get('/login/FB', 37 | fbController.afterConsent, 38 | cookieController.setFacebookCookie, 39 | fileController.createUser, 40 | (req, res) => { 41 | return res.redirect('/'); 42 | }); 43 | 44 | /* GOOGLE OAUTH LOGIN */ 45 | 46 | router.get('/loginG', 47 | loginController.oAuth, 48 | (req, res) => { 49 | return res.redirect(res.locals.url); 50 | }); 51 | 52 | router.get('/login/G', 53 | loginController.afterConsent, 54 | cookieController.setGoogleCookie, 55 | fileController.createUser, 56 | (req, res) => { 57 | return res.redirect('/'); 58 | }); 59 | 60 | // REVISIT WEBSITE AFTER LEAVING, OR VISITING SOMEONE ELSE'S PROFILE PAGE 61 | 62 | // /info IS THE ENDPOINT that is auto-hit whenever a user logins, also when a user navigates away from our app, i.e. to google.com, and navs back to our app 63 | router.get('/info', 64 | cookieController.isLoggedIn, // this is really only is applicable for the same user 65 | fileController.getUser, 66 | eventController.allEvents, // COMMENT OUT IF IT BREAKS 67 | eventController.filterForUser, // COMMENT OUT IF IT BREAKS 68 | // eventController.getMessages, // ADDING IN MESSAGES 69 | // eventController.getFullEvents, 70 | // eventController.getAllAttendees, 71 | // eventController.getUserDetail, 72 | // eventController.consolidation, 73 | (req, res) => { 74 | const responseObj = { 75 | users: res.locals.allUserInfo, 76 | events: res.locals.allEventsInfo, 77 | }; 78 | console.log('responseObj: ', responseObj); 79 | return res.status(200).json(responseObj); 80 | }); 81 | 82 | // LOGGING OUT 83 | 84 | router.get('/logout', 85 | cookieController.removeCookie, 86 | (req, res) => { 87 | return res.redirect('/'); 88 | }); 89 | 90 | // CREATE A NEW EVENT 91 | 92 | /** 93 | * ORDER OF OPERATION FOR EVENTS: 94 | * 1. /events =====> we grab all events 95 | * 2. /add or /create =======> user adds themself to an event, or user creates a new event 96 | * 3. /message ========> user posts a comment on a specific event 97 | */ 98 | 99 | // GET FULL EVENT FEED - ALL EVENTS EVER 100 | router.get('/events', 101 | eventController.allEvents, 102 | (req, res) => { 103 | return res.status(200).json(res.locals.allEventsInfo); 104 | } 105 | ) 106 | 107 | // CREATE NEW CALENDAR EVENT 108 | router.post('/create', 109 | fileController.verifyUser, 110 | fileController.getUser, 111 | eventController.createEvent, 112 | eventController.createCooking, 113 | eventController.addNewEventToJoinTable, 114 | (req, res) => { 115 | return res.status(200).json('Event succcessfully created.'); 116 | }); 117 | 118 | // CREATE NEW CALENDAR EVENT 119 | // router.post('/cooking', 120 | // fileController.verifyUser, 121 | // fileController.getUser, 122 | // eventController.createCooking, 123 | // eventController.addCookingEventToJoinTable, 124 | // (req, res) => { 125 | // return res.status(200).json('Event succcessfully created.'); 126 | // }); 127 | 128 | 129 | // ADD USER TO EXISTING EVENT 130 | router.post('/add', 131 | fileController.getUser, 132 | eventController.verifyAttendee, 133 | eventController.addAttendee, 134 | (req, res) => { 135 | return res.status(200).json('User successfully added as attendee.'); 136 | }); 137 | 138 | router.post('/message', 139 | // eventController.allEvents, 140 | // PROBABLY NEEED TO ADD GETEVENT MIDDLEWARE HERE TO FIX MESSAGE GLITCH? MAYBE 141 | // fileController.getUser, 142 | eventController.addMessage, 143 | (req, res) => { 144 | return res.status(200).json('User successfully added a comment'); 145 | } 146 | ) 147 | 148 | router.post('/upload', (req, res) => { 149 | console.log('server.js app.post /upload first line hitting?') 150 | if (!req.files) { 151 | return res.status(400).json({ msg: "File not uploaded" }) 152 | } 153 | console.log('servers.js req.files: ', req.files) 154 | 155 | // accessing the file 156 | const myFile = req.files.file; 157 | console.log('servers.js req.files.file: ', req.files.file) 158 | 159 | // mv() method places the file inside public directory 160 | let pathName = `${path.resolve(__dirname, '../../client/uploads/') + '/' + myFile.name}`; 161 | console.log('========> server.js pathName: ', pathName); 162 | 163 | myFile.mv(pathName, function (err) { 164 | if (err) { 165 | console.log('server.js myFile.mv error: ', err) 166 | return res.status(500).send({ msg: "server error occured" }); 167 | } 168 | // returing the response with file path and name 169 | // was res.send, changed to res.json 170 | return res.json({ name: myFile.name, path: `../../client/uploads/${myFile.name}` }); // medium 171 | // return res.json({ : myFile.name, filePath: `/uploads/${myFile.name}` }); // traversy 172 | }); 173 | }) 174 | 175 | 176 | module.exports = router; 177 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const cookieParser = require('cookie-parser'); 4 | const fileUpload = require('express-fileupload'); 5 | const upload = require('./upload') 6 | // const cors = require('cors'); 7 | // const multer = require('multer') 8 | // const upload = multer({ dest: 'uploads/' }) 9 | 10 | const app = express(); 11 | const apiRouter = require('./routers/api'); 12 | 13 | // BODY PARSERS & COOKIE PARSER 14 | app.use(express.json()); 15 | app.use(express.urlencoded()); 16 | app.use(cookieParser()); 17 | 18 | // SERVE UP STATIC FILES 19 | app.use(express.static(path.join(__dirname, '../client/assets'))); 20 | 21 | // const corsOptions = { 22 | // origin: '*', 23 | // optionsSuccessStatus: 200, 24 | // } 25 | // app.use(cors(corsOptions)); // Enable all cors requests 26 | 27 | app.use(fileUpload()); 28 | 29 | app.use('/dist', express.static(path.join(__dirname, '../dist'))); 30 | 31 | // SERVE INDEX.HTML ON THE ROUTE '/' 32 | app.get('/', (req, res) => { 33 | res.sendFile(path.join(__dirname, '../client/index.html')); 34 | }); 35 | 36 | // API ROUTER 37 | app.use('/api', apiRouter); 38 | 39 | // HANDLING UNKNOWN URLS 40 | app.use('*', (req, res) => { 41 | res.status(404).send('URL path not found'); 42 | }); 43 | 44 | // ERROR HANDLER 45 | app.use((err, req, res, next) => { 46 | res.status(401).send(err.message); // WHAT IS FRONT-END EXPECTING? JSON OR STRING? 47 | }); 48 | 49 | //app.listen(3000); //listens on port 3000 -> http://localhost:3000/ 50 | app.listen(process.env.PORT || 3000); 51 | module.exports = app; 52 | 53 | //from package.json npm test just in case we need to add it back ---> echo \"Error: no test specified\" && exit 1 54 | 55 | -------------------------------------------------------------------------------- /server/server.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const app = require('./server'); //gets root route 3 | const api = require('./routers/api') // gets routes for api 4 | 5 | 6 | 7 | 8 | //====================================================== 9 | /* Test for '/' root route that serves the home page */ 10 | //====================================================== 11 | 12 | describe('GET /', () => { 13 | it('responds with 200 status and text/html content type', done => { 14 | request(app) 15 | .get('/') 16 | .expect('Content-Type', /text\/html/) 17 | .expect(200, done); 18 | }); 19 | }); 20 | 21 | //====================================================== 22 | /* Test '/api' routes * test/ 23 | //====================================================== 24 | 25 | //Login routes 26 | // describe('GET /api/', () => { 27 | // it('responds with 200 status', async () => { 28 | // request(api) 29 | // .get('/api/login') 30 | // // WHAT AM I CHECKING FOR BACK FROM GOOGLE? (res.locals.url) 31 | // }); 32 | 33 | // it('redirection back to root page', async () => { 34 | // request(api) 35 | // .get('/api/login/google') 36 | // // HOW DO I CHECK IF I GET REDIRECTED BACK TO HOMEPAGE ('http://localhost:8080/') 37 | // }); 38 | // }); 39 | 40 | 41 | 42 | //Get info routes 43 | //-- NEED TO FIGURE OUT HOW TO CHECK IF RESPONSE IS AN OBJECT -- 44 | // describe('/api/', () => { 45 | // it('responds with 200 status and application/json content type', () => { 46 | // return request(api) 47 | // .get('/api/info') 48 | // .expect('Content-Type', /json/) 49 | // .expect(200) 50 | // }); 51 | // }); 52 | 53 | 54 | 55 | //Post create & add routes 56 | // describe('POST /api/', () => { 57 | // it('responds with 200 status and application/json content type', async () => { 58 | // let minchanCommentingOnMinchanWedding = [1, 'minchanjun@gmail.com', 2, 'minchan wedding', 'my wedding is gonna be LIT', '2020-10-18', '12:30:00'] 59 | // await request(api) 60 | // .post('/api/create') 61 | // .expect('Content-Type', /application\/json/) 62 | // .send(minchanCommentingOnMinchanWedding) 63 | // .then((data) => { 64 | // console.log('/create') 65 | // }) 66 | // }); 67 | 68 | // it('responds with 200 status and application/json content type', done => { 69 | // return request(api) 70 | // .post('/api/add') 71 | // .expect('Content-Type', /application\/json/) 72 | // .expect(200, done) 73 | // }); 74 | // }); 75 | 76 | 77 | //get events 78 | // -- NEED TO FIGURE OUT HOW TO CHECK IF RESPONSE IS AN OBJECT OR ARRAY -- 79 | // describe('/api/', () => { 80 | // it('responds with 200 status and application/json content type', () => { 81 | // return request(api) 82 | // .get('/api/events') 83 | // .expect('Content-Type', /application\/json/) 84 | // .then(data => console.log('/api/events')); 85 | // }); 86 | // }); 87 | 88 | */ -------------------------------------------------------------------------------- /server/upload.js: -------------------------------------------------------------------------------- 1 | const IncomingForm = require('formidable').IncomingForm 2 | 3 | module.exports = function upload(req, res) { 4 | const form = new IncomingForm(); 5 | form.on('file', (field, file) => { 6 | // Do something with the file 7 | // e.g. save it to the database 8 | // you can access it using file.path 9 | }) 10 | form.on('end', () => { 11 | res.json() 12 | }) 13 | form.parse(req) 14 | } -------------------------------------------------------------------------------- /server/utils/queries.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models.js"); 2 | 3 | const queries = {}; 4 | 5 | // GET ALL EVENTS 6 | queries.getAllEvents = ` 7 | SELECT * FROM events 8 | `; 9 | 10 | // GET ALL ATTENDEES FOR EVENT 11 | queries.getEventAllAttendees = ` 12 | SELECT u.*, ue.eventid 13 | FROM usersandevents ue 14 | JOIN users u 15 | ON u.userid = ue.userid 16 | `; 17 | // 18 | // GET USER'S EVENTS 19 | queries.userEvents = ` 20 | SELECT * FROM usersandevents WHERE userid=$1 21 | `; 22 | 23 | // let minchanuserid = [1]; 24 | // db.query(queries.userEvents, minchanuserid).then(data => console.log(data.rows)); 25 | 26 | // GET ALL USER'S PERSONAL INFO 27 | queries.userInfo = `SELECT * FROM users WHERE username=$1`; // const values = [req.query.id] 28 | 29 | // let minchanusername = ['minchanjun@gmail.com']; 30 | // db.query(queries.userInfo, minchanusername).then(data => console.log(data.rows)); 31 | 32 | 33 | // QUERY TO ADD USER 34 | queries.addUser = ` 35 | INSERT INTO users 36 | (username, firstname, lastname, profilephoto) 37 | VALUES($1, $2, $3, $4) 38 | RETURNING username 39 | ; 40 | `; 41 | 42 | // let addMinchan = ['minchanjun@gmail.com', 'minchan', 'jun', 'photo TBD']; 43 | // db.query(queries.addUser, addMinchan).then(data => console.log(data.rows)); 44 | 45 | // let addMarc = ['marcaburnie@gmail.com', 'marc', 'burnie', 'photo TBD']; 46 | // db.query(queries.addUser, addMarc).then(data => console.log(data.rows)); 47 | 48 | // let addTaylor = ['taylorsriley@gmail.com', 'Taylor', 'RileyDu', 'photo TBD']; 49 | // db.query(queries.addUser, addTaylor).then(data => console.log(data.rows)); 50 | 51 | 52 | 53 | // CHANGED THIS TO EVENTTITLE 54 | // QUERY FOR WHEN USER CREATES EVENT 55 | queries.createEvent = ` 56 | INSERT INTO events 57 | (eventtitle, eventdate, eventstarttime, eventendtime, eventlocation, eventdetails, eventownerid, eventownerusername, eventmessages, eventtype) 58 | VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) 59 | RETURNING eventtitle 60 | ; 61 | `; 62 | 63 | // let minchanBirthday = ['minchan birthday', '9/15/2020', '06:00 PM', '09:00 PM', 'golf course', 'play minigolf birthday', 1, 'minchanjun@gmail.com', "{'hey when is it again', 'happy birthday!', 'sorry can\'t make it'}"] 64 | // db.query(queries.createEvent, minchanBirthday).then(data => console.log(data.rows)); 65 | 66 | // let minchanWedding = ['minchan wedding', '10/1/2020', '02:00 PM', '03:00 PM', 'castle', 'attend wedding', 1, 'minchanjun@gmail.com', "{'so excited for your wedding!', 'loving the location', 'sorry can\'t make it'}"] 67 | // db.query(queries.createEvent, minchanWedding).then(data => console.log(data.rows)); 68 | 69 | // let marcBirthday = ['marc birthday', '1/16/2021', '01:00 PM', '02:00 PM', 'dave n buster', 'arcade games', 2, 'marcaburnie@gmail.com', "{'congrats', 'happy bussdayyy', 'sorry don\'t think i make it'}"] 70 | // db.query(queries.createEvent, marcBirthday).then(data => console.log(data.rows)); 71 | 72 | 73 | 74 | // CHANGED THIS TO EVENTTITLE 75 | // ADDS ALL CURRENT EVENTS TO USERSANDEVENTS 76 | queries.addNewEventToJoinTable = ` 77 | INSERT INTO usersandevents (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 78 | SELECT eventownerid, eventownerusername, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation FROM events 79 | WHERE eventtitle=$1 80 | RETURNING usersandevents; 81 | `; 82 | // ====================== FOR TESTING ONLY ====================== 83 | // ====================== FOR TESTING ONLY ====================== 84 | // ====================== FOR TESTING ONLY ====================== 85 | queries.addNewEventToUsersAndEvents = ` 86 | INSERT INTO usersandevents (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 87 | SELECT eventownerid, eventownerusername, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation FROM events 88 | RETURNING usersandevents; 89 | `; 90 | // db.query(queries.addNewEventToUsersAndEvents).then(data => console.log(data.rows)); 91 | 92 | 93 | // USERS ADDS THEMSELVES TO OTHER PEOPLE'S EVENTS 94 | queries.addUserToEvent = `INSERT INTO usersandevents 95 | (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 96 | VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) 97 | RETURNING eventid 98 | ; 99 | `; 100 | // let marcAttendingMinchanBirthday = [2, 'marcaburnie@gmail.com', 4, 'minchan birthday', '2020-09-15', '18:00:00', '21:00:00', 'play minigolf birthday', 'golf course']; 101 | // db.query(queries.addUserToEvent, marcAttendingMinchanBirthday).then(data => console.log(data.rows));; 102 | 103 | // let taylorAttendingEvent = [2, 'taylorsriley@gmail.com', 1, 'minchan birthday', '2020-09-15', '18:00:00', '21:00:00', 'play minigolf birthday', 'golf course']; 104 | // db.query(queries.addUserToEvent, taylorAttendingEvent).then(data => console.log(data.rows));; 105 | 106 | // let TaylorminchanWedding = [4, 'taylorsriley@gmail.com', 2, 'minchan wedding', '10/1/2020', '02:00 PM', '03:00 PM', 'castle', 'attend wedding', 1, 'minchanjun@gmail.com', "{'so excited for your wedding!', 'loving the location', 'sorry can\'t make it'}"] 107 | // db.query(queries.addUserToEvent, TaylorminchanWedding).then(data => console.log(data.rows));; 108 | 109 | 110 | // GRAB EVENT'S ATTENDEES 111 | // queries.selectEventAttendees = `SELECT * FROM usersandevents WHERE eventtitle=$1`; 112 | queries.selectEventAttendees = `SELECT * FROM usersandevents WHERE eventtitle=$1`; 113 | 114 | // let minchanBirthdayEventTitle = ['minchan birthday']; 115 | // db.query(queries.selectEventAttendees, minchanBirthdayEventTitle).then(data => console.log(data.rows)); 116 | 117 | 118 | queries.addMessageToEvent = ` 119 | INSERT INTO eventsandmessages (userid, username, eventid, eventtitle, messagetext, messagedate, messagetime) 120 | VALUES($1, $2, $3, $4, $5, $6, $7) 121 | RETURNING eventsandmessages 122 | `; 123 | 124 | // let marcCommentingOnMinchanBday = [3, 'marcaburnie@gmail.com', 1, 'minchan birthday', 'happy birthday dude, from marc', '2020-08-17', '05:00:01'] 125 | // db.query(queries.addMessageToEvent, marcCommentingOnMinchanBday) 126 | 127 | // let minchanCommentingOnMinchanBday = [1, 'minchanjun@gmail.com', 1, 'minchan birthday', 'so excited to see everyone at my birthday', '2020-08-18', '10:00:01'] 128 | // db.query(queries.addMessageToEvent, minchanCommentingOnMinchanBday) 129 | 130 | // let minchanCommentingOnMinchanWedding = [1, 'minchanjun@gmail.com', 2, 'minchan wedding', 'my wedding is gonna be LIT', '2020-10-18', '12:30:00'] 131 | // db.query(queries.addMessageToEvent, minchanCommentingOnMinchanWedding) 132 | 133 | // let minchanCommentingOnMarcBday = [1, 'minchanjun@gmail.com', 3, 'marc birthday', 'happy birthday marc!', '2020-11-12', '14:30:00'] 134 | // db.query(queries.addMessageToEvent, minchanCommentingOnMarcBday) 135 | 136 | // GET COMMENTS FOR EVENTS 137 | queries.getEventMessages = ` 138 | SELECT u.userid, u.username, u.profilephoto, em.eventtitle, em.messagetext, em.messagedate, em.messagetime 139 | FROM users u 140 | JOIN eventsandmessages em 141 | ON u.username=em.username 142 | `; 143 | // WHERE em.eventtitle=$1 144 | // let minchanBirthdayEventTitle = ['Minchan Birthday']; 145 | // db.query(queries.selectEventComments, minchanBirthdayEventTitle).then(data => console.log(data.rows)); 146 | 147 | // DELETE EVENT 148 | // queries.deleteEvent(eventid) = ` 149 | // DELETE FROM usersandevents WHERE eventid=${eventid}; 150 | // DELETE FROM events WHERE eventid = ${eventid}; 151 | // ` 152 | 153 | 154 | // CLEAR ALL TABLES & DATA 155 | queries.clearAll = ` 156 | DROP TABLE usersandevents; 157 | DROP TABLE events; 158 | DROP TABLE users; 159 | `; 160 | 161 | 162 | queries.saveRecipe = ` 163 | INSERT INTO recipes (recipename, recipeid, recipeimage) 164 | VALUES ($1, $2, $3) 165 | RETURNING recipename 166 | `; 167 | queries.saveIngredients = ` 168 | INSERT INTO ingredients (ingredientname, ingredientid, ingredientimage, recipeid) 169 | VALUES ($1, $2, $3, $4) 170 | RETURNING ingredientname 171 | `; 172 | queries.getRecipeIngredients = ` 173 | SELECT * FROM ingredients 174 | WHERE recipeid=$1 175 | `; 176 | 177 | function getAllEventsUsersMessages() { 178 | const allEvents = async function (req, res, next) { 179 | try { 180 | const queryString1 = queries.getAllEvents; 181 | const queryString2 = queries.getEventAllAttendees; 182 | const queryString3 = queries.getEventMessages; 183 | const events = await db.query(queryString1) 184 | const attendees = await db.query(queryString2) 185 | const messages = await db.query(queryString3) 186 | 187 | console.log('========> events.rows: ', events.rows); 188 | console.log('========> attendees.rows: ', attendees.rows); 189 | console.log('========> messages.rows: ', messages.rows); 190 | 191 | events.rows.forEach((eventObj, i) => { 192 | const eventAttendeeList = attendees.rows.filter(userObj => userObj.eventid == eventObj.eventid); 193 | console.log('eventAttendeeList: ', eventAttendeeList) 194 | eventObj.attendees = eventAttendeeList; 195 | console.log('eventObj: ', eventObj) 196 | 197 | const eventMessageList = messages.rows.filter(messageObj => messageObj.eventtitle == eventObj.eventtitle); 198 | console.log('eventMessageList: ', eventMessageList) 199 | eventObj.content = eventMessageList 200 | console.log('eventObj: ', eventObj) 201 | }) 202 | 203 | console.log('events after insertion of attendees & messages: ', events.rows); 204 | // res.locals.allEventsInfo = events.rows; 205 | // console.log("res.locals.allEventsInfo", res.locals.allEventsInfo) 206 | return events.rows; 207 | } catch (err) { 208 | console.log(err); 209 | }; 210 | } 211 | allEvents(); 212 | } 213 | 214 | // getAllEventsUsersMessages(); 215 | 216 | 217 | module.exports = queries; -------------------------------------------------------------------------------- /server/utils/tempCodeRunnerFile.js: -------------------------------------------------------------------------------- 1 | let minchanCommentingOnMinchanBday = [1, 'minchanjun@gmail.com', 1, 'minchan birthday', 'so excited to see everyone at my birthday', '2020-08-18', '10:00:01'] 2 | db.query(queries.addMessageToEvent, minchanCommentingOnMinchanBday) 3 | 4 | let minchanCommentingOnMinchanWedding = [1, 'minchanjun@gmail.com', 2, 'minchan wedding', 'my wedding is gonna be LIT', '2020-10-18', '12:30:00'] 5 | db.query(queries.addMessageToEvent, minchanCommentingOnMinchanWedding) 6 | 7 | let minchanCommentingOnMarcBday = [1, 'minchanjun@gmail.com', 3, 'marc birthday', 'happy birthday marc!', '2020-11-12', '14:30:00'] 8 | db.query(queries.addMessageToEvent, minchanCommentingOnMarcBday) -------------------------------------------------------------------------------- /tests/enzyme.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { configure, shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import Notnav from '../client/components/Navbar'; 5 | // import toJson from 'enzyme-to-json'; 6 | 7 | // Newer Enzyme versions require an adapter to a particular version of React 8 | configure({ adapter: new Adapter() }); 9 | 10 | //search div with two components 11 | //create event and search event 12 | 13 | describe('Navbar has two buttons', () => { 14 | const wrapper = shallow(); 15 | 16 | it('Expect the Navbar to contain two \ tags referencing google and facebook Oauth', () => { 17 | expect(wrapper.find('a')).get(0).getAttribute('href').toBe('/api/loginFB'); 18 | expect(wrapper.find('a')).get(1).getAttribute('href').toBe('/api/loginG'); 19 | }); 20 | 21 | 22 | }); -------------------------------------------------------------------------------- /tests/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testRegex": "((\\.|/*.)(spec))\\.js?$", 3 | "setupFilesAfterEnv": [ 4 | "/jest.setup.js" 5 | ] 6 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './client/index.js', 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), 9 | filename: 'bundle.js' 10 | }, 11 | devtool: 'eval-source-map', 12 | mode: process.env.NODE_ENV, 13 | module: { 14 | rules: [ 15 | { 16 | test: /.(css|scss)$/, 17 | // include: [path.resolve(__dirname, '/node_modules/react-datepicker/'), path.resolve(__dirname, '/node_modules/bootstrap/')], 18 | // exclude: /node_modules/, 19 | use: ['style-loader', 'css-loader', 'sass-loader'], 20 | }, 21 | { 22 | test: /.jsx?$/, 23 | exclude: /node_modules/, 24 | use: { 25 | loader: 'babel-loader', 26 | options: { 27 | presets: ['@babel/preset-env', '@babel/preset-react'], 28 | }, 29 | }, 30 | }, 31 | { 32 | test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/, 33 | use: [ 34 | { 35 | // loads files as base64 encoded data url if image file is less than set limit 36 | loader: 'url-loader', 37 | options: { 38 | // if file is greater than the limit (bytes), file-loader is used as fallback 39 | limit: 8192, 40 | }, 41 | }, 42 | ], 43 | }, 44 | ] 45 | }, 46 | devServer: { 47 | // port: 8080, 48 | // contentBase: path.resolve(__dirname, '/dist'), 49 | // publicPath: '/dist/', 50 | proxy: { 51 | '/api/**': { 52 | target: 'http://localhost:3000', 53 | secure: false, 54 | }, 55 | '/assets/**': { 56 | target: 'http://localhost:3000', 57 | secure: false, 58 | }, 59 | }, 60 | hot: true, 61 | }, 62 | plugins: [ 63 | new HtmlWebpackPlugin({ 64 | template: './client/index.html', 65 | }), 66 | new MiniCssExtractPlugin() 67 | ], 68 | }; 69 | --------------------------------------------------------------------------------