├── .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 |

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 |
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 |
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 |
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 |
101 | Event Photo
102 |
103 |
104 |
105 | {previewSource && (
106 |
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 |
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 |

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 |
185 | Event Photo
186 |
187 |
188 |
189 | {previewSource && (
190 |
191 | )}
192 |
193 |
196 |
197 |
198 |
199 |
200 |
201 | Get Photo With Tag
202 |
203 |
204 |
205 |
208 | Use tag
209 |
210 |
211 |
212 | {previewSource && (
213 |
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 |
--------------------------------------------------------------------------------