Adding dependencies, updating DB connection, updating api calls to db, adding base64 image start.

This commit is contained in:
Bradley Shellnut 2022-01-27 20:54:29 -08:00
parent e979a83dca
commit 197db9135b
17 changed files with 11080 additions and 3138 deletions

8
__tests__/Home.test.js Normal file
View file

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import Home from '../pages';
describe('Index Page <Home />', () => {
it('should render', () => {
render(<Home />);
});
});

15
__tests__/Nav.test.js Normal file
View file

@ -0,0 +1,15 @@
import { render, screen } from '@testing-library/react';
import Nav from '../components/Nav';
const useRouter = jest.spyOn(require('next/router'), 'useRouter');
describe('<Nav/>', () => {
it('Renders nav correctly and matches snapshot', () => {
const router = { pathname: '/' };
useRouter.mockReturnValue(router);
const { container, debug } = render(<Nav />);
expect(container).toMatchSnapshot();
const link = screen.getByText('RSVP');
expect(link).toHaveAttribute('href', '/rsvp');
});
});

View file

@ -0,0 +1,22 @@
import buildBase64Data from '../utils/buildBase64Data';
describe('build base 64 function', () => {
const imageName = 'David_Best_Man_o8k9pb';
const alt = 'test alt';
it('takes an image name and builds base64 image', async () => {
const imageData = await buildBase64Data(imageName, alt, {});
expect(imageData).toBeDefined();
expect(imageData.alt).toEqual(alt);
expect(imageData.imageProps).toBeDefined();
expect(imageData.imageProps.blurDataURL).toContain(
'data:image/jpeg;base64'
);
expect(imageData.imageProps.height).toBeGreaterThan(0);
expect(imageData.imageProps.width).toBeGreaterThan(0);
expect(imageData.imageProps.src).toContain(imageName);
});
it('fails if image not resolved', async () => {
expect(await buildBase64Data('Blah', alt, {})).toEqual({});
});
});

8
__tests__/index.test.js Normal file
View file

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import Home from '../pages/index';
describe('Home', () => {
it('renders home page', () => {
render(<Home />);
});
});

View file

@ -11,7 +11,7 @@ const NavStyles = styled.nav`
a, a,
button { button {
padding: 1rem 3rem; padding: 1rem 2.5rem;
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;

11
knexfile.js Normal file
View file

@ -0,0 +1,11 @@
/* prettier-ignore */
module.exports = {
client: 'pg',
connection: process.env.SUPABASE_URL,
searchPath: ['knex','public'],
pool: {
min: 0,
max: 50,
acquireTimeoutMillis: 60 * 1000
}
};

View file

@ -1,6 +1,6 @@
[ [
{ {
"name": "Wedding Practicing", "name": "Wedding Rehearsal",
"date": "Sunday, June 2nd, 2030", "date": "Sunday, June 2nd, 2030",
"start": "", "start": "",
"end": "", "end": "",
@ -12,7 +12,7 @@
"scheduleEvents": [] "scheduleEvents": []
}, },
{ {
"name": "Wedding Saying Hello", "name": "Saying Hello",
"date": "Sunday, June 2nd, 2030", "date": "Sunday, June 2nd, 2030",
"start": "9:00 PM", "start": "9:00 PM",
"end": "", "end": "",
@ -24,7 +24,7 @@
"scheduleEvents": [] "scheduleEvents": []
}, },
{ {
"name": "Wedding Saying I Do", "name": "Saying I Do",
"date": "Monday, June 3rd, 2030", "date": "Monday, June 3rd, 2030",
"start": "5:00 PM", "start": "5:00 PM",
"end": "11:00 PM", "end": "11:00 PM",
@ -55,7 +55,7 @@
] ]
}, },
{ {
"name": "Wedding Saying Goodbye", "name": "Saying Goodbye",
"date": "Tuesday, June 4th, 2030", "date": "Tuesday, June 4th, 2030",
"start": "", "start": "",
"end": "", "end": "",
@ -66,4 +66,4 @@
"showSchedule": false, "showSchedule": false,
"scheduleEvents": [] "scheduleEvents": []
} }
] ]

13818
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,41 +6,52 @@
"scripts": { "scripts": {
"dev": "NODE_OPTIONS='--inspect' next dev", "dev": "NODE_OPTIONS='--inspect' next dev",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start",
"test:pa11y": "node tests/pa11y"
}, },
"dependencies": { "dependencies": {
"@plaiceholder/next": "^2.2.0",
"@reach/dialog": "^0.16.2", "@reach/dialog": "^0.16.2",
"@reach/portal": "^0.16.2", "@reach/portal": "^0.16.2",
"@reach/visually-hidden": "^0.16.0", "@reach/visually-hidden": "^0.16.0",
"@types/pg": "^8.6.4",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-plugin-styled-components": "^2.0.2", "babel-plugin-styled-components": "^2.0.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cloudinary-build-url": "^0.2.1", "cloudinary-build-url": "^0.2.4",
"dotenv": "^10.0.0", "dotenv": "^14.3.2",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"file-system": "^2.2.2",
"iron-session": "^6.0.5", "iron-session": "^6.0.5",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"mongodb": "^4.3.0", "knex": "^1.0.1",
"mongoose": "^6.1.5", "mongodb": "^4.3.1",
"next": "^12.0.7", "mongoose": "^6.1.8",
"next": "^12.0.9",
"next-with-apollo": "^5.2.1", "next-with-apollo": "^5.2.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pa11y": "^6.1.1",
"pg": "^8.7.1",
"plaiceholder": "^2.2.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"sharp": "^0.29.3",
"styled-components": "^5.3.3", "styled-components": "^5.3.3",
"swr": "^0.5.6", "swr": "^0.5.6",
"waait": "^1.0.5" "waait": "^1.0.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.7", "@babel/core": "^7.16.12",
"@babel/preset-env": "^7.16.7", "@babel/preset-env": "^7.16.11",
"@typescript-eslint/eslint-plugin": "^5.9.0", "@testing-library/jest-dom": "^5.16.1",
"@typescript-eslint/parser": "^5.9.0", "@testing-library/react": "^12.1.2",
"babel-eslint": "^10.1.0", "@testing-library/user-event": "^13.5.0",
"eslint": "^8.6.0", "@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"eslint": "^8.7.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-config-wesbos": "^3.0.2", "eslint-config-wesbos": "^3.0.2",
@ -50,8 +61,9 @@
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.28.0", "eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-react-hooks": "^4.3.0",
"jest": "^27.4.7",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"typescript": "^4.5.4" "typescript": "^4.5.5"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@ -126,4 +138,4 @@
} }
} }
} }
} }

View file

@ -1,101 +0,0 @@
import escape from 'escape-html';
import withSession from '../../lib/session';
import Group from '../../models/Group';
import Guest from '../../models/Guest';
import connectDb from '../../utils/db';
export default withSession(async (req, res) => {
const {
query: { id },
method,
body,
session,
} = req;
const { user } = session;
if (!user?.isLoggedIn) {
res.status(401).end();
return;
}
// TODO: REMOVE THIS WHEN TAKING YOUR SITE TO PRODUCTION
// In production just await connectDB()
if (process.env.SITE_ENV !== 'TEST_SITE') {
await connectDb();
}
const response = {};
switch (method) {
case 'GET':
try {
const group = await Group.findById(id);
// console.log('group', group);
response.id = id;
const guestList = [];
for (const guestId of group?.guests || []) {
// console.log(JSON.stringify(guestId));
const guestData = await Guest.findById(guestId);
const guest = {
id: guestData.id,
firstName: guestData.firstName,
lastName: guestData.lastName,
role: guestData.role,
rsvpStatus: guestData.rsvpStatus || '',
dietaryNotes: guestData.dietaryNotes || '',
songRequests: guestData.songRequests || '',
};
guestList.push(guest);
}
response.guests = guestList;
response.note = group?.note || '';
// console.log('response', response);
res.status(200).json(JSON.stringify(response));
} catch (error) {
const { response: fetchResponse } = error;
res.status(fetchResponse?.status || 500).json(error.data);
}
break;
case 'POST':
try {
// TODO: REMOVE THIS WHEN TAKING YOUR SITE TO PRODUCTION
if (process.env.SITE_ENV === 'TEST_SITE') {
console.log('DONE!');
res.status(200).json(JSON.stringify({ message: 'SUCCESS' }));
} else {
const { groupId, guests, note } = body;
for (const guest of guests) {
// console.log(`Updating ${guest.id} with status ${guest.rsvpStatus}`);
const guestData = await Guest.findById(guest.id);
const accepted = guest?.rsvpStatus === 'accepted';
guestData.rsvpStatus =
guest?.rsvpStatus !== 'invited' ? guest?.rsvpStatus : 'invited';
guestData.dietaryNotes = escape(guest?.dietaryNotes);
guestData.songRequests = escape(guest?.songRequests);
guestData.plusOne =
(guestData?.hasPlusOne && guest?.plusOne && accepted) || false;
guestData.plusOneFirstName =
(guestData?.hasPlusOne && guest?.plusOneFirstName) || '';
guestData.plusOneLastName =
(guestData?.hasPlusOne && guest?.plusOneLastName) || '';
guestData.save();
}
await Group.findByIdAndUpdate(groupId, {
note: escape(note),
});
res.status(200).json(JSON.stringify({ message: 'SUCCESS' }));
}
} catch (error) {
const { response: fetchResponse } = error;
console.error('error', error);
res
.status(fetchResponse?.status || 500)
.json({ message: 'Unable to RSVP Your Group' });
}
break;
default:
res.status(400).json({ message: 'Unable to RSVP Your Group' });
break;
}
});

View file

@ -9,9 +9,10 @@ const { compare } = bcrypt;
export default withSession(async (req, res) => { export default withSession(async (req, res) => {
const { username, password, penguin } = await req.body; const { username, password, penguin } = await req.body;
// TODO: REMOVE THIS IF GOING TO PRODUCTION // TODO: REMOVE THIS IF GOING TO PRODUCTION
// In production just await connectDB() // In production just: const knex = await connectDb();
let knex;
if (process.env.SITE_ENV !== 'TEST_SITE') { if (process.env.SITE_ENV !== 'TEST_SITE') {
await connectDb(); knex = await connectDb();
} }
try { try {
@ -25,9 +26,13 @@ export default withSession(async (req, res) => {
await req.session.save(); await req.session.save();
res.json(user); res.json(user);
} else { } else {
const userData = await User.findOne({ username: escape(username) }); const userData = await knex('users')
.where({
username: escape(username),
})
.first();
const savedPassword = userData?.password || ''; const savedPassword = userData?.password || '';
isAuthorized = await compare(password, savedPassword); isAuthorized = await compare(escape(password), savedPassword);
if (isAuthorized) { if (isAuthorized) {
const user = { isLoggedIn: isAuthorized, id: userData._id }; const user = { isLoggedIn: isAuthorized, id: userData._id };
req.session.user = user; req.session.user = user;

72
pages/api/party.js Normal file
View file

@ -0,0 +1,72 @@
import escape from 'escape-html';
import withSession from '../../lib/session';
import Group from '../../models/Group';
import Guest from '../../models/Guest';
import connectDb from '../../utils/db';
export default withSession(async (req, res) => {
const {
query: { id },
method,
body,
session,
} = req;
const { user } = session;
if (!user?.isLoggedIn) {
res.status(401).end();
return;
}
// TODO: REMOVE THIS WHEN TAKING YOUR SITE TO PRODUCTION
// In production just: const knex = await connectDb();
let knex;
if (process.env.SITE_ENV !== 'TEST_SITE') {
knex = await connectDb();
}
switch (method) {
case 'POST':
try {
// TODO: REMOVE THIS WHEN TAKING YOUR SITE TO PRODUCTION
if (process.env.SITE_ENV === 'TEST_SITE') {
console.log('DONE!');
res.status(200).json(JSON.stringify({ message: 'SUCCESS' }));
} else {
const { groupId, guests, note } = body;
for (const guest of guests) {
const guestData = await knex('guests')
.where({ id: guest.id })
.first();
const accepted = guest?.rsvpStatus === 'accepted';
guestData.rsvp_status =
guest?.rsvpStatus !== 'invited' ? guest?.rsvpStatus : 'invited';
guestData.dietary_notes = escape(guest?.dietaryNotes);
guestData.song_requests = escape(guest?.songRequests);
guestData.plus_one =
(guestData?.has_plus_one && guest?.plusOne && accepted) || false;
guestData.plus_one_first_name =
(guestData?.has_plus_one && guest?.plusOneFirstName) || '';
guestData.plus_one_last_name =
(guestData?.has_plus_one && guest?.plusOneLastName) || '';
// console.log('guestData modified', guestData);
await knex('guests').where({ id: guest.id }).update(guestData);
}
const updateParty = { note: escape(note), rsvp_submitted: true };
await knex('party').where({ id: groupId }).update(updateParty);
res.status(200).json(JSON.stringify({ message: 'SUCCESS' }));
}
} catch (error) {
const { response: fetchResponse } = error;
console.error('error', error);
res
.status(fetchResponse?.status || 500)
.json({ message: 'Unable to RSVP Your Group' });
}
break;
default:
res.status(400).json({ message: 'Unable to RSVP Your Group' });
break;
}
});

View file

@ -21,16 +21,39 @@ export default withSession(async (req, res) => {
if (process.env.SITE_ENV === 'TEST_SITE') { if (process.env.SITE_ENV === 'TEST_SITE') {
res.status(200).json({ status: 'SUCCESS', groupId: 'TESTID_12345' }); res.status(200).json({ status: 'SUCCESS', groupId: 'TESTID_12345' });
} else { } else {
await connectDb(); const knex = await connectDb();
const { firstName, lastName } = await req.body; const { firstName, lastName } = await req.body;
try { try {
const result = await Guest.findOne({ const result = await knex('guests')
firstName: { $regex: new RegExp(escape(firstName.trim()), 'i') }, .where(function () {
lastName: { $regex: new RegExp(escape(lastName.trim()), 'i') }, this.where(
}); 'first_name',
// console.log(JSON.stringify(result)); 'like',
res.status(200).json({ status: 'SUCCESS', groupId: result.group }); `%${escape(firstName.trim())}%`
).where('last_name', 'like', `%${escape(lastName.trim())}%`);
})
.select(
'first_name',
'last_name',
'role',
'rsvp_status',
'dietary_notes',
'song_requests',
'has_plus_one',
'plus_one',
'plus_one_first_name',
'plus_one_last_name',
'party_id',
'search_count'
)
.first();
if (result) {
res.status(200).json({ status: 'SUCCESS', groupId: result.party_id });
} else {
res.status(400).json({ status: 'FAILURE' });
return;
}
} catch (error) { } catch (error) {
const { response: fetchResponse } = error; const { response: fetchResponse } = error;
res.status(fetchResponse?.status || 500).json({ status: 'FAILURE' }); res.status(fetchResponse?.status || 500).json({ status: 'FAILURE' });

View file

@ -1,6 +1,11 @@
MONGO_URL=
SECRET_COOKIE_PASSWORD={NEXT_IRON_SESSION_COOKIE_PASSWORD} SECRET_COOKIE_PASSWORD={NEXT_IRON_SESSION_COOKIE_PASSWORD}
ROOT_DOMAIN=localhost:3000 ROOT_DOMAIN=localhost:3000
NEXT_PUBLIC_CLOUD_NAME={CLOUDINARY_NAME} PUBLIC_CLOUD_NAME={CLOUDINARY_NAME}
NEXT_PUBLIC_FOLDER_NAME={CLOUDINARY_FOLDER_NAME} PUBLIC_FOLDER_NAME={CLOUDINARY_FOLDER_NAME}
SITE_ENV=TEST_SITE SITE_ENV=TEST_SITE
SUPABASE_USER=postgres
SUPABASE_PASSWORD=
SUPABASE_PORT=
SUPABASE_DB=postgres
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_URL=

38
utils/buildBase64Data.js Normal file
View file

@ -0,0 +1,38 @@
import { getPlaiceholder } from 'plaiceholder';
import { buildUrl } from 'cloudinary-build-url';
export default async function buildBase64Data(
imageName,
alt,
additionalProps = {}
) {
const folderName = process.env.PUBLIC_FOLDER_NAME;
const cloudName = process.env.PUBLIC_CLOUD_NAME;
let imagePath;
if (imageName && alt && additionalProps) {
imagePath = buildUrl(`${folderName}/${imageName}`, {
cloud: {
cloudName,
},
});
}
if (imagePath) {
try {
const { base64, img } = await getPlaiceholder(imagePath, { size: 10 });
return {
imageProps: {
...img,
blurDataURL: base64,
},
alt,
...additionalProps,
};
} catch (e) {
// Error getting plaiceholder
// throw new Error('Error creating plaiceholder base64 image');
}
}
return {};
}

View file

@ -1,14 +1,10 @@
import mongoose from 'mongoose'; import knex from 'knex';
import configs from '../knexfile';
const url = process.env.MONGO_URL;
async function connectDb() { async function connectDb() {
// check if we have a connection to the database or if it's currently // check if we have a connection to the database or if it's currently
// connecting or disconnecting (readyState 1, 2 and 3) // connecting or disconnecting (readyState 1, 2 and 3)
if (mongoose.connection.readyState >= 1) { return knex(configs);
return;
}
return mongoose.connect(url);
} }
export default connectDb; export default connectDb;

0
utils/imageData.js Normal file
View file