From 07b9905945bf1b560f5fb2988081f3b57a6667d4 Mon Sep 17 00:00:00 2001 From: Christopher Chang Date: Sun, 7 Nov 2021 01:33:39 -0800 Subject: [PATCH] Update api structure and use database for locations page (#143) * Move api structure to router and update locations schema * Update auth uri to match api * Use database for locations name and type * Remove location tests * Update PhotoHeader test * Format --- client/src/actions/currentUserActions.ts | 2 +- .../src/components/Header/LocationHeader.tsx | 4 +- client/src/components/Header/MapHeader.tsx | 4 +- client/src/components/Header/ProfileIcon.tsx | 2 +- .../pages/LocationPage/PhotoHeader.test.tsx | 2 +- client/src/pages/LocationPage/PhotoHeader.tsx | 20 ++--- .../__snapshots__/PhotoHeader.test.tsx.snap | 4 +- client/src/pages/LocationPage/index.tsx | 79 +++++++++++++------ src/app.ts | 6 +- src/config/passport.ts | 2 +- src/models/locationsModel.ts | 29 ++++++- src/routes/auth.test.ts | 15 +--- src/routes/auth.ts | 48 +++++------ src/routes/index.ts | 13 +++ src/routes/locations.ts | 45 +++++++++++ src/routes/users.test.ts | 17 ++++ src/routes/users.ts | 13 +++ 17 files changed, 211 insertions(+), 94 deletions(-) create mode 100644 src/routes/index.ts create mode 100644 src/routes/locations.ts create mode 100644 src/routes/users.test.ts create mode 100644 src/routes/users.ts diff --git a/client/src/actions/currentUserActions.ts b/client/src/actions/currentUserActions.ts index 08c6c82..c8b4a71 100644 --- a/client/src/actions/currentUserActions.ts +++ b/client/src/actions/currentUserActions.ts @@ -20,7 +20,7 @@ export const currentUserActionTypes = { export const setCurrentUser = () => { return (dispatch: Dispatch) => { axios - .get('/api/v1/current-user') + .get('/api/v1/users/current-user') .then((res) => { console.log(res.data); if (res.data && res.status === 200) { diff --git a/client/src/components/Header/LocationHeader.tsx b/client/src/components/Header/LocationHeader.tsx index eddfd6e..b8890a9 100644 --- a/client/src/components/Header/LocationHeader.tsx +++ b/client/src/components/Header/LocationHeader.tsx @@ -19,14 +19,14 @@ function LocationHeader() { ) : ( Sign in diff --git a/client/src/components/Header/MapHeader.tsx b/client/src/components/Header/MapHeader.tsx index e12b2a9..db474a6 100644 --- a/client/src/components/Header/MapHeader.tsx +++ b/client/src/components/Header/MapHeader.tsx @@ -32,13 +32,13 @@ function MapHeader() { isBigScreen && ( Sign in Sign up diff --git a/client/src/components/Header/ProfileIcon.tsx b/client/src/components/Header/ProfileIcon.tsx index 93de88e..4e80a29 100644 --- a/client/src/components/Header/ProfileIcon.tsx +++ b/client/src/components/Header/ProfileIcon.tsx @@ -85,7 +85,7 @@ function ProfileIcon() { - + Logout diff --git a/client/src/pages/LocationPage/PhotoHeader.test.tsx b/client/src/pages/LocationPage/PhotoHeader.test.tsx index 66318eb..f1e34c5 100644 --- a/client/src/pages/LocationPage/PhotoHeader.test.tsx +++ b/client/src/pages/LocationPage/PhotoHeader.test.tsx @@ -4,7 +4,7 @@ import PhotoHeader from './PhotoHeader'; describe('PhotoHeader', () => { it('should render successfully', () => { const renderer = ShallowRenderer.createRenderer(); - const tree = renderer.render(); + const tree = renderer.render(); expect(tree).toMatchSnapshot(); }); }); diff --git a/client/src/pages/LocationPage/PhotoHeader.tsx b/client/src/pages/LocationPage/PhotoHeader.tsx index 9982e32..349e0ee 100644 --- a/client/src/pages/LocationPage/PhotoHeader.tsx +++ b/client/src/pages/LocationPage/PhotoHeader.tsx @@ -2,25 +2,21 @@ import { Container, Stack } from '@mui/material'; import { Link } from 'react-router-dom'; import './PhotoHeader.scss'; -function PhotoHeader(props: { - selected: null | mapboxgl.MapboxGeoJSONFeature; +function PhotoHeader({ + // TODO: change any type to LocationType + name = '...', + type = '...', +}: { + name: string; + type: string; }) { - let name = '...'; - let category = '...'; - - if (props.selected && props.selected.properties) { - const properties = props.selected.properties; - name = properties.name; - category = properties.category_en || properties.type; - } - return (
{name}
-
{category}
+
{type}
See more photos diff --git a/client/src/pages/LocationPage/__snapshots__/PhotoHeader.test.tsx.snap b/client/src/pages/LocationPage/__snapshots__/PhotoHeader.test.tsx.snap index ee2153f..7f8ab86 100644 --- a/client/src/pages/LocationPage/__snapshots__/PhotoHeader.test.tsx.snap +++ b/client/src/pages/LocationPage/__snapshots__/PhotoHeader.test.tsx.snap @@ -16,12 +16,12 @@ exports[`PhotoHeader should render successfully 1`] = `
- ... + name
- ... + type
(); - const [selected, setSelected] = - useState(null); + const [selected, setSelected] = useState(null); const mapInstance = useSelector((state: RootState) => state.mapInstance.map); // Set point useEffect(() => { - if (mapInstance) { - const setSelectedPoint = () => { - const points = mapInstance.querySourceFeatures('composite', { - sourceLayer: 'poi_label', - }); - - setSelected(points.filter((point) => point.id == id)[0]); - }; - - if (mapInstance.isStyleLoaded()) { - setSelectedPoint(); - } else { - mapInstance.once('loaded', () => { - setSelectedPoint(); - }); - } - } - }, [id, mapInstance]); + axios + .get(`/api/v1/locations/loc/${id}`) + .then((res) => { + setSelected(res.data); + }) + .catch((err) => { + if (mapInstance) { + const setSelectedPoint = () => { + const points = mapInstance.querySourceFeatures('composite', { + sourceLayer: 'poi_label', + }); + + const point = points.filter((point) => point.id == id)[0]; - console.log(selected); + if (point && point.properties) { + // Request to create location data + axios + .post(`/api/v1/locations/loc/${id}`, { + id: point.id, + name: point.properties.name, + type: point.properties.category_en || point.properties.type, + // @ts-ignore + tile: point.tile, + }) + .then((res) => { + setSelected(res.data); + }); + } + }; + + if (mapInstance.isStyleLoaded()) { + setSelectedPoint(); + } else { + mapInstance.once('loaded', () => { + setSelectedPoint(); + }); + } + } + }); + }, [id, mapInstance]); return (
- + diff --git a/src/app.ts b/src/app.ts index 07ddfa1..894beeb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,7 +4,7 @@ import session from 'express-session'; import passport from 'passport'; import dotenv from 'dotenv'; import * as path from 'path'; -import auth from './routes/auth'; +import routes from './routes'; import './models/usersModel'; dotenv.config(); @@ -30,9 +30,7 @@ mongoose app.use(passport.initialize()); app.use(passport.session()); -import './config/passport'; - -auth(app); +app.use('/api', routes); // Serve static files from the React app app.use(express.static(path.join(__dirname, '../client/build'))); diff --git a/src/config/passport.ts b/src/config/passport.ts index 82282fe..8e17ef8 100644 --- a/src/config/passport.ts +++ b/src/config/passport.ts @@ -19,7 +19,7 @@ passport.use( clientID: '796959289157-ser2d8agd1nv5t1kl64ii4rtqfi7blb7.apps.googleusercontent.com', clientSecret: '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', - callbackURL: '/auth/google/callback', + callbackURL: '/api/v1/auth/google/callback', proxy: true, }, (_accessToken, _refreshToken, profile, done) => { diff --git a/src/models/locationsModel.ts b/src/models/locationsModel.ts index 0f21e36..1521241 100644 --- a/src/models/locationsModel.ts +++ b/src/models/locationsModel.ts @@ -1,23 +1,44 @@ import mongoose, { Schema, Document } from 'mongoose'; -export interface ILocationsModel extends Document { +export interface ILocation extends Document { name: string; id: string; type?: string; } const locationsSchema = new Schema({ + id: { + type: String, + required: true, + }, name: { type: String, required: true, }, - id: { + type: { type: String, required: true, }, - type: String, + description: { + type: String, + required: true, + }, + tile: { + x: { + type: Number, + required: true, + }, + y: { + type: Number, + required: true, + }, + z: { + type: Number, + required: true, + }, + }, }); -const Location = mongoose.model('Location', locationsSchema); +const Location = mongoose.model('Location', locationsSchema); export default Location; diff --git a/src/routes/auth.test.ts b/src/routes/auth.test.ts index 20d7950..2bc6ce5 100644 --- a/src/routes/auth.test.ts +++ b/src/routes/auth.test.ts @@ -5,21 +5,10 @@ import supertest from 'supertest'; import app from '../app'; -describe('/v1/current-user', () => { - it('should status 404 when not logged in', async () => { - await supertest(app) - .get('/api/v1/current-user') - .then((response) => { - expect(response.statusCode).toBe(404); - expect(response.body).toEqual({}); - }); - }); -}); - -describe('/v1/logout', () => { +describe('/v1/auth/logout', () => { it('should status 302', async () => { await supertest(app) - .post('/api/v1/logout') + .post('/api/v1/auth/logout') .then((response) => { expect(response.statusCode).toBe(302); }); diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 38c8e86..6856bea 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,36 +1,26 @@ import passport from 'passport'; import express from 'express'; -const mockApp = express(); +const router = express.Router(); -function auth(app: typeof mockApp): void { - app.get( - '/auth/google', - passport.authenticate('google', { - scope: ['profile', 'email'], - }) - ); +router.get( + '/v1/auth/google', + passport.authenticate('google', { + scope: ['profile', 'email'], + }) +); - app.get( - '/auth/google/callback', - passport.authenticate('google', { failureRedirect: '/auth/google' }), - (req, res) => { - res.redirect('/'); - } - ); - - app.get('/api/v1/current-user', (req, res) => { - if (req.user) { - res.status(200).send(req.user); - } else { - res.status(404).send(null); - } - }); - - app.post('/api/v1/logout', (req, res) => { - req.logout(); +router.get( + '/v1/auth/google/callback', + passport.authenticate('google', { failureRedirect: 'api/v1/auth/google' }), + (req, res) => { res.redirect('/'); - }); -} + } +); + +router.post('/v1/auth/logout', (req, res) => { + req.logout(); + res.redirect('/'); +}); -export default auth; +export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..a471be1 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,13 @@ +import express from 'express'; +import auth from './auth'; +import users from './users'; +import locations from './locations'; +import '../config/passport'; + +const router = express.Router(); + +router.use('/', auth); +router.use('/', users); +router.use('/', locations); + +export default router; diff --git a/src/routes/locations.ts b/src/routes/locations.ts new file mode 100644 index 0000000..768635b --- /dev/null +++ b/src/routes/locations.ts @@ -0,0 +1,45 @@ +import express from 'express'; +import { Error } from 'mongoose'; +import Location, { ILocation } from '../models/locationsModel'; + +const router = express.Router(); + +// Get location information +router.get('/v1/locations/loc/:id', (req, res) => { + Location.findOne({ id: req.params.id }, (err: Error, location: ILocation) => { + if (err) { + res.status(404).send(err); + } else if (!location) { + res.status(404).send('Location not found'); + } else { + res.status(200).send(location); + } + }); +}); + +// Find or create location +router.post('/v1/locations/loc/:id', (req, res) => { + Location.findOne({ id: req.params.id }, (err: Error, location: ILocation) => { + if (!err && location) { + // Location exists + res.status(200).send(location); + } else { + // Create new location + new Location({ + id: req.params.id, + name: req.body.name, + type: req.body.type, + description: `${req.body.name} is a location.`, + tile: req.body.tile, + }).save((err: Error, location: ILocation) => { + if (err) { + res.status(500).send(err); + } else { + res.status(201).send(location); + } + }); + } + }); +}); + +export default router; diff --git a/src/routes/users.test.ts b/src/routes/users.test.ts new file mode 100644 index 0000000..1efcd6c --- /dev/null +++ b/src/routes/users.test.ts @@ -0,0 +1,17 @@ +/** + * @jest-environment node + */ + +import supertest from 'supertest'; +import app from '../app'; + +describe('/v1/users/current-user', () => { + it('should status 404 when not logged in', async () => { + await supertest(app) + .get('/api/v1/users/current-user') + .then((response) => { + expect(response.statusCode).toBe(404); + expect(response.body).toEqual({}); + }); + }); +}); diff --git a/src/routes/users.ts b/src/routes/users.ts new file mode 100644 index 0000000..ca8e6f0 --- /dev/null +++ b/src/routes/users.ts @@ -0,0 +1,13 @@ +import express from 'express'; + +const router = express.Router(); + +router.get('/v1/users/current-user', (req, res) => { + if (req.user) { + res.status(200).send(req.user); + } else { + res.status(404).send(null); + } +}); + +export default router;