diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..456751f --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af068e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +.DS_STORE \ No newline at end of file diff --git a/React.test.js b/React.test.js new file mode 100644 index 0000000..0343ade --- /dev/null +++ b/React.test.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { configure, shallow } from 'enzyme'; +import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; +import toJson from 'enzyme-to-json'; + +// ConnectedAccess refers to the default export from the file, the destructuring is used to separately +// import the dumb component named Access which is not connected to mapStateToProps or mapDispatchToProps. +// In order to test the DUMB component, had to also specifically export it (see line 34 of Access.jsx) +import ConnectedAccess, { Access } from './client/components/Access.jsx'; +import configureStore from 'redux-mock-store'; + +// Newer Enzyme versions require an adapter to a particular version of React +configure({ adapter: new Adapter() }); + + +describe('React unit tests', () => { + + let wrapper; + + describe('Shallow Render REACT COMPONENTS', () => { + // pass in dummy props of the sort that Access expects + const props = { + pageToDisplay: 'login', + loggedIn: false, + midpoint: { lat: 40.7142700, lng: -74.0059700 }, + currentUser: {}, + selectedLocations: [], + friendsList: [], + notFriendsList: [], + }; + describe('Access component', () => { + beforeAll(() => { + wrapper = shallow(); + }); + + it('renders the DUMB component', () => { + expect(wrapper.length).toEqual(1); + }) + + it('Renders a div, className loginStyles, with six child divs', () => { + expect(wrapper.find('div.loginStyles').children()).toHaveLength(6); + }); + + it('Renders two buttons', () => { + let count = 0; + wrapper.find('button').forEach(node => { + count++; + }); + expect(count).toBe(2); + }); + }); + }); + + xdescribe('REACT-REDUX (Shallow + passing the store directly)', () => { + const initialState = { + pageToDisplay: 'login', + loggedIn: false, + midpoint: { lat: 40.7142700, lng: -74.0059700 }, + currentUser: {}, + selectedLocations: [], + friendsList: [], + notFriendsList: [], + currentUserID: 12 + }; + const mockStore = configureStore(); + let store, container; + + beforeEach(() => { + store = mockStore(initialState); + container = shallow(); + }); + + it('renders the connected(SMART) component', () => { + expect(container.length).toEqual(1); + }) + }); +}); \ No newline at end of file diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js new file mode 100644 index 0000000..84c1da6 --- /dev/null +++ b/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub'; \ No newline at end of file diff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js new file mode 100644 index 0000000..a099545 --- /dev/null +++ b/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; \ No newline at end of file diff --git a/__tests__/supertest.js b/__tests__/supertest.js new file mode 100644 index 0000000..8507cc2 --- /dev/null +++ b/__tests__/supertest.js @@ -0,0 +1,116 @@ +const request = require('supertest'); +let server = 'http://localhost:8080'; + +describe('Server Tests', () => { + describe('/', () => { + describe('GET - root endpoint', () => { + // server = 'http://localhost:8080' + it('responds with 200 status and text/html content type', () => { + return request(server) + .get('/') + .expect('Content-type', /text\/html/) + .expect(200); + }); + }); + }); + + describe('/database/login', () => { + describe('GET - with invalid login', () => { + it('if no login info provided, responds with 404 status and application/json content type', () => { + return request(server) + .get('/database/login') + .expect('Content-type', /application\/json/) + .expect(404); + }); + + it('if invalid login info provided, responds with 404 status and application/json content type', () => { + return request(server) + .get('/database/login?username=ternce&password=petrsen') + .expect('Content-type', /application\/json/) + .expect(404); + }); + }); + + describe('GET - with valid login', () => { + it('provided with *valid* login, responds with 200 status and application/json content type', () => { + return request(server) + .get('/database/login?username=terence&password=petersen') + .expect('Content-type', /application\/json/) + .expect(200) + }); + + it('--res.body contains "User verified!" message and verified prop set to true', () => { + return request(server) + .get('/database/login?username=terence&password=petersen') + .then(res => { + expect(res.body.message).toBe("User verified!"); + expect(res.body.verified).toBe(true); + }) + }); + + it('--res.body has a friendList property containing an array', () => { + return request(server) + .get('/database/login?username=terence&password=petersen') + .then(res => { + expect(res.body.friendList).toBeDefined(); + expect(Array.isArray(res.body.friendList)).toBe(true); + }); + }); + + it('--returned friendList array contains objects', () => { + return request(server) + .get('/database/login?username=terence&password=petersen') + .then(res => { + const { friendList } = res.body; + expect(friendList).toBeDefined(); + expect(typeof friendList[0] === 'object' && !Array.isArray(friendList[0])).toBe(true); + }); + }); + }); + }); + + describe('/database/signup', () => { + // both of the below tests fail. + // need to determine if it is the tests that are written incorrectly + // or if the application is not behaving as expected + describe('POST - with invalid user/password input(s)', () => { + + // shows the server returning 500 "Internal Server Error", unlike the response shown in the browser + it('returns status 401', () => { + return request(server) + .post('/database/signup') + .send({ username: '', password: '', address: '123 fake street' }) + .expect(401); + }); + + // the console log shows res.body to be an empty object + it('returns "Invalid username and/or password!" message', () => { + return request(server) + .post('/database/signup') + .send({ username: '', password: '', address: '123 fake street' }) + .then(res => { + console.log(res.body); + expect(res.body.message).toBe('Invalid username and/or password!'); + }); + }); + }); + + describe('POST - with invalid address input', () => { + it ('returns status 500', () => { + return request(server) + .post('/database/signup') + .send({ username: 'newguy3', password: 'okiedokie', address: 'gjeirogrj' }) + .expect(500); + }); + }); + + describe('POST - with valid inputs', () => { + it ('returns status 200', () => { + return request(server) + .post('/database/signup') + .send({ username: 'newguy4', password: 'okiedokie', address: '123 fake street' }) + .expect(201); + }) + }) + }); +}); diff --git a/build/bundle.js b/build/bundle.js new file mode 100644 index 0000000..b939df5 --- /dev/null +++ b/build/bundle.js @@ -0,0 +1,2 @@ +/*! For license information please see bundle.js.LICENSE.txt */ +(()=>{var e={5550:e=>{"use strict";function t(e,t){this.x=e,this.y=t}e.exports=t,t.prototype={clone:function(){return new t(this.x,this.y)},add:function(e){return this.clone()._add(e)},sub:function(e){return this.clone()._sub(e)},multByPoint:function(e){return this.clone()._multByPoint(e)},divByPoint:function(e){return this.clone()._divByPoint(e)},mult:function(e){return this.clone()._mult(e)},div:function(e){return this.clone()._div(e)},rotate:function(e){return this.clone()._rotate(e)},rotateAround:function(e,t){return this.clone()._rotateAround(e,t)},matMult:function(e){return this.clone()._matMult(e)},unit:function(){return this.clone()._unit()},perp:function(){return this.clone()._perp()},round:function(){return this.clone()._round()},mag:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals:function(e){return this.x===e.x&&this.y===e.y},dist:function(e){return Math.sqrt(this.distSqr(e))},distSqr:function(e){var t=e.x-this.x,n=e.y-this.y;return t*t+n*n},angle:function(){return Math.atan2(this.y,this.x)},angleTo:function(e){return Math.atan2(this.y-e.y,this.x-e.x)},angleWith:function(e){return this.angleWithSep(e.x,e.y)},angleWithSep:function(e,t){return Math.atan2(this.x*t-this.y*e,this.x*e+this.y*t)},_matMult:function(e){var t=e[0]*this.x+e[1]*this.y,n=e[2]*this.x+e[3]*this.y;return this.x=t,this.y=n,this},_add:function(e){return this.x+=e.x,this.y+=e.y,this},_sub:function(e){return this.x-=e.x,this.y-=e.y,this},_mult:function(e){return this.x*=e,this.y*=e,this},_div:function(e){return this.x/=e,this.y/=e,this},_multByPoint:function(e){return this.x*=e.x,this.y*=e.y,this},_divByPoint:function(e){return this.x/=e.x,this.y/=e.y,this},_unit:function(){return this._div(this.mag()),this},_perp:function(){var e=this.y;return this.y=this.x,this.x=-e,this},_rotate:function(e){var t=Math.cos(e),n=Math.sin(e),r=t*this.x-n*this.y,o=n*this.x+t*this.y;return this.x=r,this.y=o,this},_rotateAround:function(e,t){var n=Math.cos(e),r=Math.sin(e),o=t.x+n*(this.x-t.x)-r*(this.y-t.y),i=t.y+r*(this.x-t.x)+n*(this.y-t.y);return this.x=o,this.y=i,this},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}},t.convert=function(e){return e instanceof t?e:Array.isArray(e)?new t(e[0],e[1]):e}},9669:(e,t,n)=>{e.exports=n(1609)},5448:(e,t,n)=>{"use strict";var r=n(4867),o=n(6026),i=n(4372),a=n(5327),l=n(4097),u=n(4109),s=n(7985),c=n(5061),f=n(5655),p=n(5263);e.exports=function(e){return new Promise((function(t,n){var d,h=e.data,m=e.headers,v=e.responseType;function g(){e.cancelToken&&e.cancelToken.unsubscribe(d),e.signal&&e.signal.removeEventListener("abort",d)}r.isFormData(h)&&delete m["Content-Type"];var y=new XMLHttpRequest;if(e.auth){var b=e.auth.username||"",w=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";m.Authorization="Basic "+btoa(b+":"+w)}var _=l(e.baseURL,e.url);function x(){if(y){var r="getAllResponseHeaders"in y?u(y.getAllResponseHeaders()):null,i={data:v&&"text"!==v&&"json"!==v?y.response:y.responseText,status:y.status,statusText:y.statusText,headers:r,config:e,request:y};o((function(e){t(e),g()}),(function(e){n(e),g()}),i),y=null}}if(y.open(e.method.toUpperCase(),a(_,e.params,e.paramsSerializer),!0),y.timeout=e.timeout,"onloadend"in y?y.onloadend=x:y.onreadystatechange=function(){y&&4===y.readyState&&(0!==y.status||y.responseURL&&0===y.responseURL.indexOf("file:"))&&setTimeout(x)},y.onabort=function(){y&&(n(c("Request aborted",e,"ECONNABORTED",y)),y=null)},y.onerror=function(){n(c("Network Error",e,null,y)),y=null},y.ontimeout=function(){var t=e.timeout?"timeout of "+e.timeout+"ms exceeded":"timeout exceeded",r=e.transitional||f.transitional;e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(c(t,e,r.clarifyTimeoutError?"ETIMEDOUT":"ECONNABORTED",y)),y=null},r.isStandardBrowserEnv()){var S=(e.withCredentials||s(_))&&e.xsrfCookieName?i.read(e.xsrfCookieName):void 0;S&&(m[e.xsrfHeaderName]=S)}"setRequestHeader"in y&&r.forEach(m,(function(e,t){void 0===h&&"content-type"===t.toLowerCase()?delete m[t]:y.setRequestHeader(t,e)})),r.isUndefined(e.withCredentials)||(y.withCredentials=!!e.withCredentials),v&&"json"!==v&&(y.responseType=e.responseType),"function"==typeof e.onDownloadProgress&&y.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&y.upload&&y.upload.addEventListener("progress",e.onUploadProgress),(e.cancelToken||e.signal)&&(d=function(e){y&&(n(!e||e&&e.type?new p("canceled"):e),y.abort(),y=null)},e.cancelToken&&e.cancelToken.subscribe(d),e.signal&&(e.signal.aborted?d():e.signal.addEventListener("abort",d))),h||(h=null),y.send(h)}))}},1609:(e,t,n)=>{"use strict";var r=n(4867),o=n(1849),i=n(321),a=n(7185),l=function e(t){var n=new i(t),l=o(i.prototype.request,n);return r.extend(l,i.prototype,n),r.extend(l,n),l.create=function(n){return e(a(t,n))},l}(n(5655));l.Axios=i,l.Cancel=n(5263),l.CancelToken=n(4972),l.isCancel=n(6502),l.VERSION=n(7288).version,l.all=function(e){return Promise.all(e)},l.spread=n(8713),l.isAxiosError=n(6268),e.exports=l,e.exports.default=l},5263:e=>{"use strict";function t(e){this.message=e}t.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},t.prototype.__CANCEL__=!0,e.exports=t},4972:(e,t,n)=>{"use strict";var r=n(5263);function o(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var n=this;this.promise.then((function(e){if(n._listeners){var t,r=n._listeners.length;for(t=0;t{"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},321:(e,t,n)=>{"use strict";var r=n(4867),o=n(5327),i=n(782),a=n(3572),l=n(7185),u=n(4875),s=u.validators;function c(e){this.defaults=e,this.interceptors={request:new i,response:new i}}c.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{}).url=arguments[0]:e=e||{},(e=l(this.defaults,e)).method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=e.transitional;void 0!==t&&u.assertOptions(t,{silentJSONParsing:s.transitional(s.boolean),forcedJSONParsing:s.transitional(s.boolean),clarifyTimeoutError:s.transitional(s.boolean)},!1);var n=[],r=!0;this.interceptors.request.forEach((function(t){"function"==typeof t.runWhen&&!1===t.runWhen(e)||(r=r&&t.synchronous,n.unshift(t.fulfilled,t.rejected))}));var o,i=[];if(this.interceptors.response.forEach((function(e){i.push(e.fulfilled,e.rejected)})),!r){var c=[a,void 0];for(Array.prototype.unshift.apply(c,n),c=c.concat(i),o=Promise.resolve(e);c.length;)o=o.then(c.shift(),c.shift());return o}for(var f=e;n.length;){var p=n.shift(),d=n.shift();try{f=p(f)}catch(e){d(e);break}}try{o=a(f)}catch(e){return Promise.reject(e)}for(;i.length;)o=o.then(i.shift(),i.shift());return o},c.prototype.getUri=function(e){return e=l(this.defaults,e),o(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},r.forEach(["delete","get","head","options"],(function(e){c.prototype[e]=function(t,n){return this.request(l(n||{},{method:e,url:t,data:(n||{}).data}))}})),r.forEach(["post","put","patch"],(function(e){c.prototype[e]=function(t,n,r){return this.request(l(r||{},{method:e,url:t,data:n}))}})),e.exports=c},782:(e,t,n)=>{"use strict";var r=n(4867);function o(){this.handlers=[]}o.prototype.use=function(e,t,n){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!n&&n.synchronous,runWhen:n?n.runWhen:null}),this.handlers.length-1},o.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},o.prototype.forEach=function(e){r.forEach(this.handlers,(function(t){null!==t&&e(t)}))},e.exports=o},4097:(e,t,n)=>{"use strict";var r=n(1793),o=n(7303);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},5061:(e,t,n)=>{"use strict";var r=n(481);e.exports=function(e,t,n,o,i){var a=new Error(e);return r(a,t,n,o,i)}},3572:(e,t,n)=>{"use strict";var r=n(4867),o=n(8527),i=n(6502),a=n(5655),l=n(5263);function u(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new l("canceled")}e.exports=function(e){return u(e),e.headers=e.headers||{},e.data=o.call(e,e.data,e.headers,e.transformRequest),e.headers=r.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]})),(e.adapter||a.adapter)(e).then((function(t){return u(e),t.data=o.call(e,t.data,t.headers,e.transformResponse),t}),(function(t){return i(t)||(u(e),t&&t.response&&(t.response.data=o.call(e,t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},481:e=>{"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code,status:this.response&&this.response.status?this.response.status:null}},e}},7185:(e,t,n)=>{"use strict";var r=n(4867);e.exports=function(e,t){t=t||{};var n={};function o(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function i(n){return r.isUndefined(t[n])?r.isUndefined(e[n])?void 0:o(void 0,e[n]):o(e[n],t[n])}function a(e){if(!r.isUndefined(t[e]))return o(void 0,t[e])}function l(n){return r.isUndefined(t[n])?r.isUndefined(e[n])?void 0:o(void 0,e[n]):o(void 0,t[n])}function u(n){return n in t?o(e[n],t[n]):n in e?o(void 0,e[n]):void 0}var s={url:a,method:a,data:a,baseURL:l,transformRequest:l,transformResponse:l,paramsSerializer:l,timeout:l,timeoutMessage:l,withCredentials:l,adapter:l,responseType:l,xsrfCookieName:l,xsrfHeaderName:l,onUploadProgress:l,onDownloadProgress:l,decompress:l,maxContentLength:l,maxBodyLength:l,transport:l,httpAgent:l,httpsAgent:l,cancelToken:l,socketPath:l,responseEncoding:l,validateStatus:u};return r.forEach(Object.keys(e).concat(Object.keys(t)),(function(e){var t=s[e]||i,o=t(e);r.isUndefined(o)&&t!==u||(n[e]=o)})),n}},6026:(e,t,n)=>{"use strict";var r=n(5061);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},8527:(e,t,n)=>{"use strict";var r=n(4867),o=n(5655);e.exports=function(e,t,n){var i=this||o;return r.forEach(n,(function(n){e=n.call(i,e,t)})),e}},5655:(e,t,n)=>{"use strict";var r=n(4867),o=n(6016),i=n(481),a={"Content-Type":"application/x-www-form-urlencoded"};function l(e,t){!r.isUndefined(e)&&r.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}var u,s={transitional:{silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},adapter:(("undefined"!=typeof XMLHttpRequest||"undefined"!=typeof process&&"[object process]"===Object.prototype.toString.call(process))&&(u=n(5448)),u),transformRequest:[function(e,t){return o(t,"Accept"),o(t,"Content-Type"),r.isFormData(e)||r.isArrayBuffer(e)||r.isBuffer(e)||r.isStream(e)||r.isFile(e)||r.isBlob(e)?e:r.isArrayBufferView(e)?e.buffer:r.isURLSearchParams(e)?(l(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):r.isObject(e)||t&&"application/json"===t["Content-Type"]?(l(t,"application/json"),function(e,t,n){if(r.isString(e))try{return(0,JSON.parse)(e),r.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(0,JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||s.transitional,n=t&&t.silentJSONParsing,o=t&&t.forcedJSONParsing,a=!n&&"json"===this.responseType;if(a||o&&r.isString(e)&&e.length)try{return JSON.parse(e)}catch(e){if(a){if("SyntaxError"===e.name)throw i(e,this,"E_JSON_PARSE");throw e}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],(function(e){s.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){s.headers[e]=r.merge(a)})),e.exports=s},7288:e=>{e.exports={version:"0.24.0"}},1849:e=>{"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r{"use strict";var r=n(4867);function o(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(r.isURLSearchParams(t))i=t.toString();else{var a=[];r.forEach(t,(function(e,t){null!=e&&(r.isArray(e)?t+="[]":e=[e],r.forEach(e,(function(e){r.isDate(e)?e=e.toISOString():r.isObject(e)&&(e=JSON.stringify(e)),a.push(o(t)+"="+o(e))})))})),i=a.join("&")}if(i){var l=e.indexOf("#");-1!==l&&(e=e.slice(0,l)),e+=(-1===e.indexOf("?")?"?":"&")+i}return e}},7303:e=>{"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},4372:(e,t,n)=>{"use strict";var r=n(4867);e.exports=r.isStandardBrowserEnv()?{write:function(e,t,n,o,i,a){var l=[];l.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&l.push("expires="+new Date(n).toGMTString()),r.isString(o)&&l.push("path="+o),r.isString(i)&&l.push("domain="+i),!0===a&&l.push("secure"),document.cookie=l.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},1793:e=>{"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},6268:e=>{"use strict";e.exports=function(e){return"object"==typeof e&&!0===e.isAxiosError}},7985:(e,t,n)=>{"use strict";var r=n(4867);e.exports=r.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return e=o(window.location.href),function(t){var n=r.isString(t)?o(t):t;return n.protocol===e.protocol&&n.host===e.host}}():function(){return!0}},6016:(e,t,n)=>{"use strict";var r=n(4867);e.exports=function(e,t){r.forEach(e,(function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])}))}},4109:(e,t,n)=>{"use strict";var r=n(4867),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,a={};return e?(r.forEach(e.split("\n"),(function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(a[t]&&o.indexOf(t)>=0)return;a[t]="set-cookie"===t?(a[t]?a[t]:[]).concat([n]):a[t]?a[t]+", "+n:n}})),a):a}},8713:e=>{"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},4875:(e,t,n)=>{"use strict";var r=n(7288).version,o={};["object","boolean","number","function","string","symbol"].forEach((function(e,t){o[e]=function(n){return typeof n===e||"a"+(t<1?"n ":" ")+e}}));var i={};o.transitional=function(e,t,n){function o(e,t){return"[Axios v"+r+"] Transitional option '"+e+"'"+t+(n?". "+n:"")}return function(n,r,a){if(!1===e)throw new Error(o(r," has been removed"+(t?" in "+t:"")));return t&&!i[r]&&(i[r]=!0,console.warn(o(r," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(n,r,a)}},e.exports={assertOptions:function(e,t,n){if("object"!=typeof e)throw new TypeError("options must be an object");for(var r=Object.keys(e),o=r.length;o-- >0;){var i=r[o],a=t[i];if(a){var l=e[i],u=void 0===l||a(l,i,e);if(!0!==u)throw new TypeError("option "+i+" must be "+u)}else if(!0!==n)throw Error("Unknown option "+i)}},validators:o}},4867:(e,t,n)=>{"use strict";var r=n(1849),o=Object.prototype.toString;function i(e){return"[object Array]"===o.call(e)}function a(e){return void 0===e}function l(e){return null!==e&&"object"==typeof e}function u(e){if("[object Object]"!==o.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function s(e){return"[object Function]"===o.call(e)}function c(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),i(e))for(var n=0,r=e.length;n{var t={utf8:{stringToBytes:function(e){return t.bin.stringToBytes(unescape(encodeURIComponent(e)))},bytesToString:function(e){return decodeURIComponent(escape(t.bin.bytesToString(e)))}},bin:{stringToBytes:function(e){for(var t=[],n=0;n{var r=n(7854),o=n(614),i=n(6330),a=r.TypeError;e.exports=function(e){if(o(e))return e;throw a(i(e)+" is not a function")}},9483:(e,t,n)=>{var r=n(7854),o=n(4411),i=n(6330),a=r.TypeError;e.exports=function(e){if(o(e))return e;throw a(i(e)+" is not a constructor")}},1530:(e,t,n)=>{"use strict";var r=n(8710).charAt;e.exports=function(e,t,n){return t+(n?r(e,t).length:1)}},9670:(e,t,n)=>{var r=n(7854),o=n(111),i=r.String,a=r.TypeError;e.exports=function(e){if(o(e))return e;throw a(i(e)+" is not an object")}},8533:(e,t,n)=>{"use strict";var r=n(2092).forEach,o=n(9341)("forEach");e.exports=o?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},1318:(e,t,n)=>{var r=n(5656),o=n(1400),i=n(6244),a=function(e){return function(t,n,a){var l,u=r(t),s=i(u),c=o(a,s);if(e&&n!=n){for(;s>c;)if((l=u[c++])!=l)return!0}else for(;s>c;c++)if((e||c in u)&&u[c]===n)return e||c||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},2092:(e,t,n)=>{var r=n(9974),o=n(1702),i=n(8361),a=n(7908),l=n(6244),u=n(5417),s=o([].push),c=function(e){var t=1==e,n=2==e,o=3==e,c=4==e,f=6==e,p=7==e,d=5==e||f;return function(h,m,v,g){for(var y,b,w=a(h),_=i(w),x=r(m,v),S=l(_),E=0,k=g||u,C=t?k(h,S):n||p?k(h,0):void 0;S>E;E++)if((d||E in _)&&(b=x(y=_[E],E,w),e))if(t)C[E]=b;else if(b)switch(e){case 3:return!0;case 5:return y;case 6:return E;case 2:s(C,y)}else switch(e){case 4:return!1;case 7:s(C,y)}return f?-1:o||c?c:C}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterReject:c(7)}},1194:(e,t,n)=>{var r=n(7293),o=n(5112),i=n(7392),a=o("species");e.exports=function(e){return i>=51||!r((function(){var t=[];return(t.constructor={})[a]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},9341:(e,t,n)=>{"use strict";var r=n(7293);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){throw 1},1)}))}},3671:(e,t,n)=>{var r=n(7854),o=n(9662),i=n(7908),a=n(8361),l=n(6244),u=r.TypeError,s=function(e){return function(t,n,r,s){o(n);var c=i(t),f=a(c),p=l(c),d=e?p-1:0,h=e?-1:1;if(r<2)for(;;){if(d in f){s=f[d],d+=h;break}if(d+=h,e?d<0:p<=d)throw u("Reduce of empty array with no initial value")}for(;e?d>=0:p>d;d+=h)d in f&&(s=n(s,f[d],d,c));return s}};e.exports={left:s(!1),right:s(!0)}},206:(e,t,n)=>{var r=n(1702);e.exports=r([].slice)},7475:(e,t,n)=>{var r=n(7854),o=n(3157),i=n(4411),a=n(111),l=n(5112)("species"),u=r.Array;e.exports=function(e){var t;return o(e)&&(t=e.constructor,(i(t)&&(t===u||o(t.prototype))||a(t)&&null===(t=t[l]))&&(t=void 0)),void 0===t?u:t}},5417:(e,t,n)=>{var r=n(7475);e.exports=function(e,t){return new(r(e))(0===t?0:t)}},4326:(e,t,n)=>{var r=n(1702),o=r({}.toString),i=r("".slice);e.exports=function(e){return i(o(e),8,-1)}},648:(e,t,n)=>{var r=n(7854),o=n(1694),i=n(614),a=n(4326),l=n(5112)("toStringTag"),u=r.Object,s="Arguments"==a(function(){return arguments}());e.exports=o?a:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=u(e),l))?n:s?a(t):"Object"==(r=a(t))&&i(t.callee)?"Arguments":r}},9920:(e,t,n)=>{var r=n(2597),o=n(3887),i=n(1236),a=n(3070);e.exports=function(e,t){for(var n=o(t),l=a.f,u=i.f,s=0;s{var r=n(9781),o=n(3070),i=n(9114);e.exports=r?function(e,t,n){return o.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},9114:e=>{e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},6135:(e,t,n)=>{"use strict";var r=n(4948),o=n(3070),i=n(9114);e.exports=function(e,t,n){var a=r(t);a in e?o.f(e,a,i(0,n)):e[a]=n}},9781:(e,t,n)=>{var r=n(7293);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},317:(e,t,n)=>{var r=n(7854),o=n(111),i=r.document,a=o(i)&&o(i.createElement);e.exports=function(e){return a?i.createElement(e):{}}},8324:e=>{e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},8509:(e,t,n)=>{var r=n(317)("span").classList,o=r&&r.constructor&&r.constructor.prototype;e.exports=o===Object.prototype?void 0:o},5268:(e,t,n)=>{var r=n(4326),o=n(7854);e.exports="process"==r(o.process)},8113:(e,t,n)=>{var r=n(5005);e.exports=r("navigator","userAgent")||""},7392:(e,t,n)=>{var r,o,i=n(7854),a=n(8113),l=i.process,u=i.Deno,s=l&&l.versions||u&&u.version,c=s&&s.v8;c&&(o=(r=c.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!o&&a&&(!(r=a.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=a.match(/Chrome\/(\d+)/))&&(o=+r[1]),e.exports=o},748:e=>{e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:(e,t,n)=>{var r=n(7854),o=n(1236).f,i=n(8880),a=n(1320),l=n(3505),u=n(9920),s=n(4705);e.exports=function(e,t){var n,c,f,p,d,h=e.target,m=e.global,v=e.stat;if(n=m?r:v?r[h]||l(h,{}):(r[h]||{}).prototype)for(c in t){if(p=t[c],f=e.noTargetGet?(d=o(n,c))&&d.value:n[c],!s(m?c:h+(v?".":"#")+c,e.forced)&&void 0!==f){if(typeof p==typeof f)continue;u(p,f)}(e.sham||f&&f.sham)&&i(p,"sham",!0),a(n,c,p,e)}}},7293:e=>{e.exports=function(e){try{return!!e()}catch(e){return!0}}},7007:(e,t,n)=>{"use strict";n(4916);var r=n(1702),o=n(1320),i=n(2261),a=n(7293),l=n(5112),u=n(8880),s=l("species"),c=RegExp.prototype;e.exports=function(e,t,n,f){var p=l(e),d=!a((function(){var t={};return t[p]=function(){return 7},7!=""[e](t)})),h=d&&!a((function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[s]=function(){return n},n.flags="",n[p]=/./[p]),n.exec=function(){return t=!0,null},n[p](""),!t}));if(!d||!h||n){var m=r(/./[p]),v=t(p,""[e],(function(e,t,n,o,a){var l=r(e),u=t.exec;return u===i||u===c.exec?d&&!a?{done:!0,value:m(t,n,o)}:{done:!0,value:l(n,t,o)}:{done:!1}}));o(String.prototype,e,v[0]),o(c,p,v[1])}f&&u(c[p],"sham",!0)}},2104:e=>{var t=Function.prototype,n=t.apply,r=t.bind,o=t.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?o.bind(n):function(){return o.apply(n,arguments)})},9974:(e,t,n)=>{var r=n(1702),o=n(9662),i=r(r.bind);e.exports=function(e,t){return o(e),void 0===t?e:i?i(e,t):function(){return e.apply(t,arguments)}}},6916:e=>{var t=Function.prototype.call;e.exports=t.bind?t.bind(t):function(){return t.apply(t,arguments)}},6530:(e,t,n)=>{var r=n(9781),o=n(2597),i=Function.prototype,a=r&&Object.getOwnPropertyDescriptor,l=o(i,"name"),u=l&&"something"===function(){}.name,s=l&&(!r||r&&a(i,"name").configurable);e.exports={EXISTS:l,PROPER:u,CONFIGURABLE:s}},1702:e=>{var t=Function.prototype,n=t.bind,r=t.call,o=n&&n.bind(r);e.exports=n?function(e){return e&&o(r,e)}:function(e){return e&&function(){return r.apply(e,arguments)}}},5005:(e,t,n)=>{var r=n(7854),o=n(614),i=function(e){return o(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e]):r[e]&&r[e][t]}},8173:(e,t,n)=>{var r=n(9662);e.exports=function(e,t){var n=e[t];return null==n?void 0:r(n)}},647:(e,t,n)=>{var r=n(1702),o=n(7908),i=Math.floor,a=r("".charAt),l=r("".replace),u=r("".slice),s=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,c=/\$([$&'`]|\d{1,2})/g;e.exports=function(e,t,n,r,f,p){var d=n+e.length,h=r.length,m=c;return void 0!==f&&(f=o(f),m=s),l(p,m,(function(o,l){var s;switch(a(l,0)){case"$":return"$";case"&":return e;case"`":return u(t,0,n);case"'":return u(t,d);case"<":s=f[u(l,1,-1)];break;default:var c=+l;if(0===c)return o;if(c>h){var p=i(c/10);return 0===p?o:p<=h?void 0===r[p-1]?a(l,1):r[p-1]+a(l,1):o}s=r[c-1]}return void 0===s?"":s}))}},7854:(e,t,n)=>{var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},2597:(e,t,n)=>{var r=n(1702),o=n(7908),i=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return i(o(e),t)}},3501:e=>{e.exports={}},490:(e,t,n)=>{var r=n(5005);e.exports=r("document","documentElement")},4664:(e,t,n)=>{var r=n(9781),o=n(7293),i=n(317);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},8361:(e,t,n)=>{var r=n(7854),o=n(1702),i=n(7293),a=n(4326),l=r.Object,u=o("".split);e.exports=i((function(){return!l("z").propertyIsEnumerable(0)}))?function(e){return"String"==a(e)?u(e,""):l(e)}:l},2788:(e,t,n)=>{var r=n(1702),o=n(614),i=n(5465),a=r(Function.toString);o(i.inspectSource)||(i.inspectSource=function(e){return a(e)}),e.exports=i.inspectSource},9909:(e,t,n)=>{var r,o,i,a=n(8536),l=n(7854),u=n(1702),s=n(111),c=n(8880),f=n(2597),p=n(5465),d=n(6200),h=n(3501),m="Object already initialized",v=l.TypeError,g=l.WeakMap;if(a||p.state){var y=p.state||(p.state=new g),b=u(y.get),w=u(y.has),_=u(y.set);r=function(e,t){if(w(y,e))throw new v(m);return t.facade=e,_(y,e,t),t},o=function(e){return b(y,e)||{}},i=function(e){return w(y,e)}}else{var x=d("state");h[x]=!0,r=function(e,t){if(f(e,x))throw new v(m);return t.facade=e,c(e,x,t),t},o=function(e){return f(e,x)?e[x]:{}},i=function(e){return f(e,x)}}e.exports={set:r,get:o,has:i,enforce:function(e){return i(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!s(t)||(n=o(t)).type!==e)throw v("Incompatible receiver, "+e+" required");return n}}}},3157:(e,t,n)=>{var r=n(4326);e.exports=Array.isArray||function(e){return"Array"==r(e)}},614:e=>{e.exports=function(e){return"function"==typeof e}},4411:(e,t,n)=>{var r=n(1702),o=n(7293),i=n(614),a=n(648),l=n(5005),u=n(2788),s=function(){},c=[],f=l("Reflect","construct"),p=/^\s*(?:class|function)\b/,d=r(p.exec),h=!p.exec(s),m=function(e){if(!i(e))return!1;try{return f(s,c,e),!0}catch(e){return!1}};e.exports=!f||o((function(){var e;return m(m.call)||!m(Object)||!m((function(){e=!0}))||e}))?function(e){if(!i(e))return!1;switch(a(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}return h||!!d(p,u(e))}:m},4705:(e,t,n)=>{var r=n(7293),o=n(614),i=/#|\.prototype\./,a=function(e,t){var n=u[l(e)];return n==c||n!=s&&(o(t)?r(t):!!t)},l=a.normalize=function(e){return String(e).replace(i,".").toLowerCase()},u=a.data={},s=a.NATIVE="N",c=a.POLYFILL="P";e.exports=a},111:(e,t,n)=>{var r=n(614);e.exports=function(e){return"object"==typeof e?null!==e:r(e)}},1913:e=>{e.exports=!1},7850:(e,t,n)=>{var r=n(111),o=n(4326),i=n(5112)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[i])?!!t:"RegExp"==o(e))}},2190:(e,t,n)=>{var r=n(7854),o=n(5005),i=n(614),a=n(7976),l=n(3307),u=r.Object;e.exports=l?function(e){return"symbol"==typeof e}:function(e){var t=o("Symbol");return i(t)&&a(t.prototype,u(e))}},6244:(e,t,n)=>{var r=n(7466);e.exports=function(e){return r(e.length)}},133:(e,t,n)=>{var r=n(7392),o=n(7293);e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},8536:(e,t,n)=>{var r=n(7854),o=n(614),i=n(2788),a=r.WeakMap;e.exports=o(a)&&/native code/.test(i(a))},1574:(e,t,n)=>{"use strict";var r=n(9781),o=n(1702),i=n(6916),a=n(7293),l=n(1956),u=n(5181),s=n(5296),c=n(7908),f=n(8361),p=Object.assign,d=Object.defineProperty,h=o([].concat);e.exports=!p||a((function(){if(r&&1!==p({b:1},p(d({},"a",{enumerable:!0,get:function(){d(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol(),o="abcdefghijklmnopqrst";return e[n]=7,o.split("").forEach((function(e){t[e]=e})),7!=p({},e)[n]||l(p({},t)).join("")!=o}))?function(e,t){for(var n=c(e),o=arguments.length,a=1,p=u.f,d=s.f;o>a;)for(var m,v=f(arguments[a++]),g=p?h(l(v),p(v)):l(v),y=g.length,b=0;y>b;)m=g[b++],r&&!i(d,v,m)||(n[m]=v[m]);return n}:p},30:(e,t,n)=>{var r,o=n(9670),i=n(6048),a=n(748),l=n(3501),u=n(490),s=n(317),c=n(6200)("IE_PROTO"),f=function(){},p=function(e){return"
Index.html
\ No newline at end of file diff --git a/client/App.jsx b/client/App.jsx new file mode 100644 index 0000000..09335eb --- /dev/null +++ b/client/App.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { BrowserRouter as Router, Route, Routes as Switch } from 'react-router-dom' +import Access from './components/Access' +import Main from './components/Main' + + +// connect to endpoints +const App = () => ( +
+ + + {/* default page: not yet logged in, route to Access */} + } /> + {/* when logged in, route to Main */} + } /> + + +
+); + +export default App; \ No newline at end of file diff --git a/client/actions/actions.js b/client/actions/actions.js new file mode 100644 index 0000000..906f77d --- /dev/null +++ b/client/actions/actions.js @@ -0,0 +1,107 @@ +import * as types from '../constants/actionTypes'; + +import axios from 'axios'; + +export const logIn = (username, password) => (dispatch) => { + const request = { + method: 'GET', + url: '/database/login', + params: { username, password } + } + + axios.request(request).then((response) => { + if (response.status = 201) dispatch({ + type: types.LOG_IN, + payload: response.data, //will hold the user object + }); + }).catch(console.error); +}; + +export const pageToSignup = () => ({ + type: types.PAGE_TO_SIGN_UP, +}); + +export const signUpUser = (username, password, address) => (dispatch) => { + // const coordinates = {lat, lng} + + const request = { + method: 'POST', + url: '/database/signup', + data: { username, password, address } + } + + axios.request(request).then((response) => { + if (response.status = 201) dispatch({ + type: types.SIGN_UP_USER, + payload: response.data, + }); + }).catch(console.error); +}; + +//update location +export const updateLocation = (user_id, address) => (dispatch) => { + + const request = { + method: 'PUT', + url: '/database', + data: { user_id, address } + } + + axios.request(request).then((response) => { + if (response.status = 201) dispatch({ + type: types.UPDATE_LOCATION, + payload: response.data, + }); + }).catch(console.error); +}; + +export const signUpCancel = () => ({ + type: types.SIGN_UP_CANCEL, +}); + +export const addSelected = (user, boolean) => ({ + type: types.ADD_SELECTED, + payload: { user, boolean }, +}) + +export const getMidpoint = (userCoords, friendCoords) => { + + const lat = (userCoords.lat + friendCoords.lat) / 2; + const lng = (userCoords.lng + friendCoords.lng) / 2; + + return ({ + type: types.GET_MIDPOINT, + payload: { 'lat': lat, 'lng': lng } + }) +} + +export const addFriend = (user1_id, user2_id) => (dispatch) => { + const request = { + method: 'POST', + url: 'database/friend', + data: { user1_id, user2_id } + } + + axios.request(request).then((response) => { + if (response.status = 201) dispatch({ + type: types.ADD_FRIEND, + payload: response.data, + }); + }).catch(console.error); +} + +export const deleteFriend = (user1_id, user2_id) => (dispatch) => { + const request = { + method: 'DELETE', + url: 'database/delete/friend', + data: { user1_id, user2_id } + } + + axios.request(request).then((response) => { + if (response.status = 201) dispatch({ + type: types.DELETE_FRIEND, + payload: { data: response.data, user2: user2_id } + }); + }).catch(console.error); +} + diff --git a/client/components/Access.jsx b/client/components/Access.jsx new file mode 100644 index 0000000..de5ed9b --- /dev/null +++ b/client/components/Access.jsx @@ -0,0 +1,101 @@ +import React, { useState } from 'react'; +import { BrowserRouter as Router, Route, Routes as Switch } from 'react-router-dom' +import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import * as actions from '../actions/actions'; +import Main from './Main'; + +const mapStateToProps = ({ + mainPage: { currentUser, selectedLocations, pageToDisplay, loggedIn, friendsList, notFriendsList, midpoint } +}) => ({ + pageToDisplay, + loggedIn, + friendsList, + notFriendsList, + midpoint, + currentUser, + selectedLocations, +}); + +const mapDispatchToProps = dispatch => ({ + pageToSignup: () => dispatch(actions.pageToSignup()), + signUpCancel: () => dispatch(actions.signUpCancel()), + logIn: (user, pass) => dispatch(actions.logIn(user, pass)), + signUpUser: (user, pass, address) => dispatch(actions.signUpUser(user, pass, address)), + updateLocation: (user_id, address) => dispatch(actions.updateLocation(user_id, address)), + getMidpoint: (user, friendUser) => dispatch(actions.getMidpoint(user, friendUser)), + addFriend: (user1_id, user2_id) => dispatch(actions.addFriend(user1_id, user2_id)), + //deleteFriend method added + deleteFriend: (user1_id, user2_id) => dispatch(actions.deleteFriend(user1_id, user2_id)), + addSelected: (user, boolean) => dispatch(actions.addSelected(user, boolean)), +}); + + +export const Access = ({ addSelected, pageToDisplay, currentUser, selectedLocations, addFriend, deleteFriend, loggedIn, pageToSignup, signUpCancel, logIn, signUpUser, updateLocation, friendsList, notFriendsList, getMidpoint, midpoint }) => { + + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [address, setAddress] = useState(''); + + function onChangeHandler(event) { + const { name, value } = event.currentTarget; + if (name === "username") { + setUsername(value); + } else if (name === 'password') { + setPassword(value); + } else if (name === 'address') { + setAddress(value); + } + } + + if (loggedIn) { + return (
) + } + + // Log In Page + if (pageToDisplay === 'login') return ( +
+ +

Login Page

+ + onChangeHandler(event)} + /> + onChangeHandler(event)} + /> + + + +
+ ); + + + + // Sign Up Page + return ( +
+ +

Sign-up Page

+ + + onChangeHandler(event)}> + onChangeHandler(event)}> + onChangeHandler(event)}> + + + +
+ ); + +} + +export default connect(mapStateToProps, mapDispatchToProps)(Access); diff --git a/client/components/Main.jsx b/client/components/Main.jsx new file mode 100644 index 0000000..88e17e0 --- /dev/null +++ b/client/components/Main.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Map from './Map' +import Sidebar from './Sidebar' + +const Main = (props) => { + return( +
+ + +
+ ) +} + +export default Main; diff --git a/client/components/Map.jsx b/client/components/Map.jsx new file mode 100644 index 0000000..f6d4663 --- /dev/null +++ b/client/components/Map.jsx @@ -0,0 +1,70 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import GoogleMapReact from 'google-map-react'; +import Avatar from 'react-avatar'; +import { GoogleMap, LoadScript, Marker } from '@react-google-maps/api' + + +const imgUrl = 'https://i.imgur.com/WTHBUgm.png'; +// // const Marker = ({ url }) =>
+// const Marker = (props) =>
+ + + +const containerStyle = { + width: '1000px', + height: '600px' +}; + +const center = { + lat: -3.745, + lng: -38.523 +}; + +const Map = (props) => { + // const url = "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png" + + const [map, setMap] = useState(null); + const onLoad = useCallback((map) => setMap(map), []); + useEffect(() => { + if (map) { + const bounds = new window.google.maps.LatLngBounds(); + props.selectedLocations.map(marker => { + bounds.extend({ + lat: marker.coordinates.lat, + lng: marker.coordinates.lng, + }); + }); + map.fitBounds(bounds); + + } + }, [map, props.selectedLocations]); + return ( + +
+
+ + {props.selectedLocations.map(friend => { + const { user_id, username, coordinates } = friend; + const { lat, lng } = coordinates; + const arr = username.split(' ') + return ( + + + ) + })} + {} + +
+
+
+ ) + +} + +export default Map; diff --git a/client/components/Sidebar.jsx b/client/components/Sidebar.jsx new file mode 100644 index 0000000..d739e7c --- /dev/null +++ b/client/components/Sidebar.jsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import Avatar from 'react-avatar'; +import { deleteFriend } from '../actions/actions'; + +const Sidebar = (props) => { + + const [address, setAddress] = useState(''); + + function onChangeHandler(event) { + const { name, value } = event.currentTarget; + if (name === "address") { + setAddress(value); + } + } + + const imgUrl = 'https://i.imgur.com/WTHBUgm.png'; + + return ( + + ) +} + + + + + +export default Sidebar; diff --git a/client/constants/actionTypes.js b/client/constants/actionTypes.js new file mode 100644 index 0000000..dea3fff --- /dev/null +++ b/client/constants/actionTypes.js @@ -0,0 +1,10 @@ +export const LOG_IN = 'LOG_IN'; +export const PAGE_TO_SIGN_UP = 'PAGE_TO_SIGN_UP'; +export const SIGN_UP_USER = 'SIGN_UP_USER'; +export const SIGN_UP_CANCEL = 'SIGN_UP_CANCEL'; +export const UPDATE_LOCATION = 'UPDATE_LOCATION'; +export const GET_MIDPOINT = 'GET_MIDPOINT'; +export const ADD_FRIEND = 'ADD_FRIEND'; +export const DELETE_FRIEND = 'DELETE_FRIEND'; +export const ADD_SELECTED = 'ADD_SELECTED'; + diff --git a/client/index.js b/client/index.js new file mode 100644 index 0000000..cac37dc --- /dev/null +++ b/client/index.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { render } from 'react-dom'; +import { Provider } from 'react-redux'; +import App from './App.jsx'; +import store from './store'; +import styles from './scss/application.scss'; + +render( + // Set up Redux store - wrap the App in the Provider Component and pass in the store + + + + , document.getElementById('root')); diff --git a/client/reducers/combineReducer.js b/client/reducers/combineReducer.js new file mode 100644 index 0000000..91b1655 --- /dev/null +++ b/client/reducers/combineReducer.js @@ -0,0 +1,6 @@ +import { combineReducers } from 'redux'; +import mainPageReducer from './mainPageReducer'; + +export default combineReducers({ + mainPage: mainPageReducer, +}); \ No newline at end of file diff --git a/client/reducers/mainPageReducer.js b/client/reducers/mainPageReducer.js new file mode 100644 index 0000000..9f91410 --- /dev/null +++ b/client/reducers/mainPageReducer.js @@ -0,0 +1,159 @@ +import * as types from '../constants/actionTypes'; +import axios from 'axios'; +import { updateLocation } from '../actions/actions'; + + +const initialState = { + pageToDisplay: 'login', + loggedIn: false, + midpoint: { lat: 40.7142700, lng: -74.0059700 }, + currentUser: {}, + selectedLocations: [], + friendsList: [], + notFriendsList: [], +}; + +const mainPageReducer = (state = initialState, action) => { + switch (action.type) { + case types.PAGE_TO_SIGN_UP: + return { + ...state, + pageToDisplay: 'signup', + }; + + case types.SIGN_UP_CANCEL: + return { + ...state, + pageToDisplay: 'login', + }; + + case types.LOG_IN: { + const { verified, user, friendList, notFriendList } = action.payload; + if (verified) { + const newLocationList = state.selectedLocations.slice(); + newLocationList.push(user); + return { + ...state, + loggedIn: true, + midpoint: user.coordinates, + currentUser: user, + selectedLocations: newLocationList, + friendsList: friendList, + notFriendsList: notFriendList + }; + } + } + case types.SIGN_UP_USER: { + const { verified, message, user, friendList, notFriendList } = action.payload; + if (verified) { + const tempObj = { ...state.selfInfo }; + tempObj.name = user.username; + tempObj.address = user.coordinates; + + const newLocationList = state.selectedLocations.slice(); + newLocationList.push(user); + console.log(tempObj) + return { + ...state, + pageToDisplay: 'login', + loggedIn: true, + midpoint: user.coordinates, + currentUser: user, + selectedLocations: newLocationList, + friendsList: friendList, + notFriendsList: notFriendList, + }; + } + return { + ...state, + pageToDisplay: 'signup', + }; + } + + case types.UPDATE_LOCATION: + const newLocationList = state.selectedLocations.slice(); + newLocationList[0] = action.payload; + + const newMidpoint = newLocationList.reduce((acc, curr, i, arr) => { + acc.lat += (curr.coordinates.lat / arr.length) + acc.lng += (curr.coordinates.lng / arr.length) + return acc; + }, { lat: 0, lng: 0 }); + + return { + ...state, + selectedLocations: newLocationList, //note map updates markers based on selectedLocations property + currentUser: action.payload, + midpoint: newMidpoint + } + + case types.GET_MIDPOINT: + return { + ...state, + midpoint: action.payload + } + + + case types.ADD_FRIEND: + + return { + ...state, + friendsList: action.payload.friendList, + notFriendsList: action.payload.notFriendList, + } + + case types.DELETE_FRIEND: { + let newLocationList = state.selectedLocations.slice(); + + newLocationList = newLocationList.filter(friend => { + return friend.user_id !== action.payload.user2; + }) + + const newMidpoint = newLocationList.reduce((acc, curr, i, arr) => { + acc.lat += (curr.coordinates.lat / arr.length) + acc.lng += (curr.coordinates.lng / arr.length) + return acc; + }, { lat: 0, lng: 0 }); + + return { + ...state, + selectedLocations: newLocationList, + midpoint: newMidpoint, + friendsList: action.payload.data.friendList, + notFriendsList: action.payload.data.notFriendList, + } + } + + + case types.ADD_SELECTED: { + const { user, boolean } = action.payload; + console.log('user, boolean:', user, boolean); + let newLocationList = state.selectedLocations.slice(); + if (boolean) { + newLocationList.push(user); + } + else { + newLocationList = newLocationList.filter(friend => { + return friend.user_id !== user.user_id; + }) + } + + const newMidpoint = newLocationList.reduce((acc, curr, i, arr) => { + acc.lat += (curr.coordinates.lat / arr.length) + acc.lng += (curr.coordinates.lng / arr.length) + return acc; + }, { lat: 0, lng: 0 }); + + return { + ...state, + selectedLocations: newLocationList, + midpoint: newMidpoint, + } + } + + default: + return state; + } +}; + +export default mainPageReducer; \ No newline at end of file diff --git a/client/scss/_halfway.scss b/client/scss/_halfway.scss new file mode 100644 index 0000000..3d6a7c9 --- /dev/null +++ b/client/scss/_halfway.scss @@ -0,0 +1,183 @@ +#app { + display: flex; + background: $DarkBlueBG; + height: 100%; + align-items: top; + justify-content: center; +} + +.loginStyles { + display: flex; + flex-direction: column; + align-items: center; + + img { + margin-bottom: 1em; + }; + + button { + background-color: $Olive; + color: $DarkBlue; + font-size: 2em; + height: 3rem; + border-radius: 15px; + border-style: none; + box-shadow: 1px 2px 5px 1px lightcoral; + min-width: 150px; + + &:hover { + background-color: $DarkBlue; + color: $Olive; + } + } + + input { + font-size: 2em; + height: 3rem; + margin-bottom: 0.5em; + border-radius: 15px; + padding-left: 15px; + } + + h1 { + font-size: 5rem; + color: $Olive; + } +} + + +.mainStyles { + display: flex; + flex-direction: row; + color: $DarkBlue; + height: 100%; + font-size: 1.5em; +} + +.mapStyles { + border: 1px solid gray; + height: 600px; + width: 1000px; + margin: 12px 0px; +} + +.mapContainer { + display: flex; + flex-direction: column; + margin: 0px 0px 0px 20px; +} + +.flexAlignCenter { + display: flex; + align-items: center; +} + +.sidebarStyles { + display: flex; + flex-direction: column; + align-content: center; + justify-content: space-around; + background-color: $Olive; + width: 21%; + height: 95%; + border: 1px solid black; + border-radius: 6px; + margin: 10px 0px 0px 6px; + padding-left: 0.5em; + padding-right: 0.5em; + + input { + font-size: .8em; + height: 2rem; + margin: 5px; + border-radius: 15px; + padding-left: 15px; + background-color: $DarkBlue; + color: $Olive; + } + + button { + background-color: $DarkBlue; + color: $Olive; + font-size: 1em; + + border-radius: 15px; + border-style: none; + box-shadow: 1px 2px 5px 1px lightcoral; + margin: 5px; + + + &:hover { + background-color: $Olive; + color: $DarkBlue; + border: 3px solid; + } + } +} + +.center { + display: flex; + flex-direction: column; + align-content: center; + justify-content: center; + align-items: center; + height: auto; + .scrollContainer { + display: flex; + flex-direction: column; + height: 200px; + overflow: scroll; + width: inherit; + padding: 10px; + } +} + +.picStyles { + height: 40px; + width: 40px; + border-radius: 50%; + margin: 10px 16px +} + +.marker { + display: flex; + justify-content: center; + align-items: center; + height: 50px; + width: 50px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + img { + width:100%; + height: 100%; + position: 'absolute'; + }; +} +.avatar{ + border-radius: 50%; +} +.inputStyles { + background-color: 9db2cf; + margin: 6px; + height: 180px; + overflow: scroll; +} + +.pStyles { + margin-top: 24px; + padding-left: 5px; + padding-right: 5px; +} + +.latlng { + margin-top: 0; + margin-bottom: 0; +} + +.user-container{ + display: flex; + align-items: center; + justify-content: auto; + padding: 5px; +} \ No newline at end of file diff --git a/client/scss/_variables.scss b/client/scss/_variables.scss new file mode 100644 index 0000000..e9a85bc --- /dev/null +++ b/client/scss/_variables.scss @@ -0,0 +1,3 @@ +$DarkBlueBG: linear-gradient(146deg, rgba(50,65,82,1) 0%, rgba(32,44,57,1) 100%); +$DarkBlue: #202c39; +$Olive: #958a56; \ No newline at end of file diff --git a/client/scss/application.scss b/client/scss/application.scss new file mode 100644 index 0000000..b7d24b3 --- /dev/null +++ b/client/scss/application.scss @@ -0,0 +1,2 @@ +@import '_variables'; +@import '_halfway'; \ No newline at end of file diff --git a/client/store.js b/client/store.js new file mode 100644 index 0000000..5dce447 --- /dev/null +++ b/client/store.js @@ -0,0 +1,17 @@ +import { createStore, applyMiddleware } from 'redux'; +import { composeWithDevTools } from 'redux-devtools-extension'; +import thunk from 'redux-thunk'; +import { logIn } from './actions/actions'; +import reducers from './reducers/combineReducer'; + +// we are adding composeWithDevTools here to get easy access to the Redux dev tools +const store = createStore( + reducers, + composeWithDevTools(applyMiddleware(thunk)) +); + + +store.dispatch(logIn('Alex', 'B')); + + +export default store; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..320109b --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + Halfway + + +
Index.html
+ + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..d3232d8 --- /dev/null +++ b/package.json @@ -0,0 +1,95 @@ +{ + "name": "halfway", + "version": "1.0.0", + "description": "A tool for people to meet halfway", + "main": "index.js", + "scripts": { + "start": "NODE_ENV=production nodemon server/server.js", + "build": "NODE_ENV=production webpack", + "dev": "NODE_ENV=development nodemon server/server.js & NODE_ENV=development webpack serve --open", + "gulp-prod": "node_modules/.bin/gulp prod", + "gulp-dev": "node_modules/.bin/gulp dev", + "test": "jest" + }, + "jest": { + "verbose": true, + "modulePaths": [ + "/node_modules/" + ], + "moduleFileExtensions": [ + "js", + "jsx" + ], + "moduleDirectories": [ + "node_modules" + ], + "moduleNameMapper": { + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js", + "\\.(css|less)$": "/__mocks__/styleMock.js" + } + }, + "nodemonConfig": { + "ignore": [ + "build", + "client" + ] + }, + "author": "James Maguire, Cecily Jansen, Johnny Bryan, Yogi Paturu, John Lin", + "license": "ISC", + "dependencies": { + "@babel/plugin-transform-async-to-generator": "^7.16.0", + "@babel/runtime": "^7.16.0", + "@react-google-maps/api": "^2.5.0", + "axios": "^0.24.0", + "bcryptjs": "^2.4.3", + "eslint": "^8.1.0", + "express": "^4.12.3", + "google-map-react": "^2.1.10", + "node-geocoder": "^3.27.0", + "pg": "^8.7.1", + "prop-types": "^15.7.2", + "react": "^17.0.2", + "react-avatar": "^3.10.0", + "react-avatar-editor": "^12.0.0", + "react-dom": "^17.0.2", + "react-redux": "^7.2.6", + "react-router-dom": "^6.0.1", + "recharts": "^2.1.6", + "redux": "^4.1.2", + "redux-devtools-extension": "^2.13.9", + "redux-thunk": "^2.4.0" + }, + "devDependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-syntax-jsx": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.0", + "@babel/preset-env": "^7.16.0", + "@babel/preset-react": "^7.16.0", + "@wojtekmaj/enzyme-adapter-react-17": "^0.6.5", + "babel-core": "^6.26.3", + "babel-jest": "^27.3.1", + "babel-loader": "^8.2.3", + "concurrently": "^6.0.2", + "cross-env": "^7.0.3", + "css-loader": "^6.5.0", + "enzyme": "^3.11.0", + "enzyme-to-json": "^3.6.2", + "html-webpack-plugin": "^5.5.0", + "isomorphic-fetch": "^3.0.0", + "jest": "^27.3.1", + "nodemon": "^2.0.7", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-redux": "^7.2.6", + "react-router-dom": "^6.0.1", + "react-test-renderer": "^17.0.2", + "redux-mock-store": "^1.5.4", + "sass": "^1.43.4", + "sass-loader": "^12.3.0", + "style-loader": "^3.3.1", + "supertest": "^6.1.6", + "webpack": "^5.61.0", + "webpack-cli": "^4.8.0", + "webpack-dev-server": "^4.4.0" + } +} diff --git a/server/controllers/databaseController.js b/server/controllers/databaseController.js new file mode 100644 index 0000000..affa19a --- /dev/null +++ b/server/controllers/databaseController.js @@ -0,0 +1,252 @@ +const db = require('../models/model'); +const bcrypt = require('bcryptjs'); +const NodeGeocoder = require('node-geocoder'); + +const dbController = {}; + +const options = { + provider: 'google', + apiKey: 'AIzaSyAG8pD29eYb7EnZNrNFinFbmMtJiqqnzKI', +} + +const geocoder = NodeGeocoder(options); + +// verify an existing user +/* +Expects: + req.query = { username: string, password: string } +Returns: + res.locals.verified: boolean + res.locals. message: string + res.locals.user: userObject + + userObject: { user_id: int, username: string, password: string, coordinates: { lat: num, lng: num } } +*/ +dbController.verifyUser = async (req, res, next) => { + const { username, password } = req.query; + const query = `SELECT * FROM users WHERE users.username = $1` + const values = [username]; + try { + // await query response + const response = await db.query(query, values); + // send error if user not found + if (!response.rows.length) { + // TODO! - make this an error + res.status(404); + res.locals.verified = false; + res.locals.message = 'No user found!'; + res.locals.user = {}; + return next(); + } + const user = response.rows[0]; + const valid = await bcrypt.compare(password, user.password); + // send error if passwords don't match + if (!valid) { + // TODO! - make this an error + res.status(401); + res.locals.verified = false; + res.locals.message = 'Invalid password'; + res.locals.user = {}; + return next(); + } + // send object upon successful log-in + else { + res.status(200); + res.locals.verified = true; + res.locals.message = 'User verified!'; + res.locals.user = user; + return next(); + } + } catch (err) { + return next(err); + } +} + +// post/create a new user (encrypt password) +/* +Expects: + req.body = { username: string, password: string, address: string } +Returns: + res.locals.verified = boolean, + res.locals.message = string, + res.locals.user = userObj +*/ +dbController.addUser = async (req, res, next) => { + const { username, password, address } = req.body; + + try { + // turns address into coordinates + const geoData = await geocoder.geocode(address); + const coordinates = { lat: geoData[0].latitude, lng: geoData[0].longitude }; + if (typeof username === 'string' && typeof password === 'string' && username.length && password.length) { + // encrypt the password + const encrypted = await bcrypt.hash(password, 10); + const query = `INSERT INTO users(username, password, coordinates) VALUES($1, $2, $3) RETURNING *`; + const values = [username, encrypted, JSON.stringify(coordinates)]; + const response = await db.query(query, values); + const user = response.rows[0]; + + res.locals.verified = true; + res.locals.message = 'User created!' + res.locals.user = user; + return next(); + } else { + res.status(401).send({ + verified: false, + message: 'Invalid username and/or password!', + user: {}, + }) + return next(); + } + } catch (err) { + return next(err); + } +} + + + +// PUT / update a user's location +/* +Expects: + req.body = { user_id, newCoordinates } +Returns: + res.locals.user = userObj; +*/ +dbController.updateUser = async (req, res, next) => { + const {user_id} = req.body; + const query = `UPDATE users SET coordinates = $2 WHERE user_id = $1 RETURNING *`; + const values = [user_id, JSON.stringify(res.locals.coords)]; + + try { + const response = await db.query(query, values); + console.log(response) + res.locals.user = response.rows[0]; + return next(); + } catch (err) { + return next(err); + } +} + +// get list of all users on the current users friend list +/* +Expects: + res.locals.user = { user_id: int } +Returns: + res.locals.friendList = [ userObj ] +*/ +dbController.getFriendList = async (req, res, next) => { + try { + const { user_id } = res.locals.user; + const query = ` + SELECT u2.user_id, u2.username, u2.coordinates + FROM users u1 JOIN friends + ON u1.user_id = friends.user1_id + JOIN users u2 + ON u2.user_id = friends.user2_id + WHERE u1.user_id = $1 + `; + const values = [user_id]; + const response = await db.query(query, values); + res.locals.friendList = response.rows; + return next(); + } catch (err) { + return next(err); + } +} + +// get list of all users NOT on current user's friends list +/* +Expects: + res.locals.user = { user_id: int } +Returns: + res.locals.notFriendList = [ userObj ] +*/ +dbController.getNotFriendList = async (req, res, next) => { + try { + const { user_id } = res.locals.user; + const query = ` + SELECT * FROM users WHERE + user_id != $1 AND + user_id NOT IN (SELECT user2_id from users JOIN friends ON users.user_id = friends.user1_id + WHERE users.user_id = $1) + `; + const values = [user_id]; + // send data via res locals + const response = await db.query(query, values); + res.locals.notFriendList = response.rows; + return next(); + } catch (err) { + return next(err); + } +} + +// given an address as a string return the coordinates +/* +Expects: +Returns: +*/ +dbController.getCoords = async (req, res, next) => { + try { + const { address } = req.body + const geoData = await geocoder.geocode(address); + const coordinates = { lat: geoData[0].latitude, lng: geoData[0].longitude }; + res.locals.coords = coordinates; + console.log(coordinates) + return next(); + } + catch (err) { + return next(err) + } +} + +// adds a new friend to the current users friend list +/* +Expects: + req.body = { user1_id: int, user2_id: int } +Returns: + res.locals.insert = userObj +*/ +dbController.addFriend = async (req, res, next) => { + try { + const { user1_id, user2_id } = req.body; + const values = [user1_id, user2_id]; + const query = ` + INSERT INTO friends (user1_id, user2_id) VALUES($1, $2) + RETURNING user1_id as user_id + `; + const insert = await db.query(query, values); + res.locals.user = insert.rows[0]; + return next(); + } + catch (err) { + return next(err); + } +} + +// TODOS // +// DELETE user from the current users' friend list +/* +Expects: + req.body = { user1_id: int, user2_id: int } +Returns: + res.locals.insert = userObj +*/ +dbController.deleteFriend = async (req, res, next) => { + const {user1_id, user2_id} = req.body; + const values = [user1_id, user2_id]; + const query = ` + DELETE FROM friends WHERE user1_id = $1 and user2_id = $2 + RETURNING user1_id as user_id + `; + + try { + const deletedRow = await db.query(query, values); + res.locals.user = deletedRow.rows[0]; + return next(); + } + catch (err) { + return next(err); + } +} + +module.exports = dbController; \ No newline at end of file diff --git a/server/models/model.js b/server/models/model.js new file mode 100644 index 0000000..3a654c1 --- /dev/null +++ b/server/models/model.js @@ -0,0 +1,18 @@ +const { Pool } = require('pg'); + +const PG_URI = `postgres://gwzelhay:bpQW0XH3OXZFr34fAp1-Bj8x1997cnXS@fanny.db.elephantsql.com/gwzelhay` + +// create a new pool here using the connection string above +const pool = new Pool({ + connectionString: PG_URI +}); + +// We export an object that contains a property called query, +// which is a function that returns the invocation of pool.query() after logging the query +// This will be required in the controllers to be the access point to the database +module.exports = { + query: (text, params, callback) => { + console.log('executed query', text); + return pool.query(text, params, callback); + } +}; \ No newline at end of file diff --git a/server/routes/database.js b/server/routes/database.js new file mode 100644 index 0000000..1c5aa64 --- /dev/null +++ b/server/routes/database.js @@ -0,0 +1,89 @@ +const express = require('express'); +const dbController = require('../controllers/databaseController'); +const router = express.Router(); + +// get/verify current user, get a list of all of their friends, and all of their NOT friends +/* +Expects: + req.query = { username: string, password: string } +Returns: { + verified: boolean + message: string + user: userObject + friendList: [ userObject ] + notFriendList: [ userObject ] + } + + userObject: { user_id: int, username: string, password: string, coordinates: { lat: num, lng: num } } +*/ +router.get('/login', dbController.verifyUser, dbController.getFriendList, dbController.getNotFriendList, (req, res) => { + return res.json(res.locals); +}); + +// post/create a new user (encrypt password) +/* +Expects: + req.body = { username: string, password: string, address: string } +Returns: { + verified = boolean, + message = string, + user = userObj, + friendList: [ userObject ] + notFriendList: [ userObject ] + } +*/ +router.post('/signup', dbController.addUser, dbController.getFriendList, dbController.getNotFriendList, (req, res) => { + return res.status(201).json(res.locals); +}); + +// put/update current user's data (location, interests) +/* +Expects: + req.body = { user_id, newCoordinates } +Returns: + userObj; +*/ +router.put('/', dbController.getCoords, dbController.updateUser, (req, res) => { + return res.status(201).json(res.locals.user); +}) + +// add two users to the "friends" table, get the new friends list of current user, get the new NOT friends list of current user +/* +Expects: + req.body = { user1_id: int, user2_id: int } +Returns: { + user: userObj, + friendList: [ userObj ], + notFriendList: [ userObj ], +} +*/ +router.post('/friend', dbController.addFriend, dbController.getFriendList, dbController.getNotFriendList, (req, res) => { + return res.status(201).json(res.locals) +}) + +// delete user from the "friends" table, get the latest friends list of current user, get the new NOT friends list of current user +/* +Expects: + req.body = { user1_id: int, user2_id: int } +Returns: { + user: userObj, + friendList: [ userObj ], + notFriendList: [ userObj ], +} +*/ + +router.delete('/delete/friend', dbController.deleteFriend, dbController.getFriendList, dbController.getNotFriendList, (req, res) => { + return res.status(201).json(res.locals) +}) + + +router.get('/coordinates', dbController.getCoords, (req, res) => { + return res.status(200).json(res.locals.coords); +}) +// TODOS // + +// delete/remove user from friend list + +// let other user confirm whether or not to be friends + +module.exports = router; \ No newline at end of file diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..0bd595a --- /dev/null +++ b/server/server.js @@ -0,0 +1,34 @@ +const path = require('path'); +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; +const databaseRouter = require('./routes/database'); + +// parse requests +app.use(express.json()); +app.use(express.urlencoded({extended: true})); + +// need routers +app.use('/database', databaseRouter); + +// serve static HTML + +// global error handler +app.use((err, req, res, next) => { + // const defaultErr = { + // log: 'Express error handler caught unknown middleware error', + // status: 500, + // message: { err: 'An error occurred on line 37 of Server.js' }, + // }; + // const errorObj = Object.assign({}, defaultErr, err); + console.log(err.log); + return res.status(err.status).json(err.message); +}); + + +// start server +app.listen(port, () => { + console.log(`Server listening on port: ${port}`); +}); + +module.exports = app; \ No newline at end of file diff --git a/sum.js b/sum.js new file mode 100644 index 0000000..41c4bff --- /dev/null +++ b/sum.js @@ -0,0 +1,5 @@ +function sum(a, b) { + return a + b; +} + +module.exports = sum; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..95cf0b1 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,46 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); + +module.exports = { + mode: process.env.NODE_ENV, + entry: { + index: './client/index.js' + }, + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'build') + }, + devServer: { + static: { + directory: path.resolve(__dirname, 'build'), + publicPath: './build', + }, + proxy: { + '/': 'http://localhost:3000' + }, + }, + plugins: [new HtmlWebpackPlugin({ + template: './index.html', + })], + module: { + rules: [{ + test: /\.jsx?/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', '@babel/preset-react'], + plugins: ['@babel/plugin-transform-runtime', '@babel/transform-async-to-generator', '@babel/plugin-syntax-jsx'], + } + }, + }, + { + test: /\.s[ac]ss$/i, + use: ['style-loader', 'css-loader', 'sass-loader', ] + } + ] + }, + resolve: { + extensions: ['.js', '.jsx'], + }, +}; \ No newline at end of file