├── .gitignore ├── .DS_Store ├── client ├── App.jsx ├── components │ ├── AddSearchEvent.jsx │ ├── Profile.jsx │ ├── EventsFeed.jsx │ ├── EventAttendees.jsx │ ├── InviteUser.jsx │ ├── Inbox.jsx │ ├── Invite.jsx │ ├── InboxItem.jsx │ ├── CoverPhoto.jsx │ ├── Navbar.jsx │ ├── Content.jsx │ ├── EditEvent.jsx │ ├── SearchEvent.jsx │ ├── Event.jsx │ ├── CreateEvent.jsx │ └── MainContainer.jsx ├── index.js ├── index.html └── stylesheets │ └── styles.scss ├── server ├── utils │ ├── cloudinary.js │ └── queries.js ├── models │ └── models.js ├── controllers │ ├── cookieController.js │ ├── loginController.js │ ├── fileController.js │ ├── photoController.js │ ├── inviteController.js │ └── eventController.js ├── server.js └── routers │ └── api.js ├── .vscode └── launch.json ├── __tests__ └── routes.test.js ├── scratch-project_postgres_create.sql ├── webpack.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | package-lock.json 3 | node_modules 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeigs/social_scrapbook/HEAD/.DS_Store -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import MainContainer from "./components/MainContainer.jsx" 3 | 4 | 5 | 6 | export default function App() { 7 | return < MainContainer /> 8 | }; -------------------------------------------------------------------------------- /server/utils/cloudinary.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const cloudinary = require('cloudinary').v2; 3 | 4 | cloudinary.config({ 5 | cloud_name: 'dkxftbzuu', 6 | api_key: 545886881446759, 7 | api_secret: 'vM5XIZptoGMNNR7O9BZ152kyv40', 8 | }); 9 | 10 | module.exports = { cloudinary }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/models/models.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | 3 | const PG_URI = 'postgres://gsuhxhlq:eY1yIjNdQnykWHWayH-_9WcqQzfxjt0o@raja.db.elephantsql.com:5432/gsuhxhlq'; 4 | 5 | const pool = new Pool({ 6 | connectionString: PG_URI, 7 | }); 8 | 9 | module.exports = { 10 | query: (text, params, callback) => { 11 | console.log('executed query', text); 12 | return pool.query(text, params, callback); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /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 | ); -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /client/components/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Card} from 'react-bootstrap'; 3 | 4 | export default function Profile(props) { 5 | return ( 6 |
7 | {/*

Profile

*/} 8 | {/* */} 9 | 10 | 11 | 12 | {props.username} 13 | 14 | Hi, my name is {props.firstname} {props.lastname}! 15 | 16 | 17 | {/* */} 18 |
19 | ); 20 | } -------------------------------------------------------------------------------- /server/controllers/cookieController.js: -------------------------------------------------------------------------------- 1 | const cookieController = {}; 2 | //set new cookie from JWT 3 | cookieController.setSSIDCookie = (req, res, next) => { 4 | res.cookie('user', res.locals.token, { httpOnly: true }); 5 | return next(); 6 | }; 7 | //check if user is logged in (has cookie) - if not return 401 error 8 | cookieController.isLoggedIn = (req, res, next) => { 9 | if (req.cookies.user) { 10 | return next(); 11 | } else { 12 | return next({ 13 | log: `User is not logged in`, 14 | code: 401, 15 | message: { err: "User is not logged in." }, 16 | }); 17 | } 18 | }; 19 | //cookie removal 20 | cookieController.removeCookie = (req, res, next) => { 21 | res.clearCookie('user'); 22 | return next(); 23 | } 24 | 25 | module.exports = cookieController; 26 | -------------------------------------------------------------------------------- /__tests__/routes.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const app = require('../server/server.js'); 3 | 4 | 5 | describe('Serve static files', () => { 6 | it('index.html', async(done) => { 7 | const res = await request(app) 8 | .get('/') 9 | expect(res.status).toBe(200); 10 | expect(res.headers['content-type']).toContain('text/html'); 11 | done(); 12 | }); 13 | 14 | it('bundle.js', async(done) => { 15 | const res = await request(app) 16 | .get('/dist/bundle.js') 17 | expect(res.status).toBe(200); 18 | expect(res.headers['content-type']).toContain('application/javascript'); 19 | done(); 20 | }); 21 | 22 | it('404 catch all handler', async(done) => { 23 | const res = await request(app) 24 | .get('/spiderman') 25 | expect(res.status).toBe(404); 26 | done(); 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 | let feedEvents = []; 7 | //creates events for each event in feed 8 | 9 | if (props.events && props.events.length > 0) { 10 | feedEvents = props.events.map((event, index) => { 11 | return 22 | }) 23 | } 24 | 25 | 26 | return ( 27 |
28 | {feedEvents} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /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 | //creates attendee component for each attendee in list 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/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Social Scrapbook 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const cookieParser = require('cookie-parser'); 4 | 5 | const app = express(); 6 | const apiRouter = require('./routers/api'); 7 | 8 | // BODY PARSERS & COOKIE PARSER 9 | app.use(express.json({ limit: '50mb' })); 10 | app.use(express.urlencoded({ limit: '50mb', extended: true })); 11 | app.use(cookieParser()); 12 | 13 | // SERVE UP STATIC FILES 14 | app.use('/dist', express.static(path.join(__dirname, '../dist'))); 15 | 16 | // API ROUTER 17 | app.use('/api', apiRouter); 18 | 19 | // SERVE INDEX.HTML ON THE ROUTE '/' 20 | app.get('/', (req, res) => { 21 | res.sendFile(path.join(__dirname, '../client/index.html')); 22 | }); 23 | 24 | // HANDLING UNKNOWN URLS 25 | app.use('*', (req, res) => { 26 | res.status(404).send('URL path not found'); 27 | }); 28 | 29 | // ERROR HANDLER 30 | app.use((err, req, res, next) => { 31 | res.status(401).send(err.message); // WHAT IS FRONT-END EXPECTING? JSON OR STRING? 32 | }); 33 | 34 | //app.listen(3000); //listens on port 3000 -> http://localhost:3000/ 35 | app.listen(process.env.PORT || 3000); 36 | module.exports = app; 37 | -------------------------------------------------------------------------------- /client/components/InviteUser.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Modal, Button, Form, Card } from 'react-bootstrap'; 3 | import axios from 'axios'; 4 | 5 | export default function InviteUser(props) { 6 | 7 | const [response, setResponse] = useState(false); 8 | 9 | const queryData = {}; 10 | queryData.userid = props.user.userid 11 | queryData.username = props.user.username 12 | queryData.eventid = props.event.eventid 13 | queryData.eventtitle = props.event.eventtitle 14 | queryData.eventdate = props.event.eventdate 15 | queryData.eventstarttime = props.event.eventstarttime 16 | queryData.eventendtime = props.event.eventendtime 17 | queryData.eventdetails = props.event.eventdetails 18 | queryData.eventlocation = props.event.eventlocation 19 | 20 | const handleClickInvite = () => { 21 | console.log(queryData) 22 | setResponse(true) 23 | axios.post('/api/invite', queryData) 24 | } 25 | 26 | return ( 27 |
28 | {props.user.firstname} {props.user.lastname} 29 | 30 | {!response && 31 | 32 | } 33 | {response && 34 | 35 | } 36 | 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /client/components/Inbox.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import axios from "axios"; 3 | import InboxItem from './InboxItem.jsx' 4 | 5 | export default function Inbox(props) { 6 | const {username, userid} = props.user 7 | 8 | const [inviteData, setInviteData] = useState([]); 9 | const [loading, setLoading] = useState(true); 10 | 11 | useEffect(() => { 12 | axios.post('/api/inviteListGet', { 13 | userid: userid 14 | }) 15 | .then((res)=>{ 16 | console.log('response from InviteListGet') 17 | console.log(res.data.invites) 18 | const response = res.data.invites; 19 | const eventNames = []; 20 | response.forEach((el)=>{ 21 | eventNames.push(el.eventtitle) 22 | }) 23 | setLoading(false) 24 | setInviteData(eventNames); 25 | }) 26 | }, []); 27 | 28 | if (loading) { 29 | return ( 30 |
31 | Loading... 32 |
33 | ) 34 | } else { 35 | const eventList = inviteData.map((el, i)=>{ 36 | return ( 37 | 38 | ) 39 | }) 40 | return ( 41 |
42 | {inviteData[0] && eventList} 43 | {!inviteData[0] && 44 |
You have no pending invitations
45 | } 46 |
47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/components/Invite.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Modal, Button, Form, Card } from 'react-bootstrap'; 3 | import axios from 'axios'; 4 | import InviteUser from './InviteUser.jsx' 5 | 6 | export default function Invite(props) { 7 | const [show, setShow] = useState(true); 8 | const [friends, setFriends] = useState([]); 9 | const [friendsData, setFriendsData] = useState([]); 10 | 11 | const handleClickClose = () => { 12 | setShow(false); 13 | setTimeout(() => { 14 | props.setInviteView(false); 15 | }, 250) 16 | }; 17 | 18 | useEffect(() => { 19 | axios.post('/api/inviteFilter', { eventtitle: props.eventtitle }) 20 | .then((res)=>{ 21 | const resUsers = res.data.users 22 | setFriendsData(res.data.users) 23 | setFriends(resUsers.map((el, i)=>{ 24 | return ( 25 | 26 | ) 27 | })) 28 | }) 29 | }, []); 30 | 31 | return ( 32 |
33 | 34 | 35 | Invite Friends to this Event! 36 | 37 | 38 |
39 |
40 | {friends} 41 |
42 |
43 |
44 |
45 |
46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /client/components/InboxItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import axios from "axios"; 3 | 4 | export default function InboxItem(props) { 5 | 6 | const [showItem, setshowItem] = useState(true); 7 | const username = props.username 8 | 9 | const handleClickAttend = (eventtitle) => { 10 | axios.post('/api/inviteAttend', { 11 | username, eventtitle 12 | }) 13 | setshowItem(false) 14 | alert('You are attending!') 15 | } 16 | 17 | const handleClickDecline = (eventtitle) => { 18 | axios.post('/api/inviteDecline', { 19 | username, eventtitle 20 | }) 21 | setshowItem(false) 22 | alert('You have declined invitation') 23 | } 24 | 25 | return ( 26 |
27 | {showItem && 28 |
29 |
30 | {/* add event photo */} 31 | Event Name: 32 | {props.el} 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | } 44 |
45 | ) 46 | } -------------------------------------------------------------------------------- /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 | '1044493719740-9ljvllb77dqr2tjhkcj55d54ec3po465.apps.googleusercontent.com', 9 | 'XmT_P0pI9WBss35cvjMMJq8u', 10 | 'http://localhost:3000/api/login/google' 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 | //creates Oauth token 30 | loginController.afterConsent = (req, res, next) => { 31 | 32 | const oauth2Client = new google.auth.OAuth2( 33 | '1044493719740-9ljvllb77dqr2tjhkcj55d54ec3po465.apps.googleusercontent.com', 34 | 'XmT_P0pI9WBss35cvjMMJq8u', 35 | 'http://localhost:3000/api/login/google' 36 | ); 37 | 38 | oauth2Client.getToken(req.query.code) 39 | .then(data => { 40 | const { tokens } = data; 41 | oauth2Client.setCredentials(tokens); 42 | res.locals.token = tokens.id_token; 43 | return next(); 44 | }) 45 | .catch(err => { 46 | if (err) console.log('afterConsent .catch block: ', err) 47 | }) 48 | }; 49 | 50 | module.exports = loginController; 51 | -------------------------------------------------------------------------------- /client/components/CoverPhoto.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faWindowClose, faArrowCircleUp, faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons' 5 | import { Button } from 'react-bootstrap'; 6 | 7 | export default function CoverPhoto(props) { 8 | 9 | const [showHud, setShowHud] = useState(false); 10 | const [photoIndex, setPhotoIndex] = useState(0); 11 | 12 | function nextPhoto() { 13 | if (props.eventphotos[photoIndex + 1]) { 14 | setPhotoIndex(photoIndex+1); 15 | } 16 | } 17 | 18 | function prevPhoto() { 19 | if (props.eventphotos[photoIndex - 1]) { 20 | setPhotoIndex(photoIndex - 1); 21 | } 22 | } 23 | 24 | return ( 25 |
setShowHud(true)} 28 | onMouseLeave={() => setShowHud(false)} 29 | > 30 | {showHud && (
31 |
32 | 33 | 34 | 35 | { 39 | prevPhoto(); 40 | props.deletePhoto(props.eventtitle, photoIndex, props.eventphotos[photoIndex]); 41 | }} 42 | /> 43 |
44 |
)} 45 | eventpic 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /scratch-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 | 13 | CREATE TABLE events 14 | ( 15 | "eventid" SERIAL PRIMARY KEY, 16 | "eventtitle" varchar NOT NULL CHECK ( eventtitle <> ''), 17 | "eventdate" date NOT NULL, 18 | "eventstarttime" time NOT NULL, 19 | "eventendtime" time NOT NULL, 20 | "eventlocation" varchar NOT NULL CHECK ( eventlocation <> ''), 21 | "eventdetails" varchar NOT NULL CHECK ( eventdetails <> ''), 22 | "eventownerid" bigint NOT NULL, 23 | "eventownerusername" varchar NOT NULL, 24 | "eventmessages" varchar ARRAY, 25 | UNIQUE 26 | ( eventtitle ), 27 | FOREIGN KEY 28 | (eventownerid) REFERENCES users 29 | (userid), 30 | FOREIGN KEY 31 | (eventownerusername) REFERENCES users 32 | (username) 33 | ); 34 | 35 | SELECT setval('events_eventid_seq', 1, false); 36 | 37 | CREATE TABLE usersandevents 38 | ( 39 | "uselessid" serial PRIMARY KEY, 40 | "userid" bigint NOT NULL, 41 | "username" varchar NOT NULL, 42 | "eventid" bigint NOT NULL, 43 | "eventtitle" varchar NOT NULL, 44 | "eventdate" varchar NOT NULL, 45 | "eventstarttime" varchar NOT NULL, 46 | "eventendtime" varchar NOT NULL, 47 | "eventdetails" varchar NOT NULL, 48 | "eventlocation" varchar NOT NULL, 49 | UNIQUE (username, eventtitle), 50 | FOREIGN KEY ( userid ) REFERENCES users ( userid ), 51 | FOREIGN KEY ( eventid ) REFERENCES events ( eventid ) 52 | ); 53 | 54 | SELECT setval('usersandevents_uselessid_seq', 1, false); -------------------------------------------------------------------------------- /client/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { Navbar, Nav, Button } from 'react-bootstrap'; 2 | import React, { useState, useEffect } from 'react'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faGoogle } from '@fortawesome/free-brands-svg-icons' 5 | import { faFeatherAlt, faInbox } from '@fortawesome/free-solid-svg-icons' 6 | import Inbox from './Inbox.jsx' 7 | 8 | 9 | export default function OurNavbar({ loggedIn, profilePhoto, user }) { 10 | 11 | // use react hook for state to render conditionally 12 | const [inbox, setInbox] = useState(false); 13 | // change state after button click 14 | const handleClickInbox = () => { 15 | setInbox(!inbox); 16 | } 17 | 18 | 19 | return ( 20 | 21 | Social Scrapbook 2.0 22 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /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({ content }) { 5 | const [cont, setCont] = useState(content); 6 | const [comment, setComment] = useState("") 7 | 8 | let messages = []; 9 | if (cont) { 10 | messages = cont.map((message, index) => { 11 | return ( 12 |
13 |
14 | 15 |
16 |
17 |

{message.firstname} {message.lastname}

18 |

{message.text}

19 |

{message.time}

20 |
21 |
22 | ) 23 | }); 24 | } else { 25 | setCont([]) 26 | } 27 | //handles change to comment - updates the state 28 | const handleChange = (e) => { 29 | setComment(e.target.value) 30 | } 31 | //handles submit event - creates time stamp - does not submit to database....yet 32 | function handleCommentSubmit(e) { 33 | e.preventDefault(); 34 | const date = new Date(); 35 | const newContent = cont.concat([{ text: comment, time: date.toTimeString() }]) 36 | setCont(newContent) 37 | //clear form data 38 | document.getElementsByName('comment-form')[0].reset(); 39 | } 40 | 41 | return ( 42 |
43 |

Comments

44 |
45 | {messages} 46 |
47 |
48 | 49 | Add a Comment: 50 | 51 | 52 | 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /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 | node: { 69 | fs: 'empty', 70 | http2: 'empty' 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /client/components/EditEvent.jsx: -------------------------------------------------------------------------------- 1 | import React, {Fragment, useState} from 'react' 2 | 3 | const EditEvent = (props) => { 4 | const [eventtitle, setEventTitle] = useState(props.eventtitle) 5 | const [eventdetails, setEventDetails] = useState(props.eventdetails) 6 | const [eventlocation, setEventLocation] = useState(props.eventlocation) 7 | 8 | const updateEvent = async(e) => { 9 | e.preventDefault() 10 | try { 11 | const body = {eventtitle, eventdetails, eventlocation, } 12 | const response = await fetch(`api/events/${props.eventid}`, { 13 | method:'PUT', 14 | headers: {'Content-Type':'application/json'}, 15 | body:JSON.stringify(body) 16 | } 17 | 18 | ) 19 | props.editEvent(eventtitle, eventdetails, eventlocation, props.eventtitle) 20 | 21 | } catch (err) { 22 | console.error(err.message) 23 | } 24 | } 25 | 26 | return ( 27 | 31 | 32 | 33 |
34 |
35 |
36 | 37 | 38 |
39 |

Edit Event

40 | 41 | 42 |
43 | 44 | 45 |
46 | Edit Event Title 47 | setEventTitle(e.target.value)} /> 48 | Edit Event Details 49 | setEventDetails(e.target.value)} /> 50 | Edit Event Location 51 | setEventLocation(e.target.value)} /> 52 | 53 |
54 | 55 | 56 |
57 | 58 |
59 | 60 |
61 |
62 |
63 |
) 64 | } 65 | 66 | export default EditEvent 67 | -------------------------------------------------------------------------------- /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 --detectOpenHandles" 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 | "file-loader": "^6.0.0", 32 | "file-system": "^2.2.2", 33 | "html-webpack-plugin": "^4.3.0", 34 | "jest": "^26.6.3", 35 | "node-sass": "^4.14.1", 36 | "nodemon": "^2.0.4", 37 | "sass": "^1.26.10", 38 | "sass-loader": "^9.0.3", 39 | "style-loader": "^1.2.1", 40 | "supertest": "^6.1.3", 41 | "url-loader": "^4.1.0", 42 | "webpack": "^4.44.1", 43 | "webpack-cli": "^3.3.12", 44 | "webpack-dev-server": "^3.11.0", 45 | "webpack-hot-middleware": "^2.25.0" 46 | }, 47 | "dependencies": { 48 | "@fortawesome/fontawesome-svg-core": "^1.2.30", 49 | "@fortawesome/free-brands-svg-icons": "^5.14.0", 50 | "@fortawesome/free-solid-svg-icons": "^5.14.0", 51 | "@fortawesome/react-fontawesome": "^0.1.11", 52 | "axios": "^0.21.1", 53 | "bcrypt": "^5.0.0", 54 | "bcryptjs": "^2.4.3", 55 | "bootstrap": "^4.5.2", 56 | "cloudinary": "^1.22.0", 57 | "cloudinary-react": "^1.6.6", 58 | "cookie-parser": "^1.4.5", 59 | "dotenv": "^8.2.0", 60 | "express": "^4.17.1", 61 | "googleapis": "^59.0.0", 62 | "jsonwebtoken": "^8.5.1", 63 | "jwt-decode": "^2.2.0", 64 | "mini-css-extract-plugin": "^0.10.0", 65 | "node-fetch": "^2.6.0", 66 | "pg": "^8.3.0", 67 | "react": "^16.13.1", 68 | "react-bootstrap": "^1.3.0", 69 | "react-datetime-picker": "^3.0.2", 70 | "react-dom": "^16.13.1", 71 | "react-hot-loader": "^4.12.21", 72 | "react-router": "^5.2.0", 73 | "react-router-dom": "^5.2.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOCIAL SCRAPBOOK 2 | 3 | *** SETUP / INSTALLATION *** 4 | 1. Install dependencies 5 | [ ] Install your npm dependencies: run npm install in your terminal 6 | Start application (see note below) 7 | [ ] To start your node server and compile the boilerplate React application, run the following: npm run dev-mac OR 8 | npm run dev-win 9 | [ ] To 'access' your React application in the browser, visit: http://localhost:8080/ 10 | 11 | 2. Note: while the React app runs on http://localhost:8080, our server is going to be running on http://localhost:3000 so if you are planning to test with Postman instead of (or in addition to) using the React app, send your Postman requests to http://localhost:3000. 12 | 13 | 3. *** PLEASE NOTE *** THE DEV-SERVER MUST RUN ON LOCALHOST:8080. This is hard-coded into our webpack-config on line 47 if you'd like to change that and also hard-coded into api.js on line 29. Because the dev-server is forced to run on localhost:8080 at the moment, if you have a Live Share session open or any other app that runs on localhost:8080 before you run the dev-server, IT WILL GLITCH OUT. 14 | 15 | *** SETTING UP OATH *** 16 | To start the project, you will have to set up your google cloud platform. 17 | 18 | 1. Please go to https://console.cloud.google.com/ 19 | 2. On the left tap, please click APIs & Services 20 | 3. Click Credentials 21 | 4. You will see + Create Credentials 22 | 5. Click OAuth ClientID 23 | 6. Click Web Application 24 | 7. Name w/e you want 25 | 8. Authorized Javascript Origins: http://localhost:3000 26 | 9. Authorized Redirect URIs: http://localhost:3000/api/login/google 27 | 10. Save it! 28 | 29 | *** SAVE IT! *** 30 | 31 | 1. Go to OAuth consent screen 32 | 2. Set up your name 33 | 3. Scopes for Google APIs should have 34 | - email 35 | - profile 36 | - openid 37 | 4. save it! 38 | *** SAVE IT! *** 39 | 40 | *** Now go to server/controllers/loginController.js *** 41 | and change your client id and client secret in two places in line 7 and 32 42 | const oauth2Client = new google.auth.OAuth2( 43 | 'Client_ID', 44 | 'Client_Secret', 45 | 'http://localhost:3000/api/login/google' 46 | ); 47 | 48 | *** SETTING UP THE DATABASE *** 49 | 1. Set up an ElephantSQL database in the cloud and create a new instance. Copy 50 | 51 | 2. Set up your database connection 52 | Add your connection URL to the ElephantSQL database into PG_URI in models.js (line 3) - you will be using this connection string to connect to the database via a pool. 53 | 54 | 3. Run the following command in your terminal to create empty tables in the database (omit the carrots). This will create 3 empty tables, as specified in the file: 55 | psql -d -f scratch-project_postgres_create.sql 56 | 57 | -------------------------------------------------------------------------------- /client/components/SearchEvent.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 { faSearchPlus } from '@fortawesome/free-solid-svg-icons' 7 | import { Modal, Button, Form, Card } from 'react-bootstrap'; 8 | import axios from 'axios'; 9 | 10 | export default function SearchEvent({ searchEvent, events }) { 11 | /* Form data */ 12 | const initialFormData = Object.freeze({ 13 | title: "", 14 | description: "" 15 | }); 16 | 17 | const [formData, updateFormData] = React.useState(initialFormData); 18 | const [results, updateResults] = useState([]); 19 | const [show, setShow] = useState(false); 20 | const [eventData, setEventData] = useState([]); 21 | 22 | //filters list of events as the user types in 23 | const handleChange = (e) => { 24 | const regex = new RegExp(e.target.value.trim(), "gi"); 25 | const userEventTitles = events.map(event => event.eventtitle) 26 | updateResults(eventData.filter((event) => event.eventtitle.match(regex) && !userEventTitles.includes(event.eventtitle))) 27 | console.log(eventData); 28 | }; 29 | //pass the added search event back to the main container 30 | const handleSubmit = (e, event) => { 31 | e.preventDefault(); 32 | searchEvent(event); 33 | handleClose(); 34 | }; 35 | 36 | const handleClose = () => setShow(false); 37 | const handleShow = () => { 38 | // Grabs all events from database 39 | axios.get('/api/events') 40 | .then(res => { 41 | setEventData(res.data); 42 | }) 43 | setShow(true); 44 | } 45 | 46 | //generates a list of events on load using fetch 47 | const btnResults = results.map(event => { 48 | return ( 49 | 50 | ); 51 | }) 52 | 53 | 54 | 55 | return ( 56 |
57 |
58 | 59 |

Search Events

60 |
61 | 62 | 63 | 64 | Search for an Event 65 | 66 | 67 | 68 |
69 | 70 | Please enter the event name below. 71 | 72 | 73 |
74 | {btnResults} 75 | 76 |
77 | 78 |
79 |
80 |
81 |
82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /server/controllers/fileController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models.js"); 2 | const queries = require("../utils/queries"); 3 | const jwtDecode = require('jwt-decode'); 4 | const { serviceusage } = require("googleapis/build/src/apis/serviceusage"); 5 | const e = require("express"); 6 | const fileController = {}; 7 | 8 | fileController.createUser = (req, res, next) => { // ADD BACK ASYNC IF YOU TURN ON THE TRY / CATCH / AWAIT 9 | const decoded = jwtDecode(res.locals.token); 10 | 11 | const { email, given_name, family_name, picture } = decoded; 12 | 13 | const queryString1 = queries.userInfo; 14 | const queryValues1 = [email]; 15 | 16 | const queryString2 = queries.addUser; 17 | const queryValues2 = [email, given_name, family_name, picture]; 18 | 19 | db.query(queryString1, queryValues1) 20 | .then(data => { 21 | console.log('whole data:', data); 22 | if (!data.rows.length) { 23 | console.log('data.rows is empty') 24 | db.query(queryString2, queryValues2) 25 | .then(data => { 26 | res.locals.username = data.rows[0].username; // is this superfluous? 27 | console.log('NEW USER: ', res.locals.username); 28 | return next(); 29 | }) 30 | .catch(err => { 31 | return next({ 32 | log: `Error occurred with queries.addUser OR fileController.createUser middleware: ${err}`, 33 | message: { err: "An error occurred with adding new user to the database." }, 34 | }); 35 | }) 36 | } else { 37 | return next(); 38 | } 39 | }) 40 | .catch(err => { 41 | return next({ 42 | log: `Error occurred with queries.userInfo OR fileController.createUser middleware: ${err}`, 43 | message: { err: "An error occurred when checking user information from database." }, 44 | }); 45 | }); 46 | }; 47 | 48 | fileController.getUser = (req, res, next) => { 49 | let decoded; 50 | if(!res.locals.token) { 51 | decoded = jwtDecode(req.cookies.user) 52 | } else { 53 | decoded = jwtDecode(res.locals.token); 54 | } 55 | 56 | const { email } = decoded; 57 | 58 | let targetUser; 59 | if (req.query.userName) { 60 | targetUser = req.query.userName // this is in the event that user visits someone else' profile page 61 | } else { 62 | targetUser = email; 63 | } 64 | 65 | const queryString = queries.userInfo; 66 | const queryValues = [targetUser]; //user will have to be verified Jen / Minchan 67 | db.query(queryString, queryValues) 68 | .then(data => { 69 | console.log('data.rows[0]', data.rows[0]); 70 | res.locals.allUserInfo = data.rows[0]; 71 | return next(); 72 | }) 73 | .catch(err => { 74 | return next({ 75 | log: `Error occurred with queries.userInfo OR fileController.getUser middleware: ${err}`, 76 | message: { err: "An error occured with SQL or server when retrieving user information." }, 77 | }); 78 | }) 79 | }; 80 | 81 | fileController.verifyUser = (req, res, next) => { 82 | const decoded = jwtDecode(req.cookies.user); 83 | const { email } = decoded; 84 | 85 | if (email == req.query.userName) { 86 | return next(); 87 | } else { 88 | return next({ 89 | log: `Error occurred with fileController.verifyUser`, 90 | code: 401, 91 | message: { err: "Unauthorized Access." }, 92 | }) 93 | } 94 | } 95 | 96 | module.exports = fileController; 97 | -------------------------------------------------------------------------------- /client/components/Event.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import EventAttendees from './EventAttendees.jsx'; 3 | import Content from './Content.jsx'; 4 | import CoverPhoto from './CoverPhoto.jsx'; 5 | import { ListGroup, Container, Row, Jumbotron, Button, Form, Modal } from 'react-bootstrap'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 7 | import { faLocationArrow } from '@fortawesome/free-solid-svg-icons' 8 | import axios from 'axios'; 9 | import Invite from './Invite.jsx' 10 | import EditEvent from "./EditEvent.jsx" 11 | 12 | export default function Event(props) { 13 | 14 | const [eventpic, setEventpic] = useState(''); 15 | const [inviteView, setInviteView] = useState(false) 16 | 17 | const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 18 | const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 19 | const jsDate = new Date(props.eventdate); 20 | const jsHours = jsDate.getHours(); 21 | const americanHours = jsHours > 12 ? jsHours - 12 : jsHours; 22 | 23 | 24 | const humanReadableDate = `${days[jsDate.getDay()]}, ${months[jsDate.getMonth()]} ${jsDate.getDate()}`; 25 | const americanTime = `${americanHours}:${jsDate.getMinutes()} ${jsHours > 12 ? "PM" : "AM"}`; 26 | 27 | const handleClickInvite = () => { 28 | setInviteView(true) 29 | } 30 | 31 | const [show, setShow] = useState(false); 32 | const [previewSource, setPreviewSource] = useState(''); 33 | 34 | 35 | const handleSubmit = (e) => { 36 | e.preventDefault() 37 | // ADD PHOTO TO EVENT func 38 | props.updatePhoto(props.eventtitle, previewSource); 39 | handleClose(); 40 | }; 41 | 42 | const handlePhoto = (e) => { 43 | const file = e.target.files[0]; 44 | const reader = new FileReader(); 45 | reader.readAsDataURL(file); 46 | reader.onloadend = () => { 47 | setPreviewSource(reader.result); 48 | } 49 | } 50 | 51 | const handleClose = () => { 52 | setShow(false); 53 | setPreviewSource(''); 54 | } 55 | const handleShow = () => setShow(true); 56 | 57 | return ( 58 | <> 59 | 60 |
61 | 62 |
63 | 64 | 65 | 66 |
67 | 68 | 69 |

{props.eventtitle}

70 |
71 | {props.eventphotos[0] ? ( 72 | 73 | ) : ( 74 | 75 | )} 76 |
77 |

{humanReadableDate}

78 |

Location : {props.eventlocation}

79 |

{props.eventdetails}

80 |
81 |
82 | 83 | 84 | 88 | 89 | 90 |
91 | 92 | 93 | 94 | Add Photo 95 | 96 | 97 | 98 |
99 | 100 | 101 | Event Photo 102 | 103 | 104 | 105 | {previewSource && ( 106 | chosen 107 | )} 108 | 109 | 112 |
113 |
114 |
115 |
116 | {inviteView && } 117 | 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /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 } from 'react-bootstrap'; 8 | import regeneratorRuntime from "regenerator-runtime"; 9 | import Invite from "./Invite.jsx"; 10 | 11 | // import FormData from 'form-data'; 12 | 13 | export default function CreateEvent({ addEvent }) { 14 | /* Form data */ 15 | const initialFormData = Object.freeze({ 16 | eventtitle: "", 17 | eventlocation: "", 18 | eventdetails: "", 19 | }); 20 | 21 | const [formData, updateFormData] = React.useState(initialFormData); 22 | const [dateTime, onChange] = useState(new Date()); 23 | const [show, setShow] = useState(false); 24 | const [invite, setInvite] = useState(false); 25 | 26 | 27 | const [previewSource, setPreviewSource] = useState(''); 28 | 29 | //handles any change tot he form and updates the state 30 | const handleChange = (e) => { 31 | updateFormData({ 32 | ...formData, 33 | // Trimming any whitespace 34 | [e.target.name]: e.target.value.trim() 35 | }); 36 | }; 37 | //handles submit event - create date and time and append to the event object 38 | const handleSubmit = (e) => { 39 | e.preventDefault() 40 | const eventdate = dateTime.toDateString(); 41 | let time = dateTime.toTimeString(); 42 | let eventstarttime = time.split(" ")[0]; 43 | // ... submit to API or something 44 | addEvent({ ...formData, eventdate, eventstarttime, eventpic: previewSource }); 45 | handleClose(); 46 | setInvite(true) 47 | }; 48 | 49 | const handlePhoto = (e) => { 50 | const file = e.target.files[0]; 51 | // previewFile(file); 52 | const reader = new FileReader(); 53 | reader.readAsDataURL(file); 54 | reader.onloadend = () => { 55 | setPreviewSource(reader.result); 56 | } 57 | } 58 | 59 | // const previewFile = (file) => { 60 | // const reader = new FileReader(); 61 | // reader.readAsDataURL(file); 62 | // reader.onloadend = () => { 63 | // setPreviewSource(reader.result); 64 | // } 65 | // } 66 | 67 | 68 | // const uploadImage = async (base64EncodedImage) => { 69 | // try { 70 | // await fetch('/api/photo', { 71 | // method: 'POST', 72 | // body: JSON.stringify({ data: base64EncodedImage, eventtitle: formData.eventtitle}), 73 | // headers: { 74 | // 'Content-Type': 'application/json' 75 | // } 76 | // }) 77 | // } catch (err) { 78 | // console.log(err) 79 | // } 80 | // } 81 | 82 | 83 | 84 | 85 | 86 | const handleClose = () => { 87 | setShow(false); 88 | setPreviewSource(''); 89 | } 90 | const handleShow = () => { 91 | setShow(true) 92 | setInvite(false) 93 | }; 94 | 95 | 96 | 97 | return ( 98 |
99 | 100 |
101 | 102 |

Add Event

103 |
104 | 105 | 106 | 107 | Create New Event 108 | 109 | 110 | 111 |
112 | 113 | Event Title 114 | 115 | 116 | 117 | 118 | Location 119 | 120 | 121 | 122 | 123 | Event Description 124 | 125 | 126 | 127 | 128 | Event Photo 129 | 130 | 131 | 132 | {previewSource && ( 133 |
134 | chosen 135 |
136 | )} 137 | 138 | 139 | Start Date & Time 140 | 145 | 146 | 147 | 150 | 151 | 152 |
153 |
154 |
155 | {/* {invite && } */} 156 |
157 | ); 158 | } 159 | -------------------------------------------------------------------------------- /server/controllers/photoController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models"); 2 | const queries = require("../utils/queries"); 3 | 4 | const { cloudinary } = require('../utils/cloudinary.js'); 5 | 6 | const photoController = {}; 7 | 8 | photoController.uploadPhoto = (req, res, next) => { 9 | if (!req.body.eventpic) { 10 | return next(); 11 | } 12 | 13 | try { 14 | const fileStr = req.body.eventpic; 15 | const { eventtitle } = req.body; 16 | 17 | cloudinary.uploader.upload(fileStr, { upload_preset: 'social_scrapbook_2', public_id: `${eventtitle}`}, function(err, result) { 18 | res.locals.photoUrl = result.url; 19 | return next(); 20 | }); 21 | } catch (err) { 22 | console.log(err); 23 | return res.status(400).json({msg: 'Photo upload went wrong in api router middleware!'}) 24 | } 25 | } 26 | 27 | photoController.getPhoto = (req, res, next) => { 28 | const { eventtitle } = req.body; 29 | cloudinary.search 30 | .expression(`public_id: social_scrapbook_2/${eventtitle}`) 31 | .execute() 32 | .then(result => { 33 | res.locals.photoUrl = result.resources[0].url; 34 | return next(); 35 | }) 36 | .catch(err => { 37 | console.log(err); 38 | return next({err: "Error in photoController.getPhoto"}) 39 | }); 40 | } 41 | 42 | photoController.deleteCloudinary = async (req, res, next) => { 43 | console.log(req.body.url) 44 | const public_id = req.body.url.slice(req.body.url.indexOf('social')) 45 | console.log(public_id); 46 | const { eventtitle } = req.body; 47 | 48 | const response = await cloudinary.uploader.destroy(`${public_id}`); 49 | 50 | res.locals.cloudresponse = response; 51 | return next(); 52 | } 53 | 54 | // THIS WAS TO DELETE FROM EVENT TABLE... not in use anymore 55 | photoController.deleteFromSQL = (req, res, next) => { 56 | const { eventtitle } = req.body; 57 | const queryString = queries.deletePhoto; 58 | const queryValues = [eventtitle]; 59 | 60 | db.query(queryString, queryValues) 61 | .then(data => { 62 | return next(); 63 | }) 64 | .catch(err => { 65 | return next({ 66 | log: `Error occurred with queries.deletePhoto OR photoController.deleteFromSQL middleware: ${err}`, 67 | message: { err: "An error occured with SQL when retrieving events information." }, 68 | }); 69 | }) 70 | } 71 | 72 | // DELETE FROM EVENTPHOTOS TABLE 73 | photoController.deleteFromEventPhotosSQL = (req, res, next) => { 74 | const { eventtitle, url } = req.body; 75 | 76 | const queryString = queries.deleteSQLPhoto; 77 | const queryValues = [url]; 78 | 79 | console.log(queryString) 80 | console.log(queryValues); 81 | 82 | db.query(queryString, queryValues) 83 | .then(result => { 84 | console.log('delete from eventphotos result ', result) 85 | return next(); 86 | }) 87 | } 88 | 89 | // UPLOADS PHOTO TO CLOUDINARY WITH TAG ATTACHED 90 | photoController.uploadDummyPhoto = (req, res, next) => { 91 | console.log('uploadphoto') 92 | if (!req.body.eventpic) { 93 | return next(); 94 | } 95 | 96 | try { 97 | const fileStr = req.body.eventpic; 98 | const { eventtitle } = req.body; 99 | 100 | cloudinary.uploader.upload(fileStr, { upload_preset: 'social_scrapbook_2', tags: [eventtitle] }, function(err, result) { 101 | console.log('upload result with tags?? ', result) 102 | res.locals.photoUrl = result.url; 103 | return next(); 104 | }); 105 | } catch (err) { 106 | console.log(err); 107 | return res.status(400).json({msg: 'Photo upload went wrong in api router middleware!'}) 108 | } 109 | } 110 | 111 | // ADDS TO NEW JOIN TABLE FOR EVENT PHOTOS 112 | photoController.addDummyToSQL = (req, res, next) => { 113 | const { eventtitle } = req.body; 114 | const { photoUrl } = res.locals; 115 | 116 | const queryString = queries.addDummyPhoto; 117 | const queryValues = [eventtitle, photoUrl]; 118 | 119 | db.query(queryString, queryValues) 120 | .then(data => { 121 | console.log('response from sql when adding photo to join ', data); 122 | return next(); 123 | }) 124 | 125 | 126 | } 127 | 128 | // GETS PHOTO BY TAG FROM CLOUDINARY 129 | photoController.getDummyPhotoByTag = (req, res, next) => { 130 | // const { eventtitle } = req.body; 131 | const tag = res.locals.event.eventtitle; 132 | console.log('tag came through on back end ', tag) 133 | 134 | cloudinary.search 135 | .expression(`tags=${tag}`) 136 | .execute() 137 | .then(result => { 138 | console.log('back end response from tag search ', result.resources) 139 | res.locals.event.eventphotos = [result.resources[0].url] 140 | res.locals.photoUrl = result.resources[0].url; 141 | return next(); 142 | }) 143 | .catch(err => { 144 | console.log(err); 145 | return next({err: "Error in photoController.getPhoto"}) 146 | }); 147 | } 148 | 149 | //GET PHOTOS FROM JOIN TABLE IN EVENTPHOTOS BY EVENT 150 | photoController.getDummyPhotosSQL = (req, res, next) => { 151 | // const { tag } = req.params; 152 | const tag = res.locals.event.eventtitle; 153 | 154 | const queryString = queries.getDummyPhotos; 155 | const queryValues = [tag]; 156 | 157 | db.query(queryString, queryValues) 158 | .then(result => { 159 | console.log('result from db photos ! ', result.rows); 160 | const photos = result.rows.map(e => e.eventpic); 161 | res.locals.event.eventphotos = photos; 162 | res.locals.photoUrl = result.rows; 163 | return next(); 164 | }) 165 | } 166 | 167 | 168 | 169 | module.exports = photoController; 170 | -------------------------------------------------------------------------------- /server/controllers/inviteController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models"); 2 | const { getAllUsers } = require("../utils/queries"); 3 | const { addInvite } = require("../utils/queries"); 4 | const queries = require("../utils/queries"); 5 | const inviteController = {}; 6 | 7 | inviteController.userList = (req, res, next) => { 8 | db.query(getAllUsers) 9 | .then((data)=>{ 10 | if (!data.rows[0]) { 11 | res.locals.invite = []; 12 | } else { 13 | console.log('DATA ROWS FROM ALL USERS') 14 | console.log(data.rows) 15 | res.locals.invite = data.rows 16 | } 17 | next(); 18 | }) 19 | .catch(err => { 20 | return next({ 21 | log: `Error occurred with queries.getAllUsers OR inviteController.userList middleware: ${err}`, 22 | message: { err: "An error occured within request to get user list from SQL." }, 23 | }); 24 | }) 25 | } 26 | 27 | inviteController.pendingInvites = (req, res, next) => { 28 | const { eventtitle } = req.body; 29 | 30 | const queryString = queries.inviteListEventGet; 31 | const queryValues = [eventtitle]; 32 | 33 | db.query(queryString, queryValues) 34 | .then(data => { 35 | console.log(data.rows); 36 | res.locals.pendingInvites = data.rows; 37 | return next(); 38 | }) 39 | .catch(err => { 40 | return next({ 41 | log: `Error occurred with queries.inviteListEventGet OR inviteController.pendingInvites middleware: ${err}`, 42 | message: { err: "An error occured within request to get one event from SQL." }, 43 | }); 44 | }) 45 | } 46 | 47 | inviteController.filterUsers = async (req, res, next) => { 48 | res.locals.availableUsers = await res.locals.invite.filter(user => { 49 | for (let attendee of res.locals.thisEventAttendees) { 50 | if (Number(attendee.userid) === user.userid) { 51 | return false; 52 | } 53 | } 54 | for (let invitee of res.locals.pendingInvites) { 55 | if (Number(invitee.userid) === user.userid) { 56 | return false; 57 | } 58 | } 59 | return true; 60 | }) 61 | return next(); 62 | } 63 | 64 | inviteController.newInvite = (req, res, next) => { 65 | let {userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation} = req.body 66 | let values = [userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation]; 67 | db.query(addInvite, values) 68 | .then((data)=>{ 69 | console.log(data) 70 | }) 71 | .catch(err => { 72 | return next({ 73 | log: `Error occurred with queries.addInvite OR inviteController.newInvite middleware: ${err}`, 74 | message: { err: "An error occured within request to add invite to SQL." }, 75 | }); 76 | }) 77 | } 78 | 79 | inviteController.inviteListGet = (req, res, next) => { 80 | let {userid} = req.body 81 | let values = [userid]; 82 | db.query(queries.inviteListGet, values) 83 | .then((data)=>{ 84 | console.log(data.rows) 85 | console.log('got all users invites') 86 | res.locals.data = data.rows; 87 | next() 88 | }) 89 | .catch(err => { 90 | return next({ 91 | log: `Error occurred with queries.inviteListGet OR inviteController.inviteListGet middleware: ${err}`, 92 | message: { err: "An error occured within request to get data from SQL." }, 93 | }); 94 | }) 95 | } 96 | 97 | 98 | inviteController.getDatafromInvite = (req, res, next) => { 99 | let {username, eventtitle} = req.body; 100 | // do a query to get the relevant data from invite table, and then add said data to the eventsandusers 101 | let values = [username, eventtitle]; 102 | db.query(queries.inviteListGetOne, values) 103 | .then((data)=>{ 104 | console.log(data.rows[0]) 105 | console.log('got all data from specific event to response') 106 | res.locals.data = data.rows[0]; 107 | next() 108 | }) 109 | .catch(err => { 110 | return next({ 111 | log: `Error occurred with queries.inviteListGetOne OR inviteController.getDatafromInvite middleware: ${err}`, 112 | message: { err: "An error occured within request to get data from SQL." }, 113 | }); 114 | }) 115 | } 116 | 117 | inviteController.addInvitetoEvents = (req, res, next) => { 118 | let {userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation} = res.locals.data 119 | console.log('GOT DATABACK FROM INVITELIST GETONE') 120 | let values = [userid, username, eventid, eventtitle, eventdate, eventstarttime, eventstarttime, eventdetails, eventlocation]; 121 | 122 | db.query(queries.addUserToEvent, values) 123 | .then((data)=>{ 124 | next() 125 | }) 126 | .catch(err => { 127 | return next({ 128 | log: `Error occurred with queries.addUserToEvent OR inviteController.addInvitetoEvents middleware: ${err}`, 129 | message: { err: "An error occured within request to get data from SQL." }, 130 | }); 131 | }) 132 | } 133 | 134 | inviteController.removeFromInvite = (req, res, next) => { 135 | let {username, eventtitle} = req.body; 136 | let values = [username, eventtitle]; 137 | 138 | db.query(queries.inviteListRemove, values) 139 | .then((data)=>{ 140 | next() 141 | }) 142 | .catch(err => { 143 | return next({ 144 | log: `Error occurred with queries.inviteListRemove OR inviteController.removeFromInvite middleware: ${err}`, 145 | message: { err: "An error occured within request to get data from SQL." }, 146 | }); 147 | }) 148 | } 149 | 150 | 151 | 152 | 153 | module.exports = inviteController; 154 | -------------------------------------------------------------------------------- /server/utils/queries.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models.js"); // remove after testing 2 | 3 | const queries = {}; 4 | 5 | // GET ALL EVENTS 6 | queries.getAllEvents = ` 7 | SELECT * FROM events 8 | `; 9 | 10 | 11 | queries.getAttendeeEvents = ` 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 | // GET ALL USER'S PERSONAL INFO 24 | queries.userInfo = `SELECT * FROM users WHERE username=$1`; // const values = [req.query.id] 25 | 26 | // QUERY TO ADD USER 27 | queries.addUser = ` 28 | INSERT INTO users 29 | (username, firstname, lastname, profilephoto) 30 | VALUES($1, $2, $3, $4) 31 | RETURNING username 32 | ; 33 | `; 34 | 35 | // QUERY FOR WHEN USER CREATES EVENT 36 | queries.createEvent = ` 37 | INSERT INTO events 38 | (eventtitle, eventdate, eventstarttime, eventendtime, eventlocation, eventdetails, eventownerid, eventownerusername, eventmessages, eventpic) 39 | VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) 40 | RETURNING eventid 41 | ; 42 | `; 43 | 44 | // QUERY FOR WHEN USER CREATES EVENT WITHOUT PHOTO 45 | queries.createEventWithoutPhoto = ` 46 | INSERT INTO events 47 | (eventtitle, eventdate, eventstarttime, eventendtime, eventlocation, eventdetails, eventownerid, eventownerusername, eventmessages, eventpic) 48 | VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, NULL) 49 | RETURNING eventid 50 | ; 51 | `; 52 | 53 | // ADDS ALL CURRENT EVENTS TO USERSANDEVENTS 54 | queries.addNewEventToJoinTable = ` 55 | INSERT INTO usersandevents (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 56 | SELECT eventownerid, eventownerusername, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation FROM events 57 | WHERE eventid=$1 58 | RETURNING usersandevents; 59 | `; 60 | 61 | // USERS ADDS THEMSELVES TO OTHER PEOPLE'S EVENTS 62 | queries.addUserToEvent = `INSERT INTO usersandevents 63 | (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 64 | VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) 65 | RETURNING eventid 66 | ; 67 | `; 68 | 69 | // GRAB EVENT'S ATTENDEES 70 | queries.selectEventAttendees = `SELECT * FROM usersandevents WHERE eventtitle=$1`; 71 | 72 | // CLEAR ALL TABLES & DATA 73 | queries.clearAll = ` 74 | DROP TABLE usersandevents; 75 | DROP TABLE events; 76 | DROP TABLE users; 77 | `; 78 | 79 | // GET ALL USERS 80 | queries.getAllUsers = ` 81 | SELECT * FROM users 82 | ; 83 | `; 84 | 85 | // ADD ENTRY TO INVITE TABLE 86 | queries.addInvite = ` 87 | INSERT INTO invitelist 88 | (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 89 | VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) 90 | ; 91 | `; 92 | 93 | queries.userInvites = ` 94 | SELECT * FROM invitelist WHERE username=$1 95 | `; 96 | 97 | 98 | queries.inviteListGet = ` 99 | SELECT * FROM invitelist WHERE userid=$1 100 | `; 101 | 102 | queries.inviteListEventGet = ` 103 | SELECT * FROM invitelist WHERE eventtitle=$1 104 | ` 105 | 106 | 107 | queries.userEvents = ` 108 | SELECT * FROM events WHERE userid=$1 109 | `; 110 | 111 | queries.inviteListGetOne = ` 112 | SELECT * FROM invitelist WHERE username=$1 AND eventtitle=$2 113 | `; 114 | 115 | queries.inviteListRemove = ` 116 | DELETE FROM invitelist WHERE username=$1 AND eventtitle=$2 117 | `; 118 | 119 | queries.addUserToEventnoEnd = `INSERT INTO usersandevents 120 | (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventdetails, eventlocation) 121 | VALUES($1, $2, $3, $4, $5, $6, $7, $8) 122 | RETURNING eventid 123 | ; 124 | `; 125 | 126 | queries.getThisEvent =` 127 | SELECT * FROM events WHERE eventid=$1 128 | `; 129 | 130 | 131 | // DELETE SPECIFIC EVENTPIC URL FROM EVENT 132 | queries.deletePhoto = ` 133 | UPDATE events 134 | SET eventpic = NULL 135 | WHERE eventtitle = $1; 136 | `; 137 | 138 | queries.deleteUsersAndEvents = ` 139 | DELETE FROM usersandevents WHERE eventid=$1` 140 | 141 | queries.deleteInviteUsersAndEvents = ` 142 | DELETE FROM invitelist WHERE eventid=$1` 143 | 144 | //DELETE FROM EVENTS 145 | queries.deleteEvent = ` 146 | DELETE FROM events WHERE eventid=$1` 147 | 148 | //GET last id# to fill in 149 | // queries.getLastID = ` 150 | // SELECT * FROM events WHERE eventid= (SELECT max(eventid) FROM events)` 151 | 152 | // UPDATE EVENTPIC IN EVENT 153 | queries.updatePhoto = ` 154 | UPDATE events 155 | SET eventpic = $1 156 | WHERE eventtitle = $2; 157 | `; 158 | 159 | // GET ONE EVENT BY EVENTTITLE 160 | queries.getOneEvent = ` 161 | SELECT * FROM events 162 | WHERE eventtitle = $1; 163 | `; 164 | //Update EventPhotos -Darwin 165 | queries.updateEventPhotos = 166 | `UPDATE eventphotos SET eventtitle =$1 WHERE eventtitle = $2` 167 | 168 | queries.updateUsersAndEvents = 169 | `UPDATE usersandevents SET eventtitle =$1, eventdetails =$2, eventlocation =$3 WHERE eventid = $4` 170 | 171 | //EDIT FROM EVENTS 172 | 173 | queries.updateEvents = 174 | `UPDATE events SET eventtitle = $1, eventdetails =$2, eventlocation =$3 WHERE eventid = $4 ` 175 | 176 | // ADD PHOTO TO EVENTPHOTOS 177 | queries.addDummyPhoto = ` 178 | INSERT INTO eventphotos 179 | (eventtitle, eventpic) 180 | VALUES($1, $2); 181 | `; 182 | 183 | // GET PHOTOS FROM EVENTPHOTOS 184 | queries.getDummyPhotos = ` 185 | SELECT eventpic 186 | FROM eventphotos 187 | WHERE eventtitle = $1; 188 | `; 189 | 190 | // GET ALL PHOTOS FROM EVENTPHOTOS 191 | queries.getAllPhotos = ` 192 | SELECT * FROM eventphotos; 193 | `; 194 | 195 | //DELETE SPECIFIC PHOTO FROM EVENTPHOTOS 196 | queries.deleteSQLPhoto = ` 197 | DELETE FROM eventphotos 198 | WHERE eventpic = $1; 199 | `; 200 | 201 | 202 | // ADD PHOTO TO EVENTPHOTOS 203 | queries.addDummyPhoto = ` 204 | INSERT INTO eventphotos 205 | (eventtitle, eventpic) 206 | VALUES($1, $2); 207 | `; 208 | 209 | // GET PHOTOS FROM EVENTPHOTOS 210 | queries.getDummyPhotos = ` 211 | SELECT eventpic 212 | FROM eventphotos 213 | WHERE eventtitle = $1; 214 | `; 215 | 216 | // GET ALL PHOTOS FROM EVENTPHOTOS 217 | queries.getAllPhotos = ` 218 | SELECT * FROM eventphotos; 219 | `; 220 | 221 | //DELETE SPECIFIC PHOTO FROM EVENTPHOTOS 222 | queries.deleteSQLPhoto = ` 223 | DELETE FROM eventphotos 224 | WHERE eventpic = $1; 225 | `; 226 | 227 | module.exports = queries; 228 | -------------------------------------------------------------------------------- /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 inviteController = require('../controllers/inviteController'); 9 | 10 | const photoController = require('../controllers/photoController'); 11 | 12 | const { cloudinary } = require('../utils/cloudinary.js'); 13 | 14 | // EXISING USER LOGIN 15 | 16 | router.get('/login', 17 | loginController.oAuth, 18 | (req, res) => { 19 | // res.send('ok'); 20 | return res.redirect(res.locals.url) 21 | }); 22 | 23 | router.get('/login/google', 24 | loginController.afterConsent, 25 | cookieController.setSSIDCookie, 26 | fileController.createUser, // if username already exists, return next() => getUser // if not, create user in SQL database 27 | // fileController.getUser, 28 | // eventController.getFullEvents, 29 | (req, res) => { 30 | // const responseObj = { 31 | // users: res.locals.allUserInfo, 32 | // events: res.locals.allEventsInfo 33 | // }; 34 | return res.redirect('http://localhost:8080/') 35 | }); 36 | 37 | // REVISIT WEBSITE AFTER LEAVING, OR VISITING SOMEONE ELSE'S PROFILE PAGE 38 | 39 | router.get('/info', 40 | cookieController.isLoggedIn, // this is really only is applicable for the same user 41 | fileController.getUser, 42 | eventController.allEvents, 43 | eventController.filterForUser, 44 | // eventController.getFullEvents, //ALL COMMENTED OUT OBSOLETE - KEPT IN CASE NEEDED LATER - REPLACED BY .allEvents and .filterForUser 45 | // eventController.getAllAttendees, 46 | // eventController.getUserDetail, 47 | // eventController.consolidation, 48 | (req, res) => { 49 | const responseObj = { 50 | users: res.locals.allUserInfo, 51 | events: res.locals.allEventsInfo, 52 | }; 53 | console.log('responseObj: ', responseObj); 54 | return res.status(200).json(responseObj); 55 | }); 56 | 57 | // LOGGING OUT 58 | 59 | router.use('/logout', // SWITCH THIS TO POST REQUEST!! 60 | cookieController.removeCookie, 61 | (req, res) => { 62 | return res.redirect('/'); 63 | }); 64 | 65 | // CREATE A NEW EVENT 66 | 67 | router.post('/create', 68 | // photoController.uploadPhoto, 69 | photoController.uploadDummyPhoto, 70 | fileController.verifyUser, 71 | fileController.getUser, 72 | eventController.createEvent, 73 | eventController.addNewEventToJoinTable, 74 | photoController.addDummyToSQL, 75 | (req, res) => { 76 | return res.status(200).json({newEvent: res.locals.newEvent, eventpic: res.locals.photoUrl}); 77 | }); 78 | 79 | // ADD USER TO AN EXISTING EVENT 80 | 81 | router.post('/add', 82 | fileController.getUser, 83 | eventController.verifyAttendee, 84 | eventController.addAttendee, 85 | (req, res) => { 86 | return res.status(200).json('User successfully added as attendee.'); 87 | }); 88 | 89 | router.get('/events', // SWITCH THIS TO A GET REQUEST!! 90 | eventController.allEvents, 91 | (req, res) => { 92 | return res.status(200).json(res.locals.allEventsInfo); 93 | } 94 | ) 95 | 96 | // UPLOAD A PHOTO TO CLOUDINARY API 97 | 98 | router.post('/photo', 99 | // photoController.uploadPhoto, 100 | photoController.uploadDummyPhoto, 101 | (req, res, next) => { 102 | return res.status(200).json(res.locals.eventpic) 103 | } 104 | ) 105 | 106 | // FETCH SPECIFIC PHOTO FROM CLOUDINARY API 107 | 108 | router.get('/photo', 109 | photoController.getPhoto, 110 | (req, res, next) => { 111 | res.status(200).json(res.locals.photoUrl); 112 | } 113 | ) 114 | 115 | // DELETE PHOTO FROM CLOUDINARY API 116 | 117 | router.delete('/photo', 118 | photoController.deleteCloudinary, 119 | // photoController.deleteFromSQL, 120 | photoController.deleteFromEventPhotosSQL, 121 | (req, res, next) => { 122 | res.status(200).json({ }); 123 | }) 124 | 125 | router.put('/photo', 126 | // photoController.uploadPhoto, 127 | photoController.uploadDummyPhoto, 128 | // eventController.updatePhoto, 129 | photoController.addDummyToSQL, 130 | eventController.getOneEvent, 131 | // photoController.getDummyPhotoByTag, 132 | photoController.getDummyPhotosSQL, 133 | (req, res, next) => { 134 | res.status(200).json({ event : res.locals.event }) 135 | }) 136 | 137 | // FETCH ALL PHOTOS FROM CLOUDINARY API 138 | 139 | router.get('/allphotos', async (req, res, next) => { 140 | const { resources } = await cloudinary.search 141 | .expression('folder: social_scrapbook_2') 142 | .sort_by('public_id', 'desc') 143 | .max_results(30) 144 | .execute(); 145 | const publicIds = resources.map(file => file.public_id); 146 | 147 | res.status(200).json({ ids: publicIds }); 148 | }) 149 | 150 | // GET ALL USERS FOR INVITE LIST 151 | router.post('/inviteListGet', 152 | inviteController.inviteListGet, 153 | (req, res) =>{ 154 | res.status(202).json({invites: res.locals.data}) 155 | }) 156 | 157 | router.post('/inviteFilter', 158 | inviteController.userList, 159 | eventController.getAttendeesOneEvent, 160 | inviteController.pendingInvites, 161 | inviteController.filterUsers, 162 | (req, res) => { 163 | res.status(202).json({users: res.locals.availableUsers}) 164 | }) 165 | 166 | router.post('/invite', 167 | inviteController.newInvite, 168 | (req, res) =>{ 169 | res.status(202) 170 | }) 171 | 172 | router.post('/inviteAttend', inviteController.getDatafromInvite, inviteController.addInvitetoEvents, inviteController.removeFromInvite, (req, res) =>{ 173 | res.status(202) 174 | }) 175 | 176 | router.post('/inviteDecline', inviteController.removeFromInvite, (req, res) =>{ 177 | res.status(202) 178 | }) 179 | 180 | //DELETE an event 181 | router.delete('/events/:id', 182 | 183 | eventController.deleteUsersAndEvents, 184 | eventController.deleteInviteEvent, 185 | eventController.deleteEvent, 186 | (req,res) =>{ 187 | return res.status(200).json("User has been deleted") 188 | } 189 | ) 190 | 191 | //update 192 | 193 | router.put('/events/:id', 194 | // eventController.updateEventPhotos, 195 | 196 | eventController.updateUsersAndEvents, 197 | eventController.updateEvents, 198 | // eventController.allEvents, 199 | (req,res) => { 200 | return res.status(200).json("Events has been updated") 201 | } 202 | ) 203 | 204 | 205 | router.post('/dummy', 206 | photoController.uploadDummyPhoto, 207 | photoController.addDummyToSQL, 208 | (req, res, next) => { 209 | return res.status(200).json(res.locals.photoUrl); 210 | }) 211 | // photoController.getDummyPhotoByTag 212 | router.get('/dummy/:tag', 213 | photoController.getDummyPhotosSQL, 214 | (req, res, next) => { 215 | return res.status(200).json(res.locals.photoUrl); 216 | }) 217 | 218 | 219 | module.exports = router; 220 | -------------------------------------------------------------------------------- /client/components/MainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Profile from './Profile.jsx'; 3 | import EventsFeed from './EventsFeed.jsx'; 4 | import Navbar from './Navbar.jsx'; 5 | import axios from 'axios'; 6 | import { Card, Button, Col, Row, Container, Modal, Form } from 'react-bootstrap'; 7 | import AddSearchEvent from './AddSearchEvent.jsx'; 8 | 9 | // Implemented with hooks throughout 10 | export default function MainContainer() { 11 | 12 | const [userName, setUserName] = useState(""); 13 | const [user, setUser] = useState({}); 14 | const [events, setEvents] = useState([]); 15 | const [loggedIn, setLoggedIn] = useState(false); 16 | // pull user data after OAuth login - all variables are named from SQL DB columns 17 | useEffect(() => { 18 | axios.get(`/api/info?userName=${userName}`) 19 | .then((res) => { 20 | let userInfo = { 21 | userid: res.data.users.userid, 22 | username: res.data.users.username, 23 | firstname: res.data.users.firstname, 24 | lastname: res.data.users.lastname, 25 | profilephoto: res.data.users.profilephoto, 26 | } 27 | let eventsInfo = res.data.events; 28 | 29 | setUser(userInfo); 30 | setEvents(eventsInfo); 31 | setUserName(res.data.users.username); 32 | setLoggedIn(true); 33 | }) 34 | }, []); 35 | //updates username when a different user is selected 36 | function handleUserPageChange(username) { 37 | setUserName(username); 38 | } 39 | //handles the state change and posts to database on event creation 40 | function handleCreateEvent(event) { 41 | let { eventtitle, eventlocation, eventdate, eventstarttime, eventdetails, eventpic } = event; 42 | axios.post(`/api/create?userName=${userName}`, { eventtitle, eventlocation, eventdate, eventstarttime, eventdetails, eventpic }) 43 | .then((res) => { 44 | event.attendees = [{ 45 | username: user.username, 46 | profilephoto: user.profilephoto 47 | }]; 48 | event.eventid = res.data.newEvent.eventid; 49 | const newEvents = [event].concat(events); 50 | setEvents(newEvents); 51 | }) 52 | } 53 | //handles the state change and posts to database on search event add 54 | function handleSearchEvent(event) { 55 | // ADD 56 | axios.post(`/api/add?eventtitle=${event.eventtitle}`) 57 | .then((res) => { 58 | event.attendees.push( 59 | { 60 | username: user.username, 61 | firstname: user.firstname, 62 | lastname: user.lastname, 63 | profilephoto: user.profilephoto 64 | }); 65 | const newEvents = [event].concat(events); 66 | setEvents(newEvents); 67 | }) 68 | } 69 | 70 | // handles delete 71 | function handleDeletePhoto(eventtitle, index, url) { 72 | const lessEvents = events.map(event => { // need to change this for albums (immediate delete) 73 | if (event.eventtitle === eventtitle) { 74 | const copy = event.eventphotos.slice(); 75 | console.log(copy) 76 | copy.splice(index, 1) 77 | console.log(copy) 78 | return {...event, eventphotos: copy} 79 | } else { 80 | return event; 81 | } 82 | }); 83 | 84 | const response = axios.delete('/api/photo', { 85 | data: { 86 | eventtitle, 87 | url, 88 | } 89 | }); 90 | 91 | setEvents(lessEvents); 92 | } 93 | // Delete Buttons for individual events 94 | 95 | // function handleDeleteEvent(id){ 96 | // const 97 | // } 98 | 99 | 100 | const deleteEvent = async (id) => { 101 | try{ 102 | 103 | console.log("THIS is the id youre deleting"+ id) 104 | const deleteEvent = await fetch(`api/events/${id}`, { 105 | method: "DELETE", 106 | }) 107 | console.log(deleteEvent) 108 | setEvents(events.filter(event => event.eventid !== id)) 109 | } 110 | catch(err){ 111 | console.error(err.message) 112 | } 113 | } 114 | 115 | // handles updating photos to existing events 116 | function handlePhotoUpdate(eventtitle, source) { 117 | // console.log('got into handle photo update main') 118 | // const updatedEvents = events.map(event => { 119 | // if (event.eventtitle === eventtitle) { 120 | // event.eventphotos.push(source); 121 | // } else return event 122 | // }); 123 | 124 | // setEvents(updatedEvents); 125 | 126 | 127 | axios.put('/api/photo', { 128 | eventtitle, 129 | eventpic: source 130 | }) 131 | .then(data => { 132 | const insertion = data.data.event; 133 | const updatedEvents = events.map(event => { 134 | if (event.eventtitle === eventtitle) { 135 | return insertion 136 | } else return event 137 | }); 138 | 139 | setEvents(updatedEvents); 140 | }); 141 | } 142 | 143 | //changes state once update has occured 144 | function handleEditEvent (eventtitle, eventdetails, eventlocation, ){ 145 | const editEvent = events.map(event => { 146 | if (event.eventtitle === oldeventtitle) { 147 | return {...event, eventtitle: eventtitle, eventdetails:eventdetails, eventlocation: eventlocation, } 148 | } else { 149 | return event; 150 | } 151 | }); 152 | 153 | setEvents(editEvent); 154 | } 155 | 156 | 157 | return ( 158 |
159 | 160 |
161 | 162 | {/* */} 163 | 164 | {/* 165 | */} 166 | 167 | 175 |
176 | {/* 177 | 178 | Add Photo 179 | 180 | 181 | 182 |
183 | 184 | 185 | Event Photo 186 | 187 | 188 | 189 | {previewSource && ( 190 | chosen 191 | )} 192 | 193 | 196 |
197 |
198 |
199 | 200 | 201 | Get Photo With Tag 202 | 203 | 204 | 205 |
206 | 207 | 208 | Use tag 209 | 210 | 211 | 212 | {previewSource && ( 213 | chosen 214 | )} 215 | 216 | 219 |
220 |
221 |
*/} 222 |
223 | ); 224 | } 225 | -------------------------------------------------------------------------------- /client/stylesheets/styles.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Dancing+Script&family=Montserrat&family=Julius+Sans+One&family=Baloo+Tamma+2&family=Poiret+One&display=swap'); 2 | 3 | body { 4 | color:rgb(77, 77, 77); 5 | } 6 | 7 | /* Navbar.jsx */ 8 | 9 | .myNavbar { 10 | // width: 90%; 11 | background-color: rgb(129, 216, 208); 12 | position: fixed; 13 | width: 100%; 14 | z-index: 1000; 15 | // margin-bottom: 30px; 16 | } 17 | 18 | .navButton { 19 | color: white; 20 | border: 1px solid white; 21 | } 22 | 23 | .brand { 24 | font-family: 'Dancing Script', cursive; 25 | font-size: 40px; 26 | margin-bottom: 10px; 27 | } 28 | 29 | .myNavbar img { 30 | border-radius: 4px; 31 | width: 38px; 32 | } 33 | 34 | .myNavbar .inboxIcon { 35 | display: flex; 36 | justify-content: center; 37 | align-items: center; 38 | margin-left: 10px; 39 | margin-right: 10px; 40 | } 41 | 42 | .inboxIconSvg { 43 | font-size: 1em; 44 | color: white; 45 | } 46 | 47 | .header { 48 | padding-top: 100px; 49 | } 50 | 51 | .col { 52 | display: flex; 53 | align-content: center; 54 | } 55 | 56 | .myCol { 57 | display: flex; 58 | justify-content: center; 59 | } 60 | 61 | .card { 62 | border: 0px; 63 | } 64 | 65 | .profile { 66 | display: flex; 67 | align-items: center; 68 | border: 1px rgb(157, 189, 211) solid; 69 | padding: 2em; 70 | padding-left: 80px; 71 | border-radius: 5px; 72 | box-shadow: 1px 1px 2px 1px rgb(129, 216, 208); 73 | background-color: rgb(250, 250, 250); 74 | font-family: 'Poiret One', cursive; 75 | } 76 | 77 | .cardContainer { 78 | border: 1px gainsboro solid; 79 | width: 150px; 80 | height: 150px; 81 | border-radius: 5px; 82 | box-shadow: 1px 1px 1px 1px gainsboro; 83 | text-align: center; 84 | padding-top: 25px; 85 | margin-top: 40px; 86 | margin-right: 25px; 87 | font-family: 'Baloo Tamma 2' 88 | } 89 | .cardContainer:hover{ 90 | // color: rgb(37, 13, 190); 91 | background-color: #9ed0d8; 92 | color: rgb(255, 255, 255); 93 | cursor: pointer; 94 | } 95 | 96 | .cardContainer p { 97 | padding-top: 20px; 98 | } 99 | 100 | .timePicker { 101 | margin-left: 10px; 102 | } 103 | 104 | .photoPreviewContainer { 105 | display: flex; 106 | justify-content: center; 107 | align-items: center; 108 | padding-bottom: 15px; 109 | } 110 | 111 | /* Event.jsx */ 112 | 113 | .event { 114 | border: 1px solid gainsboro; 115 | margin: 1em; 116 | padding: 1em; 117 | box-shadow: 1px 1px 1px 1px gainsboro; 118 | border-radius: 5px; 119 | background-color: rgb(250, 250, 250); 120 | } 121 | 122 | .eventButtons { 123 | padding: 1em; 124 | display: flex; 125 | justify-content: space-between; 126 | align-items: center; 127 | } 128 | 129 | .jumbotron { 130 | border: 1px solid rgb(129, 216, 208); 131 | box-shadow: 1px 1px 1px 1px gainsboro; 132 | } 133 | 134 | .eventJumbotron { 135 | text-align: center; 136 | font-family: 'Montserrat'; 137 | } 138 | 139 | .eventJumbotron h1 { 140 | font-family: 'Abril Fatface'; 141 | letter-spacing: 3px; 142 | font-size: 60px; 143 | margin: 0; 144 | } 145 | 146 | /* CoverPhoto.jsx */ 147 | 148 | .coverPhotoContainer { 149 | padding-top: 20px; 150 | padding-bottom: 20px; 151 | display: flex; 152 | justify-content: center; 153 | align-items: center; 154 | } 155 | 156 | .coverPhoto { 157 | width: 400px; 158 | } 159 | 160 | .coverPhoto img { 161 | width: 100%; 162 | height: auto; 163 | } 164 | 165 | @keyframes fadeIn { 166 | 0% { 167 | opacity:0; 168 | } 169 | 100% { 170 | opacity:1; 171 | } 172 | } 173 | 174 | 175 | .hudBackground { 176 | background-color: rgba(0, 0, 0, 0.4); 177 | position: absolute; 178 | height: 40px; 179 | width: 400px; 180 | animation: fadeIn 0.1s linear; 181 | } 182 | 183 | .newPhotoButton { 184 | float: left; 185 | margin-top: 12px; 186 | margin-left: 12px; 187 | cursor: pointer; 188 | color: white; 189 | } 190 | 191 | .leftPhotoButton { 192 | margin-top: 12px; 193 | cursor: pointer; 194 | color: white; 195 | margin-right: 30px; 196 | z-index: 500; 197 | } 198 | 199 | .rightPhotoButton { 200 | margin-top: 12px; 201 | cursor: pointer; 202 | color: white; 203 | z-index: 500; 204 | } 205 | 206 | .cancelButton { 207 | float: right; 208 | margin-top: 12px; 209 | margin-right: 12px; 210 | cursor: pointer; 211 | color: white; 212 | } 213 | 214 | /* EventAttendees.jsx */ 215 | 216 | .circular { 217 | width: 85px; 218 | height: 85px; 219 | border-radius: 50%; 220 | position: relative; 221 | overflow: hidden; 222 | border: 2px solid rgb(204, 204, 204); 223 | box-shadow: 2px 3px 6px #cacaca; 224 | margin: 10px; 225 | } 226 | .circular img { 227 | height: 85px; 228 | object-fit: contain; 229 | position: absolute; 230 | left: 50%; 231 | top: 50%; 232 | -webkit-transform: translate(-50%, -50%); 233 | -moz-transform: translate(-50%, -50%); 234 | -ms-transform: translate(-50%, -50%); 235 | transform: translate(-50%, -50%); 236 | } 237 | 238 | .attendeesContainer h5 { 239 | text-align: center; 240 | } 241 | 242 | .attendees { 243 | display: flex; 244 | justify-content: center; 245 | margin-bottom: 20px; 246 | } 247 | 248 | .attendeeInfo { 249 | text-align: center; 250 | font-size: 10px; 251 | } 252 | 253 | /* SearchEvent.jsx */ 254 | 255 | .searchResults { 256 | display: grid; 257 | } 258 | 259 | .searchResult { 260 | margin: 0.3em; 261 | } 262 | 263 | 264 | /* Content.jsx */ 265 | 266 | .userMessage { 267 | width: 50px; 268 | height: 50px; 269 | border-radius: 50%; 270 | position: relative; 271 | overflow: hidden; 272 | border: 1px solid rgb(204, 204, 204); 273 | box-shadow: 2px 3px 6px #cacaca; 274 | margin: 10px; 275 | } 276 | 277 | .userMessage img { 278 | height: 50px; 279 | object-fit: contain; 280 | position: absolute; 281 | left: 50%; 282 | top: 50%; 283 | -webkit-transform: translate(-50%, -50%); 284 | -moz-transform: translate(-50%, -50%); 285 | -ms-transform: translate(-50%, -50%); 286 | transform: translate(-50%, -50%); 287 | } 288 | .messageBox { 289 | display: flex; 290 | justify-content: left; 291 | } 292 | 293 | .messages p { 294 | font-size: 12px; 295 | padding: 0; 296 | margin: 0; 297 | } 298 | .messageName { 299 | font-weight: bold; 300 | } 301 | 302 | .message { 303 | padding: 10px; 304 | } 305 | 306 | 307 | /* rainbow divider */ 308 | 309 | $bg: white; 310 | $barsize: 15px; 311 | 312 | .hr { 313 | width: 80%; 314 | margin: 0 auto; 315 | height: 1px; 316 | display: block; 317 | position: relative; 318 | margin-bottom: 0em; 319 | padding: 2em 0; 320 | z-index: 1; 321 | 322 | &:after, 323 | &:before { 324 | content: ""; 325 | position: absolute; 326 | width: 100%; 327 | height: 1px; 328 | bottom: 50%; 329 | left: 0; 330 | } 331 | 332 | &:before { 333 | 334 | background: linear-gradient( 90deg, $bg 0%, $bg 50%, transparent 50%, transparent 100% ); 335 | background-size: $barsize; 336 | background-position: center; 337 | z-index: 1; 338 | 339 | } 340 | 341 | &:after { 342 | transition: opacity 0.3s ease, animation 0.3s ease; 343 | background: linear-gradient( 344 | to right, 345 | #62efab 5%, 346 | #F2EA7D 15%, 347 | #F2EA7D 25%, 348 | #FF8797 35%, 349 | #FF8797 45%, 350 | #e1a4f4 55%, 351 | #e1a4f4 65%, 352 | #82fff4 75%, 353 | #82fff4 85%, 354 | #62efab 95%); 355 | background-size: 200%; 356 | background-position: 0%; 357 | animation: bar 15s linear infinite; 358 | } 359 | 360 | @keyframes bar { 361 | 0% { background-position: 0%; } 362 | 100% { background-position: 200%; } 363 | } 364 | } 365 | 366 | .hr.anim { 367 | &:before { 368 | background: linear-gradient( 369 | 90deg, 370 | $bg 0%, $bg 5%, 371 | transparent 5%, transparent 10%, 372 | $bg 10%, $bg 15%, 373 | transparent 15%, transparent 20%, 374 | $bg 20%, $bg 25%, 375 | transparent 25%, transparent 30%, 376 | $bg 30%, $bg 35%, 377 | transparent 35%, transparent 40%, 378 | $bg 40%, $bg 45%, 379 | transparent 45%, transparent 50%, 380 | $bg 50%, $bg 55%, 381 | transparent 55%, transparent 60%, 382 | $bg 60%, $bg 65%, 383 | transparent 65%, transparent 70%, 384 | $bg 70%, $bg 75%, 385 | transparent 75%, transparent 80%, 386 | $bg 80%, $bg 85%, 387 | transparent 85%, transparent 90%, 388 | $bg 90%, $bg 95%, 389 | transparent 95%, transparent 100% ); 390 | 391 | background-size: $barsize * 10; 392 | background-position: center; 393 | z-index: 1; 394 | 395 | animation: bar 120s linear infinite; 396 | 397 | } 398 | 399 | &:hover { 400 | &:before { 401 | animation-duration: 20s; 402 | } 403 | &:after { 404 | animation-duration: 2s; 405 | } 406 | } 407 | } 408 | 409 | /* Inbox.jsx */ 410 | 411 | .inboxContainer { 412 | position: absolute; 413 | top: 136px; 414 | right: 40px; 415 | // background-color: rgb(129, 216, 208); 416 | background-color: #cf81d8; 417 | color: white; 418 | padding: 20px; 419 | border-radius: 20px; 420 | z-index: 100; 421 | } 422 | 423 | .inboxButtons { 424 | display: flex; 425 | align-content: center; 426 | justify-content: flex-end; 427 | } 428 | 429 | .inboxSubmitAttend { 430 | background-color: rgb(129, 216, 208); 431 | } 432 | 433 | .inboxSubmitDecline { 434 | background-color:lightcoral; 435 | } 436 | 437 | .inboxDetails { 438 | display: flex; 439 | justify-content: flex-start; 440 | min-width: 270px; 441 | } 442 | 443 | .inboxItem { 444 | display: flex; 445 | } 446 | 447 | /* InviteUser.jsx */ 448 | 449 | .inviteFriendContainer { 450 | margin-top: 5px; 451 | margin-bottom: 5px; 452 | display: flex; 453 | align-items: center; 454 | justify-content: space-between; 455 | border-top: 1px grey solid; 456 | padding-top: 5px; 457 | } 458 | -------------------------------------------------------------------------------- /server/controllers/eventController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/models"); 2 | const queries = require("../utils/queries"); 3 | const eventController = {}; 4 | 5 | eventController.getFullEvents = (req, res, next) => { 6 | 7 | const queryString = queries.userEvents; 8 | const queryValues = [res.locals.allUserInfo.userid]; //user will have to be verified Jen / Minchan 9 | db.query(queryString, queryValues) 10 | .then(data => { 11 | if (!data.rows[0]) { 12 | res.locals.allEventsInfo = []; 13 | } else { 14 | res.locals.allEventsInfo = data.rows; 15 | } 16 | return next(); 17 | }) 18 | .catch(err => { 19 | return next({ 20 | log: `Error occurred with queries.userEvents OR eventController.getFullEvents middleware: ${err}`, 21 | message: { err: "An error occured with SQL when retrieving events information." }, 22 | }); 23 | }) 24 | }; 25 | 26 | eventController.getAllAttendees = async (req, res, next) => { 27 | const allEvents = res.locals.allEventsInfo; // ALL EVENTS FOR THAT USER 28 | const arrayOfEventTitles = []; // ['marc birthday', 'minchan birthday' ... ] 29 | for (const event of allEvents) { 30 | arrayOfEventTitles.push(event.eventtitle); 31 | } 32 | 33 | res.locals.attendees = []; 34 | const queryString = queries.selectEventAttendees; 35 | 36 | const promises = []; 37 | 38 | for (let i = 0; i < arrayOfEventTitles.length; i++) { 39 | const result = new Promise((resolve, reject) => { 40 | try { 41 | const queryResult = db.query(queryString, [arrayOfEventTitles[i]]); 42 | return resolve(queryResult) 43 | } catch (err) { 44 | return reject(err); 45 | } 46 | }) 47 | promises.push(result); 48 | } 49 | 50 | const resolvedPromises = Promise.all(promises) 51 | .then(data => { 52 | for (let i = 0; i < data.length; i++) { 53 | const container = []; 54 | data[i].rows.forEach(obj => { 55 | container.push(obj.username); 56 | }) 57 | res.locals.attendees.push(container); 58 | } 59 | return next(); 60 | }) 61 | .catch(err => console.log('promise.all err: ', err)); 62 | 63 | } 64 | 65 | eventController.createEvent = (req, res, next) => { 66 | console.log('create') 67 | const { userid, username } = res.locals.allUserInfo; 68 | let { eventtitle, eventlocation, eventdate, eventstarttime, eventdetails } = req.body; 69 | 70 | let queryString; 71 | let queryValues; 72 | 73 | if (res.locals.photoUrl) { 74 | const { photoUrl } = res.locals; 75 | queryString = queries.createEvent; 76 | queryValues = [eventtitle, eventdate, eventstarttime, eventstarttime, eventlocation, eventdetails, userid, username, "{}", photoUrl]; 77 | } else { 78 | queryString = queries.createEventWithoutPhoto; 79 | queryValues = [eventtitle, eventdate, eventstarttime, eventstarttime, eventlocation, eventdetails, userid, username, "{}"]; 80 | } 81 | 82 | db.query(queryString, queryValues) 83 | .then(data => { 84 | // console.log('>>> eventController.createEvent DATA ', data); 85 | res.locals.newEvent = data.rows[0]; 86 | return next(); 87 | }) 88 | .catch(err => { 89 | console.log('>>> eventController.createEvent ERR ', err); 90 | return next({ 91 | log: `Error occurred with queries.createEvent OR eventController.createEvent middleware: ${err}`, 92 | message: { err: "An error occured with SQL when creating event." }, 93 | }); 94 | }) 95 | }; 96 | 97 | eventController.addNewEventToJoinTable = (req, res, next) => { 98 | // console.log('eventController.addNewEventToJoinTable') 99 | const queryString = queries.addNewEventToJoinTable; 100 | const queryValues = [res.locals.newEvent.eventid] 101 | db.query(queryString, queryValues) 102 | .then(data => { 103 | res.locals.usersandevents = data.rows[0]; 104 | return next(); 105 | }) 106 | .catch(err => { 107 | console.log('>>> eventController.addNewEventToJoinTable ERR', err); 108 | return next({ 109 | log: `Error occurred with queries.addtoUsersAndEvents OR eventController.addNewEventToJoinTable middleware: ${err}`, 110 | message: { err: "An error occured with SQL when adding to addtoUsersAndEvents table." }, 111 | }); 112 | }) 113 | }; 114 | 115 | eventController.verifyAttendee = (req, res, next) => { 116 | const title = req.query.eventtitle; // verify with frontend 117 | const { username } = res.locals.allUserInfo 118 | 119 | const queryString = queries.selectEventAttendees; 120 | const queryValues = [title]; 121 | 122 | db.query(queryString, queryValues) 123 | .then(data => { 124 | // console.log('data: ', data); 125 | const attendees = []; 126 | for (const attendeeObj of data.rows) { 127 | attendees.push(attendeeObj.username); 128 | } 129 | // console.log(attendees); 130 | if (attendees.includes(username)) { 131 | return next({ 132 | log: `Error: User is already an attendee`, 133 | message: { err: "User is already an attendee" }, 134 | }); 135 | } else { 136 | res.locals.eventID = data.rows[0].eventid; 137 | res.locals.eventTitle = data.rows[0].eventtitle; 138 | res.locals.eventDate = data.rows[0].eventdate; 139 | res.locals.eventStartTime = data.rows[0].eventstarttime; 140 | res.locals.eventEndTime = data.rows[0].eventendtime; 141 | res.locals.eventDetails = data.rows[0].eventdetails; 142 | res.locals.eventLocation = data.rows[0].eventlocation; 143 | return next(); 144 | } 145 | }) 146 | .catch(err => { 147 | return next({ 148 | log: `Error occurred with queries.selectEventAttendees OR eventController.verifyAttendee middleware: ${err}`, 149 | message: { err: "An error occured with SQL when verifying if user attended said event." }, 150 | }); 151 | }) 152 | } 153 | 154 | // (userid, username, eventid, eventtitle, eventdate, eventstarttime, eventendtime, eventdetails, eventlocation) 155 | eventController.addAttendee = (req, res, next) => { 156 | const title = req.query.eventtitle 157 | const { userid, username } = res.locals.allUserInfo 158 | // eventsID is saved in res.locals.eventID 159 | 160 | const queryString = queries.addUserToEvent; 161 | const queryValues = [ 162 | userid, 163 | username, 164 | res.locals.eventID, 165 | title, 166 | res.locals.eventDate, 167 | res.locals.eventStartTime, 168 | res.locals.eventEndTime, 169 | res.locals.eventDetails, 170 | res.locals.eventLocation, 171 | ]; 172 | 173 | db.query(queryString, queryValues) 174 | .then(data => { 175 | // console.log('data from addAttendee: ', data); 176 | return next(); 177 | }) 178 | .catch(err => { 179 | return next({ 180 | log: `Error occurred with queries.addUserToEvent OR eventController.addAttendee middleware: ${err}`, 181 | message: { err: "An error occured with SQL adding a user to an existing event as an attendee." }, 182 | }); 183 | }) 184 | }; 185 | //extracts all events and then pulls the user and events DB and appends all attendees to each event 186 | eventController.allEvents = (req, res, next) => { 187 | 188 | const queryString = queries.getAllEvents; 189 | //pulls all events 190 | db.query(queryString) 191 | .then(data => { 192 | if (!data.rows) { 193 | res.locals.allEventsInfo = []; 194 | } else { 195 | // then grabs all the attendees from the user and events table joined with the user table 196 | const eventAndUserDataQueryString = queries.getAttendeeEvents; 197 | db.query(eventAndUserDataQueryString).then(eventAndUserData => { 198 | // goes through the table and creates an attendees array with the list of user data 199 | const mergedTable = data.rows.map(e => { 200 | const attendees = eventAndUserData.rows.filter(entry => entry.eventid == e.eventid) 201 | e.attendees = attendees; 202 | return e; 203 | }) 204 | 205 | const photoQueryString = queries.getAllPhotos; 206 | // const queryValues = []; 207 | db.query(photoQueryString).then(allphotos => { 208 | // console.log('inside photo query ', allphotos.rows) 209 | const allinfo = mergedTable.map(ev => { 210 | const filtered = allphotos.rows.filter(photo => photo.eventtitle == ev.eventtitle); 211 | // console.log('I got the photos!!', filtered); 212 | const photos = filtered.reduce((acc, cur) => acc.concat([cur.eventpic]), []) 213 | // console.log(photos); 214 | ev.eventphotos = photos; 215 | console.log(photos) 216 | return ev; 217 | }) 218 | // console.log(allinfo) 219 | res.locals.allEventsInfo = allinfo; 220 | return next(); 221 | }) 222 | // console.log('all events info ', mergedTable) 223 | 224 | // used to have alleventsinfo saved with mergedTable 225 | }) 226 | } 227 | 228 | }) 229 | .catch(err => { 230 | return next({ 231 | log: `Error occurred with queries.getAllEvents OR eventController.allEvents middleware: ${err}`, 232 | message: { err: "An error occured with SQL when retrieving all events information." }, 233 | }); 234 | }) 235 | }; 236 | 237 | 238 | eventController.getUserDetail = (req, res, next) => { 239 | 240 | const countObj = []; // each element should how many attendees are for each event in succession; 241 | res.locals.attendees.forEach(arr => { 242 | countObj.push(arr.length); 243 | }) 244 | 245 | const allUsernames = res.locals.attendees.flat(Infinity); 246 | // console.log('FLATTENED USERNAMES', allUsernames); 247 | 248 | const queryString = queries.userInfo; 249 | 250 | const promises = []; 251 | 252 | for (let i = 0; i < allUsernames.length; i++) { 253 | const result = new Promise((resolve, reject) => { 254 | try { 255 | const queryResult = db.query(queryString, [allUsernames[i]]); 256 | return resolve(queryResult) 257 | } catch (err) { 258 | return reject(err); 259 | } 260 | }) 261 | promises.push(result); 262 | } 263 | 264 | const resolvedPromises = Promise.all(promises) 265 | .then(data => { 266 | 267 | res.locals.userDetail = []; 268 | 269 | for (let i = 0; i < countObj.length; i += 1) { 270 | let turns = countObj[i] 271 | let count = 0; 272 | const container = []; 273 | while (count < turns) { 274 | const minchan = data.shift() 275 | container.push(minchan.rows[0]); 276 | count++; 277 | } 278 | res.locals.userDetail.push(container); 279 | } 280 | return next(); 281 | }) 282 | .catch(err => console.log('promise.all err: ', err)); 283 | } 284 | 285 | eventController.consolidation = (req, res, next) => { 286 | const consolidatedEvents = { ...res.locals.allEventsInfo }; 287 | res.locals.userDetail.forEach((arr, i) => { 288 | consolidatedEvents[i].attendees = arr; 289 | }) 290 | return next(); 291 | } 292 | 293 | //filters out all events to only return the ones that the current user is attending 294 | eventController.filterForUser = (req, res, next) => { 295 | const { userid } = res.locals.allUserInfo 296 | 297 | const filtered = res.locals.allEventsInfo.filter(event => event.attendees.some(attendee => attendee.userid === userid)) 298 | res.locals.allEventsInfo = filtered; 299 | return next(); 300 | } 301 | 302 | //delete 303 | eventController.deleteUsersAndEvents = (req, res, next) => { 304 | // console.log("deleteUSER and Events Working") 305 | let values = Number(req.params.id) 306 | values = [values] 307 | 308 | const deleteUsersAndEvents = queries.deleteUsersAndEvents 309 | db.query(deleteUsersAndEvents, values) 310 | .then(data => { 311 | // console.log('table minus deleted event: ', data); 312 | return next(); 313 | }) 314 | .catch(err => { 315 | console.log(err) 316 | return next({ 317 | log: `Error occurred with queries.deleteUsersEvent OR eventController.deleteUserAndEvent middleware: ${err}`, 318 | message: { err: "An error occured within request to delete an event at deleteUsersAndEvents." }, 319 | }); 320 | }) 321 | } 322 | 323 | eventController.deleteInviteEvent = (req, res, next) => { 324 | // console.log("deleteUSER and Events Working") 325 | let values = Number(req.params.id) 326 | values = [values] 327 | 328 | const deleteInviteUsersAndEvents = queries.deleteInviteUsersAndEvents 329 | db.query(deleteInviteUsersAndEvents, values) 330 | .then(data => { 331 | // console.log('table minus deleted event: ', data); 332 | return next(); 333 | }) 334 | .catch(err => { 335 | console.log(err) 336 | return next({ 337 | log: `Error occurred with queries.deleteUsersEvent OR eventController.deleteUserAndEvent middleware: ${err}`, 338 | message: { err: "An error occured within request to delete an event at deleteUsersAndEvents." }, 339 | }); 340 | }) 341 | } 342 | 343 | 344 | //DELETE a post from event 345 | 346 | eventController.deleteEvent = (req, res, next) => { 347 | let values = Number(req.params.id) 348 | values = [values] 349 | 350 | const deleteEvent = queries.deleteEvent 351 | 352 | db.query(deleteEvent,values) 353 | .then(data => { 354 | // console.log('table minus deleted event: ', data); 355 | return next(); 356 | }) 357 | .catch(err => { 358 | console.log(err) 359 | return next({ 360 | log: `Error occurred with queries.deleteEvent OR eventController.deleteEvent middleware: ${err}`, 361 | message: { err: "An error occured within request to delete an event." }, 362 | }); 363 | }) 364 | } 365 | 366 | // Update photo for event 367 | 368 | eventController.updatePhoto = (req, res, next) => { 369 | const { eventtitle } = req.body; 370 | 371 | const queryString = queries.updatePhoto; 372 | const queryValues = [res.locals.photoUrl, eventtitle]; 373 | 374 | db.query(queryString, queryValues) 375 | .then(data => { 376 | // console.log('response from update photo ', data) 377 | return next(); 378 | }) 379 | .catch(err => { 380 | return next({ 381 | log: `Error occurred with queries.updatePhoto OR eventController.updatePhoto middleware: ${err}`, 382 | message: { err: "An error occured within request to update a photo in an event." }, 383 | }); 384 | }) 385 | } 386 | 387 | 388 | 389 | // grab specific single event (after update) 390 | 391 | eventController.getOneEvent = (req, res, next) => { 392 | const { eventtitle } = req.body; 393 | 394 | const queryString = queries.getOneEvent; 395 | const queryValues = [eventtitle]; 396 | 397 | db.query(queryString, queryValues) 398 | .then(data => { 399 | // console.log('get one event response ', data); 400 | if (data.rows[0]) { 401 | res.locals.event = data.rows[0]; 402 | } else { 403 | console.log('huh, didnt find'); 404 | } 405 | return next(); 406 | }) 407 | .catch(err => { 408 | return next({ 409 | log: `Error occurred with queries.getOneEvent OR eventController.getOneEvent middleware: ${err}`, 410 | message: { err: "An error occured within request to get one event from SQL." }, 411 | }); 412 | }) 413 | } 414 | 415 | // eventController.getEventDummy = (req, res, next) => { 416 | 417 | // const queryString = queries.getAllEvents; 418 | // //pulls all events 419 | // db.query(queryString) 420 | // .then(data => { 421 | // if (!data.rows) { 422 | // res.locals.allEventsInfo = []; 423 | // } else { 424 | // // then grabs all the attendees from the user and events table joined with the user table 425 | // const eventAndUserDataQueryString = queries.getAttendeeEvents; 426 | // db.query(eventAndUserDataQueryString).then(eventAndUserData => { 427 | // // goes through the table and creates an attendees array with the list of user data 428 | // const mergedTable = data.rows.map(e => { 429 | // const attendees = eventAndUserData.rows.filter(entry => entry.eventid == e.eventid) 430 | // e.attendees = attendees; 431 | // return e; 432 | // }) 433 | 434 | // const photoQueryString = queries.getAllPhotos; 435 | // // const queryValues = []; 436 | // db.query(photoQueryString).then(allphotos => { 437 | // console.log('inside photo query ', allphotos.rows) 438 | // const allinfo = mergedTable.map(e => { 439 | // const photos = allphotos.rows.filter(photo => photo.eventtitle == e.eventtitle); 440 | // console.log('I got the photos!!', photos); 441 | 442 | // }) 443 | // }) 444 | // // console.log('all events info ', mergedTable) 445 | 446 | 447 | // res.locals.allEventsInfo = mergedTable 448 | // return next(); 449 | // }) 450 | 451 | // } 452 | 453 | // }) 454 | // .catch(err => { 455 | // return next({ 456 | // log: `Error occurred with queries.getAllEvents OR eventController.allEvents middleware: ${err}`, 457 | // message: { err: "An error occured with SQL when retrieving all events information." }, 458 | // }) 459 | // } 460 | // } 461 | 462 | eventController.getAttendeesOneEvent = (req, res, next) => { 463 | const { eventtitle } = req.body; 464 | 465 | const queryString = queries.selectEventAttendees; 466 | const queryValues = [eventtitle]; 467 | 468 | db.query(queryString, queryValues) 469 | .then(data => { 470 | res.locals.thisEventAttendees = data.rows; 471 | return next(); 472 | }) 473 | .catch(err => { 474 | return next({ 475 | log: `Error occurred with queries.selectEventAttendees OR eventController.getAttendeesOneEvent middleware: ${err}`, 476 | message: { err: "An error occured within request to get one event from SQL." }, 477 | }); 478 | }) 479 | } 480 | 481 | //Update a post from userandevents 482 | 483 | eventController.updateEventPhotos = (req, res, next) => { 484 | // let values = Number(req.params.id) 485 | // values = [values] 486 | //putting values first inside queryValues 487 | 488 | const updateEventPhotos = queries.updateEventPhotos 489 | 490 | let { eventtitle, oldeventtitle } = req.body; 491 | console.log(eventtitlem, oldeventtitle) 492 | const queryValues = [eventtitle, oldeventtitle ]; 493 | db.query("these are your updates: " +updateEventPhotos, queryValues) 494 | .then(data => { 495 | console.log('table has been updated: ', data); 496 | return next(); 497 | }) 498 | .catch(err => { 499 | console.log(err) 500 | return next({ 501 | log: `Error occurred with queries.updateEventPhotos OR eventController.updateEventPhotos middleware: ${err}`, 502 | message: { err: "An error occured within request to update EventPhotos." }, 503 | }); 504 | }) 505 | } 506 | 507 | eventController.updateUsersAndEvents = (req, res, next) => { 508 | let values = Number(req.params.id) 509 | // values = [values] 510 | //putting values first inside queryValues 511 | console.log("this is your value:" + values) 512 | const updateUsersAndEvents = queries.updateUsersAndEvents 513 | console.log("this is your updateUsersAndEvents:"+ updateUsersAndEvents) 514 | let { eventtitle, eventdetails, eventlocation, } = req.body; 515 | const queryValues = [eventtitle, eventdetails, eventlocation, values ]; 516 | console.log("this is your queryValues:" + queryValues) 517 | db.query(updateUsersAndEvents, queryValues) 518 | .then(data => { 519 | console.log('table has been updated: ', data); 520 | return next(); 521 | }) 522 | .catch(err => { 523 | console.log(err) 524 | return next({ 525 | log: `Error occurred with queries.updateUsersAndEvents OR eventController.updateUsersAndEvents middleware: ${err}`, 526 | message: { err: "An error occured within request to update userandevent." }, 527 | }); 528 | }) 529 | } 530 | 531 | //Update a post from events Table 532 | eventController.updateEvents = (req, res, next) => { 533 | let values = Number(req.params.id) 534 | // values = [values] 535 | //putting values first inside queryValues 536 | console.log("this is your value:" + values) 537 | const updateEvents = queries.updateEvents 538 | console.log("this is your updateEvents:"+ updateEvents) 539 | let { eventtitle, eventdetails, eventlocation, } = req.body; 540 | const queryValues = [eventtitle, eventdetails, eventlocation, values ]; 541 | console.log("this is your queryValues:" + queryValues) 542 | db.query(updateEvents, queryValues) 543 | .then(data => { 544 | console.log('table has been updated: ', data); 545 | return next(); 546 | }) 547 | .catch(err => { 548 | console.log(err) 549 | return next({ 550 | log: `Error occurred with queries.updateEvents OR eventController.updateEvents middleware: ${err}`, 551 | message: { err: "An error occured within request to updateEvent." }, 552 | }); 553 | }) 554 | } 555 | 556 | 557 | 558 | module.exports = eventController; 559 | --------------------------------------------------------------------------------