From c0b1e96b7d0e8c7cc21e266156b64db42a7025c8 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Tue, 16 May 2023 15:07:18 -0400 Subject: [PATCH 01/48] Refactored user model to separate snippets --- .gitignore | 5 +- package.json | 3 +- .../controllers/authenticationController.js | 12 + server/controllers/snippetsController.js | 291 +++++++++++++----- server/models/snippetModel.js | 12 + server/models/tagModel.js | 0 server/models/userModel.js | 29 +- server/routes/authenticationRouter.js | 10 + .../routes/{snippets.js => snippetsRouter.js} | 9 +- server/server.js | 22 +- 10 files changed, 283 insertions(+), 110 deletions(-) create mode 100644 server/controllers/authenticationController.js create mode 100644 server/models/snippetModel.js create mode 100644 server/models/tagModel.js create mode 100644 server/routes/authenticationRouter.js rename server/routes/{snippets.js => snippetsRouter.js} (70%) diff --git a/.gitignore b/.gitignore index d241c34..c3f62c9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ package-lock.json npm-debug.log* # output from webpack -dist/ \ No newline at end of file +dist/ + +#environment file +.env \ No newline at end of file diff --git a/package.json b/package.json index f13e347..a287341 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,13 @@ "bootstrap": "^5.2.3", "codemirror": "^6.0.1", "cors": "^2.8.5", + "dotenv": "^16.0.3", "express": "^4.18.2", "mongodb": "^5.5.0", "mongoose": "^7.1.1", "react": "^18.2.0", - "react-copy-to-clipboard": "^5.1.0", "react-bootstrap": "^2.7.4", + "react-copy-to-clipboard": "^5.1.0", "react-dnd": "^14.0.5", "react-dnd-html5-backend": "^14.1.0", "react-dom": "^18.2.0", diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js new file mode 100644 index 0000000..287ebaa --- /dev/null +++ b/server/controllers/authenticationController.js @@ -0,0 +1,12 @@ +const User = require('../models/userModel.js'); +const authenticationController = {}; + +//Authentication and user creation methods go here +authenticationController.createUser = (req, res, next) => { + const { uid, password } = req.query; + User + .create //Information goes here... + (); +}; + +module.exports = authenticationController; diff --git a/server/controllers/snippetsController.js b/server/controllers/snippetsController.js index 864cf42..d2aa9a2 100644 --- a/server/controllers/snippetsController.js +++ b/server/controllers/snippetsController.js @@ -1,13 +1,24 @@ const User = require('../models/userModel.js'); - +const Snippet = require('../models/snippetModel.js'); const snippetsController = {}; -snippetsController.getSnippets = (req, res, next) => { +const createError = (method, log, status, message = log) => { + return { + log: `Error occurred in snippetsController.${method}: ${log}`, + status, + message: { err: message } + }; +}; + +snippetsController.getSnippetsByUser = (req, res, next) => { + //const userId = req.body.userId; const userId = '645fee9104d1f0acef95a002'; User.findOne({ _id: userId }) + .populate('snippets') + .exec() .then((user) => { - res.locals.allSnippets = user; + res.locals.allSnippets = user.snippets; return next(); }) .catch((err) => { @@ -19,97 +30,227 @@ snippetsController.getSnippets = (req, res, next) => { snippetsController.createSnippet = (req, res, next) => { const { title, comments, storedCode, tags, language } = req.body; const snippet = { title, comments, storedCode, tags, language }; - const userId = '645fee9104d1f0acef95a002'; + Snippet.create(snippet) + .then((doc) => { + res.locals.newSnippet = doc; + return next(); + }) + .catch((err) => { + return next( + createError('createSnippet', `Error creating new snippet: ${err}`, 500) + ); + }); +}; +snippetsController.saveSnippetToUser = (req, res, next) => { + const userId = req.body.userId; User.findById(userId) .then((user) => { - // Increment the lastId and assign it to the new snippet - const newSnippetId = user.lastId + 1; - user.lastId = newSnippetId; - - // Create the new snippet object with the assigned ID - const newSnippet = { - id: newSnippetId, - ...snippet, - }; - - // Push the new snippet to the snippets array - user.snippets.push(newSnippet); - - const [tags, languages] = recalcTagsAndLang(user); - user.tags = tags; - user.languages = languages; - - // Save the updated user document - return user.save().then((updatedUser) => { - res.locals.createdSnippet = newSnippet; - next(); - }); + user.snippets.push(res.locals.newSnippet._id); + user + .save() + .then((r) => { + res.locals.updatedUserRecord = r; + return next(); + }) + .catch((err) => { + return next( + createError( + 'saveSnippetToUser', + `Encountered error when saving new snippet to user: ${err}`, + 500 + ) + ); + }); }) - .catch((error) => { - console.error('Creating a snippet has failed:', error); - next(error); + .catch((err) => { + return next( + createError( + 'saveSnippetToUser', + `Encountered error when retrieving user record: ${err}`, + 500 + ) + ); }); }; - + +// snippetsController.createSnippet = (req, res, next) => { +// const { title, comments, storedCode, tags, language } = req.body; +// const snippet = { title, comments, storedCode, tags, language }; +// const userId = '645fee9104d1f0acef95a002'; + +// User.findById(userId) +// .then((user) => { +// // Increment the lastId and assign it to the new snippet +// const newSnippetId = user.lastId + 1; +// user.lastId = newSnippetId; + +// // Create the new snippet object with the assigned ID +// const newSnippet = { +// id: newSnippetId, +// ...snippet +// }; + +// // Push the new snippet to the snippets array +// user.snippets.push(newSnippet); + +// const [tags, languages] = recalcTagsAndLang(user); +// user.tags = tags; +// user.languages = languages; + +// // Save the updated user document +// return user.save().then((updatedUser) => { +// res.locals.createdSnippet = newSnippet; +// next(); +// }); +// }) +// .catch((error) => { +// console.error('Creating a snippet has failed:', error); +// next(error); +// }); +// }; + snippetsController.updateSnippet = (req, res, next) => { - const { id, title, comments, storedCode, tags, language } = req.body; - const updatedSnippet = { id, title, comments, storedCode, tags, language }; - const userId = '645fee9104d1f0acef95a002'; + const { _id, title, comments, storedCode, tags, language } = req.body; + const updatedSnippet = { title, comments, storedCode, tags, language }; + + for (const key in updatedSnippet) { + if (!updatedSnippet[key]) { + delete updatedSnippet[key]; + } + } + + //Need to work out how best to update user tags, languages under this new approach - User.findOneAndUpdate( - { _id: userId, 'snippets.id': updatedSnippet.id }, - { - $set: { 'snippets.$': updatedSnippet }, - }, - { new: true } + Snippet.findByIdAndUpdate( + _id, + { ...updatedSnippet }, + { new: true, upsert: true } ) - .then((updatedUser) => { - const [tags, languages] = recalcTagsAndLang(updatedUser); - updatedUser.tags = tags; - updatedUser.languages = languages; - return updatedUser.save(); - }) - .then((savedUser) => { - res.locals.updatedSnippet = updatedSnippet; - next(); + .exec() + .then((result) => { + res.locals.updatedSnippet = result; + return next(); }) .catch((err) => { - console.log('Updating the snippet has failed:', err); - next(err); + return next( + createError( + '.updateSnippet', + `Encountered error while updating snippet: ${err}`, + 500 + ) + ); }); }; -snippetsController.deleteSnippet = (req, res, next) => { - const { id } = req.query; - const userId = '645fee9104d1f0acef95a002'; +// snippetsController.updateSnippet = (req, res, next) => { +// const { id, title, comments, storedCode, tags, language } = req.body; +// const updatedSnippet = { id, title, comments, storedCode, tags, language }; +// const userId = '645fee9104d1f0acef95a002'; - User.findOne({ _id: userId }) - .then((user) => { - const deletedSnippet = user.snippets.find((snippet) => { - return `${snippet.id}` === id; - }); - - // Remove the snippet from the user's snippets array - user.snippets = user.snippets.filter((snippet) => `${snippet.id}` !== id); - - //recalculate the tags and languages. - const [tags, languages] = recalcTagsAndLang(user); - user.tags = tags; - user.languages = languages; - - // Save the updated user document - return user.save().then(() => { - res.locals.deletedSnippet = deletedSnippet; - next(); - }); +// User.findOneAndUpdate( +// { _id: userId, 'snippets.id': updatedSnippet.id }, +// { +// $set: { 'snippets.$': updatedSnippet } +// }, +// { new: true } +// ) +// .then((updatedUser) => { +// const [tags, languages] = recalcTagsAndLang(updatedUser); +// updatedUser.tags = tags; +// updatedUser.languages = languages; +// return updatedUser.save(); +// }) +// .then((savedUser) => { +// res.locals.updatedSnippet = updatedSnippet; +// next(); +// }) +// .catch((err) => { +// console.log('Updating the snippet has failed:', err); +// next(err); +// }); +// }; + +snippetsController.deleteSnippet = (req, res, next) => { + const { uid, sid } = req.query; + Snippet.findByIdAndDelete(sid) + .exec() + .then((result) => { + res.locals.deletedSnippet = result; + }) + .then(() => { + User.findById(uid) + .exec() + .then((user) => { + user.snippets = user.snippets.filter((el) => el != uid); + user + .save() + .then((updatedUser) => { + res.locals.updatedUserRecord = updatedUser; + return next(); + }) + .catch((err) => { + return next( + createError( + 'deleteSnippet', + `Encountered error while saving updated user snippets list: ${err}`, + 500 + ) + ); + }); + }) + .catch((err) => { + return next( + createError( + 'deleteSnippet', + `Encountered error while retrieving user with provided ID: ${err}`, + 500 + ) + ); + }); }) - .catch((error) => { - console.error('Error deleting snippet:', error); - next(error); + .catch((err) => { + return next( + createError( + 'deleteSnippet', + `Encountered error while attempting to delete snippet with provided ID: ${err}`, + 500 + ) + ); }); }; + +// snippetsController.deleteSnippet = (req, res, next) => { +// const { id } = req.query; +// const userId = '645fee9104d1f0acef95a002'; + +// User.findOne({ _id: userId }) +// .then((user) => { +// const deletedSnippet = user.snippets.find((snippet) => { +// return `${snippet.id}` === id; +// }); + +// // Remove the snippet from the user's snippets array +// user.snippets = user.snippets.filter((snippet) => `${snippet.id}` !== id); + +// //recalculate the tags and languages. +// const [tags, languages] = recalcTagsAndLang(user); +// user.tags = tags; +// user.languages = languages; + +// // Save the updated user document +// return user.save().then(() => { +// res.locals.deletedSnippet = deletedSnippet; +// next(); +// }); +// }) +// .catch((error) => { +// console.error('Error deleting snippet:', error); +// next(error); +// }); +// }; // helper function to re-calculate taglist/language counts? +//Not using this at present... const recalcTagsAndLang = function (user) { const tagList = {}; const languageList = {}; diff --git a/server/models/snippetModel.js b/server/models/snippetModel.js new file mode 100644 index 0000000..a3a1a39 --- /dev/null +++ b/server/models/snippetModel.js @@ -0,0 +1,12 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const snippetSchema = new Schema({ + title: { type: String, required: true }, + comments: { type: String }, + storedCode: { type: String }, + tags: [String], + language: { type: String } +}); + +module.exports = mongoose.model('Snippet', snippetSchema); diff --git a/server/models/tagModel.js b/server/models/tagModel.js new file mode 100644 index 0000000..e69de29 diff --git a/server/models/userModel.js b/server/models/userModel.js index a29070c..4528435 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -4,32 +4,17 @@ const Schema = mongoose.Schema; const userSchema = new Schema({ username: { type: String, required: true }, password: { type: String, required: true }, - tags: { - type: Object, - default: {}, - }, - languages: { - type: Object, - default: {}, - }, - - lastId: { type: Number, default: 0 }, - + tags: [String], + languages: [String], snippets: { type: [ { - id: { type: Number, required: true }, - type: Object, - title: { type: String, required: true }, - comments: { type: String }, - storedCode: { type: String }, - - tags: [String], - language: { type: String }, - }, + type: Schema.Types.ObjectId, + ref: 'Snippet' + } ], - default: [], - }, + default: [] + } }); module.exports = mongoose.model('User', userSchema); diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js new file mode 100644 index 0000000..a604332 --- /dev/null +++ b/server/routes/authenticationRouter.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); + +const authenticationController = require('../controllers/authenticationController'); + +router.post('/', authenticationController.createUser, (req, res) => { + res.sendStatus(200); +}); + +module.exports = router; diff --git a/server/routes/snippets.js b/server/routes/snippetsRouter.js similarity index 70% rename from server/routes/snippets.js rename to server/routes/snippetsRouter.js index 4e1b5c2..3b5571c 100644 --- a/server/routes/snippets.js +++ b/server/routes/snippetsRouter.js @@ -4,12 +4,15 @@ const snippetsController = require('../controllers/snippetsController'); const router = express.Router(); -router.get('/', snippetsController.getSnippets, (req, res) => +router.get('/', snippetsController.getSnippetsByUser, (req, res) => res.status(200).json(res.locals.allSnippets) ); -router.post('/', snippetsController.createSnippet, (req, res) => - res.status(200).json(res.locals.createdSnippet) +router.post( + '/', + snippetsController.createSnippet, + snippetsController.saveSnippetToUser, + (req, res) => res.status(200).json(res.locals.newSnippet) ); router.put('/', snippetsController.updateSnippet, (req, res) => diff --git a/server/server.js b/server/server.js index 855cf32..528aa96 100644 --- a/server/server.js +++ b/server/server.js @@ -1,37 +1,43 @@ const path = require('path'); const express = require('express'); -const app = express(); const mongoose = require('mongoose'); const cors = require('cors'); +const snippetsRouter = require('./routes/snippetsRouter'); +const authenticationRouter = require('./routes/authenticationRouter'); -const port = process.env.PORT || 3000; +require('dotenv').config(); -const mongoURI = - 'mongodb+srv://paaoul:Melikeit1@scratchcluster.igf2bag.mongodb.net/'; +//Create express app and set constants +const app = express(); +const port = process.env.PORT || 3000; +//Get mongoURI and connect to DB +const mongoURI = process.env.MONGO_URI; mongoose.connect(mongoURI); -const snippetsRouter = require('./routes/snippets'); - +//Call default middleware app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); +//Point relevant requests to snippet and authentication routers app.use('/snippets', snippetsRouter); +app.use('/authentication', authenticationRouter); +//Handle requests to invalid endpoints and middleware errors app.use((req, res) => res.status(404).send('Invalid endpoint')); - app.use((err, req, res, next) => { const defaultErr = { log: 'Express error handler caught unknown middleware error', status: 500, - message: { err: 'An error occurred' }, + message: { err: 'An error occurred' } }; const errorObj = Object.assign({}, defaultErr, err); console.log(errorObj.log); return res.status(errorObj.status).json(errorObj.message); }); +//Get 'er goin' app.listen(port, () => { console.log(`Server listening on port ${port}...`); }); From fbbef714ba7b935eb822923c4f2b1fc0b6d8985c Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Tue, 16 May 2023 15:53:45 -0400 Subject: [PATCH 02/48] Updated routes after testing (all but DELETE working) --- .../src/components/AddSnippet/AddSnippet.jsx | 23 +-- .../SnippetDisplay/SnippetDisplay.jsx | 153 +++++++------- .../controllers/authenticationController.js | 49 ++++- server/controllers/snippetsController.js | 191 +++++++++--------- server/models/userModel.js | 4 +- server/routes/authenticationRouter.js | 5 +- 6 files changed, 244 insertions(+), 181 deletions(-) diff --git a/client/src/components/AddSnippet/AddSnippet.jsx b/client/src/components/AddSnippet/AddSnippet.jsx index efebf01..7171ba7 100644 --- a/client/src/components/AddSnippet/AddSnippet.jsx +++ b/client/src/components/AddSnippet/AddSnippet.jsx @@ -32,15 +32,15 @@ const AddSnippet = ({ closeModal }) => { fetch('/snippets', { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, body: JSON.stringify({ title: title, language: language, comments: comments, tags: tagList, - storedCode: storedCode, - }), + storedCode: storedCode + }) }) .then((data) => data.json()) .catch((err) => { @@ -48,7 +48,6 @@ const AddSnippet = ({ closeModal }) => { console.log('failed saving snippet'); }); - // setTitle(''); // setLanguage(''); // setComments(''); @@ -72,12 +71,13 @@ const AddSnippet = ({ closeModal }) => { centered > - Add a snippet + + Add a snippet +
- { setTitle(e.target.value); }} > - {error && Title is required!} + {error && Title is required!}

- {
- {openModal && } + {openModal && } @@ -154,7 +154,6 @@ const AddSnippet = ({ closeModal }) => { Save - diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index 2bba1ca..6d0b593 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -5,14 +5,20 @@ import CodeMirror from '@uiw/react-codemirror'; import styles from './SnippetDisplay.module.scss'; import { langs } from '@uiw/codemirror-extensions-langs'; import TagInput from '../../components/ui/TagInput/TagInput'; -import {Card, Button} from 'react-bootstrap'; +import { Card, Button } from 'react-bootstrap'; const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { // indSnippet = this.props // create delete method using fetch request let snippetTitle = selectedSnippet.title ? selectedSnippet.title : ''; - let snippetLanguage = selectedSnippet.language ? selectedSnippet.language : ''; - let snippetComments = selectedSnippet.comments ? selectedSnippet.comments : ''; - let snippetStoredCode = selectedSnippet.storedCode ? selectedSnippet.storedCode : ''; + let snippetLanguage = selectedSnippet.language + ? selectedSnippet.language + : ''; + let snippetComments = selectedSnippet.comments + ? selectedSnippet.comments + : ''; + let snippetStoredCode = selectedSnippet.storedCode + ? selectedSnippet.storedCode + : ''; let snippetTagList = selectedSnippet.tags ? selectedSnippet.tags : []; // create a state variable for each passed down state and the its setState function @@ -24,8 +30,8 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { const [editButtonState, setEditButtonState] = useState(false); const deleteSnippet = (id) => { - fetch (`http://localhost:3000/snippets?id=${id}`, { - method: 'DELETE', + fetch(`http://localhost:3000/snippets?id=${id}`, { + method: 'DELETE' }) .then((response) => { if (response.ok) { @@ -33,13 +39,13 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { } }) .catch((err) => { - return ({ + return { log: `SnippetDisiplay.deleteSnippet: Error: ${err}`, status: err.status || 500, message: 'There was an error deleting snippet.' - }) - }) - } + }; + }); + }; const editSnippet = (id) => { // const [oldState, setOldState] = React.useState([]); @@ -54,7 +60,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { }; // within fetch request (post) // body: JSON.stringify(created object) - fetch (`/snippets?id=${id}`, { + fetch(`/snippets?id=${id}`, { method: 'PUT', body: JSON.stringify(updatedSnippet) }) @@ -63,65 +69,64 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { getSnippet(); }) .catch((err) => { - return ({ + return { log: `SnippetDisplay.editSnippet: Error: ${err}`, status: err.status || 500, message: 'There was an error editing code snippet.' - }); + }; }); }; - // copy code state + // copy code state const [copied, setCopied] = useState(false); - const checkEdit = () => { if (editButtonState === true) { - return( + return (
-
- - Title: +
+ Title: { snippetTitle = e.target.value; }} - > - + > - Language: + Language: { snippetLanguage = e.target.value; }} - > - + > - Comments: + Comments: { snippetComments = e.target.value; }} - > - + > - snippetTagList = e} tags={snippetTagList} /> + (snippetTagList = e)} + tags={snippetTagList} + /> {/* {setTags}}> Title: {snippetTagList} */}
{\n console.log(\'Hello World!)\n}'} - onChange={(e) => snippetStoredCode = (e)} + onChange={(e) => (snippetStoredCode = e)} > { - -
); } @@ -148,60 +152,71 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { if (editButtonState === false) { return (
- -
-

Title: {snippetTitle}

-

Language: {snippetLanguage}

-

Comments: {snippetComments}

- +
+

+ {' '} + Title: {snippetTitle} +

+

+ {' '} + Language: {snippetLanguage} +

+

+ {' '} + Comments: {snippetComments} +

+ {/*
{renderTags()}
*/}
{\n console.log(\'Hello World!)\n}'} options={{ - readOnly: true, + readOnly: true }} - onChange={(e) => snippetStoredCode = (e)} + onChange={(e) => (snippetStoredCode = e)} > setCopied(true)} > - + -
); } }; - + return ( - <> - - {checkEdit()} + <> + + {checkEdit()} -
- - -
+
+ + +
); diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 287ebaa..4e200ab 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -1,12 +1,51 @@ const User = require('../models/userModel.js'); const authenticationController = {}; -//Authentication and user creation methods go here +//Error creator method specific to this controller +const createError = (method, log, status, message = log) => { + return { + log: `Error occurred in authenticationController.${method}: ${log}`, + status, + message: { err: message } + }; +}; + +//Authentication and user creation methods go here. +//Feel free to change these as you see fit -- I just have them in here for testing purposes. authenticationController.createUser = (req, res, next) => { - const { uid, password } = req.query; - User - .create //Information goes here... - (); + const { username, password } = req.body; + User.create( + //Information goes here... + { username, password } + ) + .then((user) => { + res.locals.newUserInfo = user; + return next(); + }) + .catch((err) => { + return next( + createError( + 'createUser', + `Error creating user with provided credentials: ${err}`, + 500 + ) + ); + }); +}; + +authenticationController.getUserData = (req, res, next) => { + const { _id } = req.query; + User.findById(_id) + .exec() + .then((user) => { + res.locals.userData = user; + return next(); + }) + .catch((err) => { + return next( + createError('getUserData', `Error retrieving user data: ${err}`, 500) + ); + }); }; module.exports = authenticationController; diff --git a/server/controllers/snippetsController.js b/server/controllers/snippetsController.js index d2aa9a2..52f3efd 100644 --- a/server/controllers/snippetsController.js +++ b/server/controllers/snippetsController.js @@ -2,6 +2,7 @@ const User = require('../models/userModel.js'); const Snippet = require('../models/snippetModel.js'); const snippetsController = {}; +//Error creator method specific to this controller const createError = (method, log, status, message = log) => { return { log: `Error occurred in snippetsController.${method}: ${log}`, @@ -10,11 +11,12 @@ const createError = (method, log, status, message = log) => { }; }; +//Retrieves all snippets associated with a user by looking up user (by ID) and referencing all snippets in the associated list snippetsController.getSnippetsByUser = (req, res, next) => { - //const userId = req.body.userId; - const userId = '645fee9104d1f0acef95a002'; + const { _id } = req.query; + //const userId = '645fee9104d1f0acef95a002'; - User.findOne({ _id: userId }) + User.findById(_id) .populate('snippets') .exec() .then((user) => { @@ -27,6 +29,7 @@ snippetsController.getSnippetsByUser = (req, res, next) => { }); }; +//Creates snippet under Snippets collection snippetsController.createSnippet = (req, res, next) => { const { title, comments, storedCode, tags, language } = req.body; const snippet = { title, comments, storedCode, tags, language }; @@ -42,6 +45,7 @@ snippetsController.createSnippet = (req, res, next) => { }); }; +//Associates snippet with a particular user snippetsController.saveSnippetToUser = (req, res, next) => { const userId = req.body.userId; User.findById(userId) @@ -74,42 +78,7 @@ snippetsController.saveSnippetToUser = (req, res, next) => { }); }; -// snippetsController.createSnippet = (req, res, next) => { -// const { title, comments, storedCode, tags, language } = req.body; -// const snippet = { title, comments, storedCode, tags, language }; -// const userId = '645fee9104d1f0acef95a002'; - -// User.findById(userId) -// .then((user) => { -// // Increment the lastId and assign it to the new snippet -// const newSnippetId = user.lastId + 1; -// user.lastId = newSnippetId; - -// // Create the new snippet object with the assigned ID -// const newSnippet = { -// id: newSnippetId, -// ...snippet -// }; - -// // Push the new snippet to the snippets array -// user.snippets.push(newSnippet); - -// const [tags, languages] = recalcTagsAndLang(user); -// user.tags = tags; -// user.languages = languages; - -// // Save the updated user document -// return user.save().then((updatedUser) => { -// res.locals.createdSnippet = newSnippet; -// next(); -// }); -// }) -// .catch((error) => { -// console.error('Creating a snippet has failed:', error); -// next(error); -// }); -// }; - +//Updates snippet with provided properties snippetsController.updateSnippet = (req, res, next) => { const { _id, title, comments, storedCode, tags, language } = req.body; const updatedSnippet = { title, comments, storedCode, tags, language }; @@ -143,34 +112,7 @@ snippetsController.updateSnippet = (req, res, next) => { }); }; -// snippetsController.updateSnippet = (req, res, next) => { -// const { id, title, comments, storedCode, tags, language } = req.body; -// const updatedSnippet = { id, title, comments, storedCode, tags, language }; -// const userId = '645fee9104d1f0acef95a002'; - -// User.findOneAndUpdate( -// { _id: userId, 'snippets.id': updatedSnippet.id }, -// { -// $set: { 'snippets.$': updatedSnippet } -// }, -// { new: true } -// ) -// .then((updatedUser) => { -// const [tags, languages] = recalcTagsAndLang(updatedUser); -// updatedUser.tags = tags; -// updatedUser.languages = languages; -// return updatedUser.save(); -// }) -// .then((savedUser) => { -// res.locals.updatedSnippet = updatedSnippet; -// next(); -// }) -// .catch((err) => { -// console.log('Updating the snippet has failed:', err); -// next(err); -// }); -// }; - +//Deletes snippet with provided ID and removes from users with associated ID snippetsController.deleteSnippet = (req, res, next) => { const { uid, sid } = req.query; Snippet.findByIdAndDelete(sid) @@ -220,6 +162,96 @@ snippetsController.deleteSnippet = (req, res, next) => { }); }; +//Not using this at present... +const recalcTagsAndLang = function (user) { + const tagList = {}; + const languageList = {}; + + for (const snippet of user.snippets) { + if (Array.isArray(snippet.tags)) { + for (const tag of snippet.tags) { + if (!tagList[tag]) { + tagList[tag] = []; + } + tagList[tag].push(snippet); + } + + if (!languageList[snippet.language]) { + languageList[snippet.language] = []; + } + languageList[snippet.language].push(snippet); + } + } + //return something here. + return [tagList, languageList]; +}; + +module.exports = snippetsController; + +// snippetsController.createSnippet = (req, res, next) => { +// const { title, comments, storedCode, tags, language } = req.body; +// const snippet = { title, comments, storedCode, tags, language }; +// const userId = '645fee9104d1f0acef95a002'; + +// User.findById(userId) +// .then((user) => { +// // Increment the lastId and assign it to the new snippet +// const newSnippetId = user.lastId + 1; +// user.lastId = newSnippetId; + +// // Create the new snippet object with the assigned ID +// const newSnippet = { +// id: newSnippetId, +// ...snippet +// }; + +// // Push the new snippet to the snippets array +// user.snippets.push(newSnippet); + +// const [tags, languages] = recalcTagsAndLang(user); +// user.tags = tags; +// user.languages = languages; + +// // Save the updated user document +// return user.save().then((updatedUser) => { +// res.locals.createdSnippet = newSnippet; +// next(); +// }); +// }) +// .catch((error) => { +// console.error('Creating a snippet has failed:', error); +// next(error); +// }); +// }; + +// snippetsController.updateSnippet = (req, res, next) => { +// const { id, title, comments, storedCode, tags, language } = req.body; +// const updatedSnippet = { id, title, comments, storedCode, tags, language }; +// const userId = '645fee9104d1f0acef95a002'; + +// User.findOneAndUpdate( +// { _id: userId, 'snippets.id': updatedSnippet.id }, +// { +// $set: { 'snippets.$': updatedSnippet } +// }, +// { new: true } +// ) +// .then((updatedUser) => { +// const [tags, languages] = recalcTagsAndLang(updatedUser); +// updatedUser.tags = tags; +// updatedUser.languages = languages; +// return updatedUser.save(); +// }) +// .then((savedUser) => { +// res.locals.updatedSnippet = updatedSnippet; +// next(); +// }) +// .catch((err) => { +// console.log('Updating the snippet has failed:', err); +// next(err); +// }); +// }; + // snippetsController.deleteSnippet = (req, res, next) => { // const { id } = req.query; // const userId = '645fee9104d1f0acef95a002'; @@ -250,28 +282,3 @@ snippetsController.deleteSnippet = (req, res, next) => { // }); // }; // helper function to re-calculate taglist/language counts? -//Not using this at present... -const recalcTagsAndLang = function (user) { - const tagList = {}; - const languageList = {}; - - for (const snippet of user.snippets) { - if (Array.isArray(snippet.tags)) { - for (const tag of snippet.tags) { - if (!tagList[tag]) { - tagList[tag] = []; - } - tagList[tag].push(snippet); - } - - if (!languageList[snippet.language]) { - languageList[snippet.language] = []; - } - languageList[snippet.language].push(snippet); - } - } - //return something here. - return [tagList, languageList]; -}; - -module.exports = snippetsController; diff --git a/server/models/userModel.js b/server/models/userModel.js index 4528435..68cbf2c 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -4,8 +4,8 @@ const Schema = mongoose.Schema; const userSchema = new Schema({ username: { type: String, required: true }, password: { type: String, required: true }, - tags: [String], - languages: [String], + tags: { type: [String], default: [] }, + languages: { type: [String], default: [] }, snippets: { type: [ { diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index a604332..fb92d4e 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -4,7 +4,10 @@ const router = express.Router(); const authenticationController = require('../controllers/authenticationController'); router.post('/', authenticationController.createUser, (req, res) => { - res.sendStatus(200); + res.status(200).json(res.locals.newUserInfo); }); +router.get('/', authenticationController.getUserData, (req, res) => { + res.status(200).json(res.locals.userData); +}); module.exports = router; From fca674edff7378c2a4e5f8efa30235934bfab4c1 Mon Sep 17 00:00:00 2001 From: LeRocque Date: Tue, 16 May 2023 16:15:11 -0400 Subject: [PATCH 03/48] pulling --- package.json | 4 +++- server/.env | 0 server/server.js | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 server/.env diff --git a/package.json b/package.json index f13e347..9a36020 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,14 @@ "bootstrap": "^5.2.3", "codemirror": "^6.0.1", "cors": "^2.8.5", + "dotenv": "^16.0.3", "express": "^4.18.2", + "express-openid-connect": "^2.16.0", "mongodb": "^5.5.0", "mongoose": "^7.1.1", "react": "^18.2.0", - "react-copy-to-clipboard": "^5.1.0", "react-bootstrap": "^2.7.4", + "react-copy-to-clipboard": "^5.1.0", "react-dnd": "^14.0.5", "react-dnd-html5-backend": "^14.1.0", "react-dom": "^18.2.0", diff --git a/server/.env b/server/.env new file mode 100644 index 0000000..e69de29 diff --git a/server/server.js b/server/server.js index 855cf32..eb9c3f1 100644 --- a/server/server.js +++ b/server/server.js @@ -3,6 +3,16 @@ const express = require('express'); const app = express(); const mongoose = require('mongoose'); const cors = require('cors'); +const { auth } = require('express-openid-connect'); + +const config = { + authRequired: false, + auth0Logout: true, + secret: 'a long, randomly-generated string stored in env', + baseURL: 'http://localhost:3000', + clientID: 'Adw5QsdNG4dW47WgU78YAHOMorrzLhmW', + issuerBaseURL: 'https://dev-y3r4evhiowfqu1xi.us.auth0.com' +}; const port = process.env.PORT || 3000; @@ -17,6 +27,12 @@ app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); +// auth router attaches /login, /logout, and /callback routes to the baseURL +app.use(auth(config)); + +app.get('/', (req, res) => { + res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out'); +}); app.use('/snippets', snippetsRouter); app.use((req, res) => res.status(404).send('Invalid endpoint')); From 7c7db68f9b9d89e76695008d310ef9f747c30ab5 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Tue, 16 May 2023 15:17:13 -0500 Subject: [PATCH 04/48] Added comments throughout front end code --- client/App.jsx | 6 +- .../src/components/AddSnippet/AddSnippet.jsx | 81 ++++----- .../src/components/AddSnippet/SaveModal.jsx | 10 +- .../SnippetDisplay/SnippetDisplay.jsx | 165 +++++++++--------- .../src/components/ui/TagInput/TagInput.jsx | 14 +- .../ui/TagInput/TagInput.module.scss | 0 .../MainContainer/MainContainer.jsx | 15 +- client/src/containers/Sidebar/Sidebar.jsx | 78 +++++---- .../SnippetsRadioList/SnippetsRadioList.jsx | 73 +++----- server/server.js | 1 - 10 files changed, 215 insertions(+), 228 deletions(-) delete mode 100644 client/src/components/ui/TagInput/TagInput.module.scss diff --git a/client/App.jsx b/client/App.jsx index c9b1e1c..1862db4 100644 --- a/client/App.jsx +++ b/client/App.jsx @@ -1,10 +1,12 @@ import React from 'react'; + +// importing child components import MainContainer from './src/containers/MainContainer/MainContainer.jsx'; const App = () => ( -
+
); -export default App; \ No newline at end of file +export default App; diff --git a/client/src/components/AddSnippet/AddSnippet.jsx b/client/src/components/AddSnippet/AddSnippet.jsx index efebf01..1b79d4f 100644 --- a/client/src/components/AddSnippet/AddSnippet.jsx +++ b/client/src/components/AddSnippet/AddSnippet.jsx @@ -1,13 +1,23 @@ +import React, { useState } from 'react'; + +// importing child components +import SaveModal from '../../components/AddSnippet/SaveModal.jsx'; +import TagInput from '../../components/ui/TagInput/TagInput'; + +// importing external functionality import CodeMirror from '@uiw/react-codemirror'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { languages } from '@codemirror/language-data'; import { langs } from '@uiw/codemirror-extensions-langs'; -import styles from './AddSnippet.module.scss'; -import React, { useState } from 'react'; -import SaveModal from '../../components/AddSnippet/SaveModal.jsx'; -import TagInput from '../../components/ui/TagInput/TagInput'; + +// importing utils import Modal from 'react-bootstrap/Modal'; import Button from 'react-bootstrap/Button'; + +// importing styles +import styles from './AddSnippet.module.scss'; + +// importing data import { LANGUAGES } from '../../data/data.js'; const AddSnippet = ({ closeModal }) => { @@ -47,12 +57,6 @@ const AddSnippet = ({ closeModal }) => { console.log(err); console.log('failed saving snippet'); }); - - - // setTitle(''); - // setLanguage(''); - // setComments(''); - // setStoredCode(''); } // wrapper function for setTags to send to TagInput @@ -61,40 +65,39 @@ const AddSnippet = ({ closeModal }) => { } return ( -
-
+
+
closeModal(false)} - size="xl" - aria-labelledby="contained-modal-title-vcenter" - centered - > + size='xl' + aria-labelledby='contained-modal-title-vcenter' + centered> - Add a snippet + + Add a snippet +
- { setTitle(e.target.value); - }} - > + }}> {error && Title is required!}

- { - setStoredCode(e.target.value); - }} - > */} + onChange={(e) => setStoredCode(e)}>
- {openModal && } - -
diff --git a/client/src/components/AddSnippet/SaveModal.jsx b/client/src/components/AddSnippet/SaveModal.jsx index d5c8e25..43a8625 100644 --- a/client/src/components/AddSnippet/SaveModal.jsx +++ b/client/src/components/AddSnippet/SaveModal.jsx @@ -1,13 +1,7 @@ -import React, { useState} from 'react'; -import Modal from 'react-bootstrap/Modal'; +import React from 'react'; function SaveModal() { - - return ( -
- You have successfully saved a snippet! -
- ) + return
You have successfully saved a snippet!
; } export default SaveModal; diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index 2bba1ca..548ff6f 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -1,30 +1,34 @@ import React, { useState } from 'react'; -// import Snippet from + +// importing child components +import TagInput from '../../components/ui/TagInput/TagInput'; + +// importing external functionality import { CopyToClipboard } from 'react-copy-to-clipboard'; import CodeMirror from '@uiw/react-codemirror'; import styles from './SnippetDisplay.module.scss'; import { langs } from '@uiw/codemirror-extensions-langs'; -import TagInput from '../../components/ui/TagInput/TagInput'; -import {Card, Button} from 'react-bootstrap'; + +// importing utils +import { Card, Button } from 'react-bootstrap'; + const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { - // indSnippet = this.props - // create delete method using fetch request let snippetTitle = selectedSnippet.title ? selectedSnippet.title : ''; - let snippetLanguage = selectedSnippet.language ? selectedSnippet.language : ''; - let snippetComments = selectedSnippet.comments ? selectedSnippet.comments : ''; - let snippetStoredCode = selectedSnippet.storedCode ? selectedSnippet.storedCode : ''; + let snippetLanguage = selectedSnippet.language + ? selectedSnippet.language + : ''; + let snippetComments = selectedSnippet.comments + ? selectedSnippet.comments + : ''; + let snippetStoredCode = selectedSnippet.storedCode + ? selectedSnippet.storedCode + : ''; let snippetTagList = selectedSnippet.tags ? selectedSnippet.tags : []; - // create a state variable for each passed down state and the its setState function - // const [title, setTitle] = useState(snippetTitle); - // const [language, setLanguage] = useState(snippetLanguage); - // const [comments, setComments] = useState(snippetComments); - // const [storedCode, setStoredCode] = useState(snippetStoredCode); - // const [tagList, setTags] = useState(snippetTagList); const [editButtonState, setEditButtonState] = useState(false); const deleteSnippet = (id) => { - fetch (`http://localhost:3000/snippets?id=${id}`, { + fetch(`/snippets?id=${id}`, { method: 'DELETE', }) .then((response) => { @@ -33,13 +37,13 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { } }) .catch((err) => { - return ({ + return { log: `SnippetDisiplay.deleteSnippet: Error: ${err}`, status: err.status || 500, - message: 'There was an error deleting snippet.' - }) - }) - } + message: 'There was an error deleting snippet.', + }; + }); + }; const editSnippet = (id) => { // const [oldState, setOldState] = React.useState([]); @@ -50,55 +54,49 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { comments: snippetComments, storedCode: snippetStoredCode, tags: snippetTagList, - language: snippetLanguage + language: snippetLanguage, }; // within fetch request (post) // body: JSON.stringify(created object) - fetch (`/snippets?id=${id}`, { + fetch(`/snippets?id=${id}`, { method: 'PUT', - body: JSON.stringify(updatedSnippet) + body: JSON.stringify(updatedSnippet), }) .then((response) => { response.json(); getSnippet(); }) .catch((err) => { - return ({ + return { log: `SnippetDisplay.editSnippet: Error: ${err}`, status: err.status || 500, - message: 'There was an error editing code snippet.' - }); + message: 'There was an error editing code snippet.', + }; }); }; - // copy code state + // copy code state const [copied, setCopied] = useState(false); - const checkEdit = () => { if (editButtonState === true) { - return( + return (
- Title: { snippetTitle = e.target.value; - }} - > - + }}> - Language: + Language: { snippetLanguage = e.target.value; - }} - > - + }}> Comments: { className='commentsEdit' onChange={(e) => { snippetComments = e.target.value; - }} - > - + }}> - snippetTagList = e} tags={snippetTagList} /> + (snippetTagList = e)} + tags={snippetTagList} + /> {/* {setTags}}> Title: {snippetTagList} */}
@@ -121,26 +121,22 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { value={snippetStoredCode} extensions={[langs.tsx()]} // placeholder={'const sayHi = () => {\n console.log(\'Hello World!)\n}'} - onChange={(e) => snippetStoredCode = (e)} - > + onChange={(e) => (snippetStoredCode = e)}> setCopied(true)} - > + onCopy={() => setCopied(true)}> - -
); } @@ -148,12 +144,20 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { if (editButtonState === false) { return (
- -
-

Title: {snippetTitle}

-

Language: {snippetLanguage}

-

Comments: {snippetComments}

- +
+

+ {' '} + Title: {snippetTitle} +

+

+ {' '} + Language: {snippetLanguage} +

+

+ {' '} + Comments: {snippetComments} +

+ {/*
{renderTags()}
*/}
@@ -167,43 +171,42 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { options={{ readOnly: true, }} - onChange={(e) => snippetStoredCode = (e)} - > + onChange={(e) => (snippetStoredCode = e)}> setCopied(true)} - > + onCopy={() => setCopied(true)}> -
); } }; - + return ( - <> - - {checkEdit()} - -
- - -
+ + + {checkEdit()} + +
+ + +
- +
); }; diff --git a/client/src/components/ui/TagInput/TagInput.jsx b/client/src/components/ui/TagInput/TagInput.jsx index 44f4a78..67a5a6a 100644 --- a/client/src/components/ui/TagInput/TagInput.jsx +++ b/client/src/components/ui/TagInput/TagInput.jsx @@ -1,9 +1,14 @@ -import React, { useEffect } from 'react'; -import { TAGS } from '../../../data/data'; -import styles from './TagInput.module.scss'; +import React, { useState, useEffect } from 'react'; + +// importing utils import { WithContext as ReactTags } from 'react-tag-input'; import PropTypes from 'prop-types'; +// importing data +import { TAGS } from '../../../data/data'; + +// importing styles + const suggestions = TAGS.map((tag) => { return { id: tag, @@ -19,8 +24,7 @@ const KeyCodes = { const delimiters = [KeyCodes.comma, KeyCodes.enter]; const TagInput = (props) => { - - const [tags, setTags] = React.useState([]); + const [tags, setTags] = useState([]); const handleDelete = (i) => { setTags(tags.filter((tag, index) => index !== i)); diff --git a/client/src/components/ui/TagInput/TagInput.module.scss b/client/src/components/ui/TagInput/TagInput.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 2954daf..73950ba 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -1,14 +1,17 @@ import React from 'react'; + +// importing child components import Sidebar from '../Sidebar/Sidebar.jsx'; -import styles from './MainContainer.module.scss'; +// importing styles +import styles from './MainContainer.module.scss'; -const MainContainer = () =>{ - return ( +const MainContainer = () => { + return (
- ); -} + ); +}; -export default MainContainer; \ No newline at end of file +export default MainContainer; diff --git a/client/src/containers/Sidebar/Sidebar.jsx b/client/src/containers/Sidebar/Sidebar.jsx index 7177f59..29125c7 100644 --- a/client/src/containers/Sidebar/Sidebar.jsx +++ b/client/src/containers/Sidebar/Sidebar.jsx @@ -1,15 +1,23 @@ import React, { useState, useEffect } from 'react'; + +// importing child components import SnippetDisplay from '../../components/SnippetDisplay/SnippetDisplay.jsx'; import AddSnippet from '../../components/AddSnippet/AddSnippet.jsx'; -import styles from './Sidebar.module.scss'; import SnippetsRadioList from './SnippetsRadioList/SnippetsRadioList.jsx'; + +// importing utils import { Card, Spinner } from 'react-bootstrap'; + +// importing styles +import styles from './Sidebar.module.scss'; + +// importing assets import arrow from '../../assets/arrow.png'; import img from '../../assets/star nose mole.jpeg'; const Sidebar = () => { const [snippets, setSnippets] = useState([]); - const [selectedSnippet, setSelectedSnippet] = useState({}); + const [selectedSnippet, setSelectedSnippet] = useState(); const [openModal, setOpenModal] = useState(false); const [collapse, setCollapse] = useState(false); const [loading, setLoading] = useState(true); @@ -17,49 +25,43 @@ const Sidebar = () => { // getSnippet func const getSnippet = () => { setLoading(true); - fetch('http://localhost:3000/snippets') + fetch('/snippets') .then((res) => res.json()) .then((res) => { - console.log('res', res); - // moved setSnippets to outside of for loop so we arent re-rendering each time a snippet is added to state const newSnippetArray = []; - for (const snippet of res.snippets) newSnippetArray.push(snippet); + + for (const snippet of res.snippets) { + newSnippetArray.push(snippet); + } setSnippets(newSnippetArray); setLoading(false); }) .catch((error) => console.log('Get request failed', error)); }; - - // renderTags function - const renderTabs = () => { - const tabs = []; - - for (let i = 0; i < snippets.length; i++) { - tabs.push(); - } - - return tabs; - }; + + useEffect(() => getSnippet(), []); // wrapper to send to our snippets radio list for updating selected snippet. probably not 100% needed, but want to be able to console log from Sidebar const setSelectedSnippetWrapper = (e) => { setSelectedSnippet(e); }; - // get data from backend at first page load - useEffect(() => getSnippet(), []); const toggleSidebar = () => { setCollapse(() => !collapse); }; return ( - <> + + {/*----- SIDE BAR -----*/}

Code Snippets

+ + {/* Changes the collapse state, which will render/unrender the sidebar*/} +
+ + {/* Renders the list of snippets fetched from DB */} + + {/* Animation while app is fetching data from DB */}
- {/* render our snippet list, pass down snippets and function to update selectedSnippet */} {loading && (
- +
)} { />
- -

Click me to add a new snippet!

+ +

+ Click me to add a new snippet! +

-
+ + {/*----- ADD SNIPPET MODAL -----*/} + {openModal && } + + {/*----- SNIPPET DISPLAY -----*/} +
- {snippets && ( + }`}> + {selectedSnippet && ( )}
- +
); }; diff --git a/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx b/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx index 5fae115..6a9a805 100644 --- a/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx +++ b/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx @@ -1,6 +1,10 @@ +import React, { Fragment } from 'react'; + +// importing utils import PropTypes from 'prop-types'; + +// importing styles import styles from './SnippetsRadioList.module.scss'; -import { Fragment } from 'react'; function SnippetsRadioList(props) { // call passed in function on changed button, should find a way to do this without having to iterate through snippet array. store the snippet on the input itself somehow? @@ -16,59 +20,40 @@ function SnippetsRadioList(props) { if (props.snippets) { props.snippets.forEach((el, i) => { - const currButton = - i === 0 ? ( - - - -
-
- ) : ( - - - -
-
- ); + const currButton = ( + + + +
+
+ ); toggleButtons.push(currButton); }); } return ( - <> +
onChangeValue(e)} - > + onChange={(e) => { + onChangeValue(e); + }}> {toggleButtons}
- +
); } diff --git a/server/server.js b/server/server.js index 855cf32..36651a4 100644 --- a/server/server.js +++ b/server/server.js @@ -1,4 +1,3 @@ -const path = require('path'); const express = require('express'); const app = express(); const mongoose = require('mongoose'); From ac9bbd75beda9b59c6457d15b305131d696ee4f6 Mon Sep 17 00:00:00 2001 From: LeRocque Date: Tue, 16 May 2023 17:33:39 -0400 Subject: [PATCH 05/48] Auth0Started --- server/.env | 0 server/routes/snippetsRouter.js | 5 +++++ server/server.js | 33 ++++++++++++++++++++++----------- 3 files changed, 27 insertions(+), 11 deletions(-) delete mode 100644 server/.env diff --git a/server/.env b/server/.env deleted file mode 100644 index e69de29..0000000 diff --git a/server/routes/snippetsRouter.js b/server/routes/snippetsRouter.js index 3b5571c..374d681 100644 --- a/server/routes/snippetsRouter.js +++ b/server/routes/snippetsRouter.js @@ -1,4 +1,9 @@ const express = require('express'); +const { requiresAuth } = require('express-openid-connect'); + +app.get('/profile', requiresAuth(), (req, res) => { + res.send(JSON.stringify(req.oidc.user)); +}); const snippetsController = require('../controllers/snippetsController'); diff --git a/server/server.js b/server/server.js index ce64763..8cf11ca 100644 --- a/server/server.js +++ b/server/server.js @@ -4,14 +4,6 @@ const mongoose = require('mongoose'); const cors = require('cors'); const { auth } = require('express-openid-connect'); -const config = { - authRequired: false, - auth0Logout: true, - secret: 'a long, randomly-generated string stored in env', - baseURL: 'http://localhost:3000', - clientID: 'Adw5QsdNG4dW47WgU78YAHOMorrzLhmW', - issuerBaseURL: 'https://dev-y3r4evhiowfqu1xi.us.auth0.com' -}; const snippetsRouter = require('./routes/snippetsRouter'); const authenticationRouter = require('./routes/authenticationRouter'); @@ -30,12 +22,31 @@ app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); + +// Auth0 config +const secret = process.env.SECRET; +const clientID = process.env.CLIENT_ID; +const issuerBaseURL = process.env.ISSUER_BASE_URL; + +const config = { + authRequired: false, + auth0Logout: true, + secret: secret, + baseURL: 'http://localhost:3000', + clientID: clientID, + issuerBaseURL: issuerBaseURL +}; + + // auth router attaches /login, /logout, and /callback routes to the baseURL app.use(auth(config)); -app.get('/', (req, res) => { - res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out'); -}); +// testing auth confid + +// app.get('/', (req, res) => { +// res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out'); +// }); + //Point relevant requests to snippet and authentication routers app.use('/snippets', snippetsRouter); app.use('/authentication', authenticationRouter); From e6c3a7f299abfde5980d18aa07be543f8e6706ac Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Tue, 16 May 2023 16:42:56 -0500 Subject: [PATCH 06/48] Installed supertest and added jest to our eslint --- .eslintrc.json | 5 +++-- package.json | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 48e35cb..8f2d3ac 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,8 @@ "env": { "node": true, "browser": true, - "es2021": true + "es2021": true, + "jest": true }, "plugins": ["import", "react", "jsx-a11y", "css-modules"], "extends": [ @@ -39,4 +40,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index a287341..29c43fe 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "nodemon": "^2.0.22", "sass-loader": "^13.2.2", "style-loader": "^3.3.2", + "supertest": "^6.3.3", "webpack": "^5.82.1", "webpack-cli": "^5.1.1", "webpack-dev-server": "^4.15.0" From 775f560714f1e7b9e08e9a8758a34158c4c600d9 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Tue, 16 May 2023 16:53:57 -0500 Subject: [PATCH 07/48] Tests for GET request to snippets endpoint --- __tests__/routes.js | 65 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 __tests__/routes.js diff --git a/__tests__/routes.js b/__tests__/routes.js new file mode 100644 index 0000000..5abc219 --- /dev/null +++ b/__tests__/routes.js @@ -0,0 +1,65 @@ +const request = require('supertest'); + +const server = 'http://localhost:3000'; + +describe('Snippets route', () => { + describe('GET', () => { + xit('responds with 200 status and json', () => { + return request(server) + .get('/snippets') + .expect(200) + .expect('Content-Type', 'application/json') + .end((err, res) => { + if (err) throw err; + }); + }); + xit('responds with data that has keys: title, comments, storedCode, language', () => { + return request(server) + .get('/snippets') + .expect((res) => { + if (!res.body.hasOwnProperty('title')) { + throw new Error("Expected 'title' key!"); + } + if (!res.body.hasOwnProperty('comments')) { + throw new Error("Expected 'comments' key!"); + } + if (!res.body.hasOwnProperty('language')) { + throw new Error("Expected 'language' key!"); + } + if (!res.body.hasOwnProperty('storedCode')) { + throw new Error("Expected 'storedCode' key!"); + } + }) + .end((err, res) => { + if (err) throw err; + }); + }); + xit('responds with data that has key, tags, and value of an array', () => { + return request(server) + .get('/snippets') + .expect((res) => { + if (!res.body.hasOwnProperty('tags')) { + throw new Error("Expected 'tags' key!"); + } + if (!Array.isArray(res.body.tags)) { + throw new Error("Expected 'tags' to be an array!"); + } + }) + .end((err, res) => { + if (err) throw err; + }); + }); + }); + describe('POST', () => {}); + describe('PUT', () => {}); + describe('DELETE', () => {}); +}); + +describe('Authentication route', () => { + describe('GET', () => {}); + describe('POST', () => {}); +}); + +describe('Invalid route', () => { + describe('GET', () => {}); +}); From 80c6a9579ee0fd96ec3275d6b811122439c70ae8 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Tue, 16 May 2023 18:53:36 -0400 Subject: [PATCH 08/48] Working on updating fetch() formatting to match route patterns --- .../src/components/AddSnippet/AddSnippet.jsx | 24 ++++++-- .../src/components/AddSnippet/SaveModal.jsx | 9 +-- .../SnippetDisplay/SnippetDisplay.jsx | 55 +++++++++--------- client/src/containers/Sidebar/Sidebar.jsx | 58 ++++++++++--------- .../SnippetsRadioList/SnippetsRadioList.jsx | 45 +++++++------- server/controllers/snippetsController.js | 11 ++-- 6 files changed, 106 insertions(+), 96 deletions(-) diff --git a/client/src/components/AddSnippet/AddSnippet.jsx b/client/src/components/AddSnippet/AddSnippet.jsx index 7171ba7..00a3210 100644 --- a/client/src/components/AddSnippet/AddSnippet.jsx +++ b/client/src/components/AddSnippet/AddSnippet.jsx @@ -1,4 +1,5 @@ import CodeMirror from '@uiw/react-codemirror'; +import PropTypes from 'prop-types'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { languages } from '@codemirror/language-data'; import { langs } from '@uiw/codemirror-extensions-langs'; @@ -78,8 +79,9 @@ const AddSnippet = ({ closeModal }) => {
- + { @@ -90,8 +92,9 @@ const AddSnippet = ({ closeModal }) => {

- + { @@ -116,8 +120,12 @@ const AddSnippet = ({ closeModal }) => {

- - + +
Enter code:
@@ -128,7 +136,7 @@ const AddSnippet = ({ closeModal }) => { // value={storedCode} extensions={[langs.tsx()]} placeholder={ - "const sayHi = () => {\n console.log('Hello World!)\n}" + "const sayHi = () => {\n console.log('Hello World!')\n}" } onChange={(e) => setStoredCode(e)} > @@ -160,4 +168,8 @@ const AddSnippet = ({ closeModal }) => { ); }; +AddSnippet.propTypes = { + closeModal: PropTypes.func +}; + export default AddSnippet; diff --git a/client/src/components/AddSnippet/SaveModal.jsx b/client/src/components/AddSnippet/SaveModal.jsx index d5c8e25..be1237a 100644 --- a/client/src/components/AddSnippet/SaveModal.jsx +++ b/client/src/components/AddSnippet/SaveModal.jsx @@ -1,13 +1,8 @@ -import React, { useState} from 'react'; +import React, { useState } from 'react'; import Modal from 'react-bootstrap/Modal'; function SaveModal() { - - return ( -
- You have successfully saved a snippet! -
- ) + return
You have successfully saved a snippet!
; } export default SaveModal; diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index 6d0b593..c5f1f01 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; // import Snippet from +import PropTypes from 'prop-types'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import CodeMirror from '@uiw/react-codemirror'; import styles from './SnippetDisplay.module.scss'; @@ -7,8 +8,15 @@ import { langs } from '@uiw/codemirror-extensions-langs'; import TagInput from '../../components/ui/TagInput/TagInput'; import { Card, Button } from 'react-bootstrap'; const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { + // copy code state + const [copied, setCopied] = useState(false); + const [editButtonState, setEditButtonState] = useState(false); + //TODO: Pull userId from global state + //FIXME: HARDCODING USER ID FOR NOW + const userId = '6463eb52ab99bf89a84a3ebd'; // indSnippet = this.props // create delete method using fetch request + let snippetTitle = selectedSnippet.title ? selectedSnippet.title : ''; let snippetLanguage = selectedSnippet.language ? selectedSnippet.language @@ -21,16 +29,8 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { : ''; let snippetTagList = selectedSnippet.tags ? selectedSnippet.tags : []; - // create a state variable for each passed down state and the its setState function - // const [title, setTitle] = useState(snippetTitle); - // const [language, setLanguage] = useState(snippetLanguage); - // const [comments, setComments] = useState(snippetComments); - // const [storedCode, setStoredCode] = useState(snippetStoredCode); - // const [tagList, setTags] = useState(snippetTagList); - const [editButtonState, setEditButtonState] = useState(false); - - const deleteSnippet = (id) => { - fetch(`http://localhost:3000/snippets?id=${id}`, { + const deleteSnippet = (snippetId, userId) => { + fetch('/snippets?' + new URLSearchParams({ snippetId, userId }), { method: 'DELETE' }) .then((response) => { @@ -41,43 +41,39 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { .catch((err) => { return { log: `SnippetDisiplay.deleteSnippet: Error: ${err}`, - status: err.status || 500, + status: err.status, message: 'There was an error deleting snippet.' }; }); }; const editSnippet = (id) => { - // const [oldState, setOldState] = React.useState([]); - // create an object (eventually will hold the updated state) const updatedSnippet = { - id: id, title: snippetTitle, comments: snippetComments, storedCode: snippetStoredCode, tags: snippetTagList, language: snippetLanguage }; - // within fetch request (post) - // body: JSON.stringify(created object) - fetch(`/snippets?id=${id}`, { + + fetch('/snippets?' + new URLSearchParams({ _id: id }), { method: 'PUT', body: JSON.stringify(updatedSnippet) }) .then((response) => { - response.json(); + //Are we using this response anywhere? IF not, delete this. + //response.json(); getSnippet(); }) .catch((err) => { + //What's happening here? Where is this being returned to? return { log: `SnippetDisplay.editSnippet: Error: ${err}`, - status: err.status || 500, + status: err.status, message: 'There was an error editing code snippet.' }; }); }; - // copy code state - const [copied, setCopied] = useState(false); const checkEdit = () => { if (editButtonState === true) { @@ -139,7 +135,8 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { ); - } + // for (let i = 0; i < snippets.length; i++) { + // tabs.push(); + // } - return tabs; - }; + // return tabs; + // }; // wrapper to send to our snippets radio list for updating selected snippet. probably not 100% needed, but want to be able to console log from Sidebar const setSelectedSnippetWrapper = (e) => { @@ -64,19 +65,19 @@ const Sidebar = () => { arrow - +
{/* render our snippet list, pass down snippets and function to update selectedSnippet */} {loading && ( -
+
)} @@ -86,17 +87,18 @@ const Sidebar = () => { />
- -

Click me to add a new snippet!

+ +

+ Click me to add a new snippet! +

- {openModal && }
{ - if (!props.onChange) return; - for (const el of props.snippets) { - if (el.id !== undefined && el.id.toString() === e.target.value) - props.onChange(el); - } + console.log(e); + if (!onChange) return; + onChange(buttonMapper[e.target.id]); }; - const toggleButtons = []; + const buttonMapper = {}; + const buttonList = []; - if (props.snippets) { - props.snippets.forEach((el, i) => { + if (snippets) { + snippets.forEach((el, i) => { const currButton = i === 0 ? ( - + @@ -32,17 +31,17 @@ function SnippetsRadioList(props) {
{el.title}
{el.language}
-

{el.storedCode}

+

{el.storedCode}


) : ( - +
-

{el.storedCode}

+

{el.storedCode}


); - - toggleButtons.push(currButton); + buttonMapper[`snippet-input-${i}`] = el; + buttonList.push(currButton); }); } @@ -66,7 +65,7 @@ function SnippetsRadioList(props) { className={`${styles.snippetGroup}`} onChange={(e) => onChangeValue(e)} > - {toggleButtons} + {buttonList}
); @@ -74,7 +73,7 @@ function SnippetsRadioList(props) { SnippetsRadioList.propTypes = { snippets: PropTypes.array, - onChange: PropTypes.func, + onChange: PropTypes.func }; export default SnippetsRadioList; diff --git a/server/controllers/snippetsController.js b/server/controllers/snippetsController.js index 52f3efd..8de73d7 100644 --- a/server/controllers/snippetsController.js +++ b/server/controllers/snippetsController.js @@ -80,7 +80,8 @@ snippetsController.saveSnippetToUser = (req, res, next) => { //Updates snippet with provided properties snippetsController.updateSnippet = (req, res, next) => { - const { _id, title, comments, storedCode, tags, language } = req.body; + const { _id } = req.query; + const { title, comments, storedCode, tags, language } = req.body; const updatedSnippet = { title, comments, storedCode, tags, language }; for (const key in updatedSnippet) { @@ -114,17 +115,17 @@ snippetsController.updateSnippet = (req, res, next) => { //Deletes snippet with provided ID and removes from users with associated ID snippetsController.deleteSnippet = (req, res, next) => { - const { uid, sid } = req.query; - Snippet.findByIdAndDelete(sid) + const { userId, snippetId } = req.query; + Snippet.findByIdAndDelete(snippetId) .exec() .then((result) => { res.locals.deletedSnippet = result; }) .then(() => { - User.findById(uid) + User.findById(userId) .exec() .then((user) => { - user.snippets = user.snippets.filter((el) => el != uid); + user.snippets = user.snippets.filter((el) => el != snippetId); user .save() .then((updatedUser) => { From d0f9df995e41ea0b351743bdb36205ad5d5dab0a Mon Sep 17 00:00:00 2001 From: Wan Wang Date: Tue, 16 May 2023 19:02:43 -0400 Subject: [PATCH 09/48] added login component and signup component --- client/src/components/userStart/Login.jsx | 31 ++++++++++ client/src/components/userStart/Signup.jsx | 22 +++++++ .../MainContainer/MainContainer.jsx | 60 ++++++++++++++++--- client/src/containers/Sidebar/Sidebar.jsx | 2 +- client/src/scss/_global.scss | 55 +++++++++++++++++ 5 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 client/src/components/userStart/Login.jsx create mode 100644 client/src/components/userStart/Signup.jsx diff --git a/client/src/components/userStart/Login.jsx b/client/src/components/userStart/Login.jsx new file mode 100644 index 0000000..9169c07 --- /dev/null +++ b/client/src/components/userStart/Login.jsx @@ -0,0 +1,31 @@ +import React, {useState} from 'react'; + + + +// eslint-disable-next-line react/prop-types +const Login = ({handleLogin, handleHaveAccount}) => { + + + + return ( +
+
+

USER LOGIN

+
+
+ + +
+
+ + +
+ +
+
+ +
+ ); +}; + +export default Login; diff --git a/client/src/components/userStart/Signup.jsx b/client/src/components/userStart/Signup.jsx new file mode 100644 index 0000000..c3be27e --- /dev/null +++ b/client/src/components/userStart/Signup.jsx @@ -0,0 +1,22 @@ +import React, {useState} from 'react'; + +// eslint-disable-next-line react/prop-types +const Signup = ({handleSigned}) => { + return ( +
+

Create Your Account Here

+
+ + +
+
+ + +
+ +
+ + ); +}; + +export default Signup; diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 2954daf..8623a85 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -1,14 +1,60 @@ -import React from 'react'; +import React, {useState} from 'react'; import Sidebar from '../Sidebar/Sidebar.jsx'; import styles from './MainContainer.module.scss'; - +import Login from '../../components/userStart/Login.jsx'; +import Signup from '../../components/userStart/Signup.jsx'; const MainContainer = () =>{ - return ( -
- -
+ + const [login, setLogin] = useState(false); + const [haveAccount,setHaveAccount] = useState(true); + + const handleLogin = (e) => { + e.preventDefault(); + const usernameInputValue = document.getElementById('username').value; + document.getElementById('username').value = ''; + const passwordInputValue = document.getElementById('password').value; + document.getElementById('password').value = ''; + + // fetch(URLtoGetUser, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ + // username: usernameInputValue, + // password: passwordInputValue, + // }), + // }) + // .then((result) => result.json()) + // .then((result) => { + // console.log('result is: ', result); + // }) + // .catch((err) => { + // console.log(err); + // }); + + setLogin(true); + }; + + const handleHaveAccount = () => setHaveAccount(false); + const handleSigned = () => setHaveAccount(true); + + + return login ? + ( +
+ +
+ ) : haveAccount ? ( +
+ +
+ ) : ( +
+ +
); -} +}; export default MainContainer; \ No newline at end of file diff --git a/client/src/containers/Sidebar/Sidebar.jsx b/client/src/containers/Sidebar/Sidebar.jsx index 7177f59..f545c1d 100644 --- a/client/src/containers/Sidebar/Sidebar.jsx +++ b/client/src/containers/Sidebar/Sidebar.jsx @@ -7,7 +7,7 @@ import { Card, Spinner } from 'react-bootstrap'; import arrow from '../../assets/arrow.png'; import img from '../../assets/star nose mole.jpeg'; -const Sidebar = () => { +const Sidebar = ({handleLogin}) => { const [snippets, setSnippets] = useState([]); const [selectedSnippet, setSelectedSnippet] = useState({}); const [openModal, setOpenModal] = useState(false); diff --git a/client/src/scss/_global.scss b/client/src/scss/_global.scss index 0e76d3f..82290be 100644 --- a/client/src/scss/_global.scss +++ b/client/src/scss/_global.scss @@ -12,4 +12,59 @@ body { .entireSnippetDisplay { z-index: 50; +} + +.login { + display: flex; + flex-direction: column; + min-height: 500px; + width: 60%; + margin: auto; + // margin-top: 300px; + justify-content: center; + align-items: center; +} + +.form { + display: flex; + flex-direction: column; + justify-content:space-between; + align-items: flex-end; + margin-top: 20px; +} + +#go { + margin-top: 20px; + width: 80px; + border: 1px solid green; + background-color: green; + border-radius: 10px; + color: white; +} +#submit { + margin-top: 20px; + width: 100px; + border: 1px solid green; + background-color: green; + border-radius: 10px; + color: white; +} + +#loginBox { + + height: 260px; + width: 500px; + border: 2px solid rgb(42, 41, 41); + display: flex; + justify-content: center; + align-items:center; + flex-direction: column; + border-radius: 12px; + border-style: double; +} + + +.signup { + width: 30%; + margin: auto; } \ No newline at end of file From 2df3f5bfa758ce4b0f4f0a45657b15c100a4a22a Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Tue, 16 May 2023 19:21:45 -0400 Subject: [PATCH 10/48] Removed lingering merge comments --- .../src/components/AddSnippet/AddSnippet.jsx | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/client/src/components/AddSnippet/AddSnippet.jsx b/client/src/components/AddSnippet/AddSnippet.jsx index 6270315..5869d85 100644 --- a/client/src/components/AddSnippet/AddSnippet.jsx +++ b/client/src/components/AddSnippet/AddSnippet.jsx @@ -58,14 +58,6 @@ const AddSnippet = ({ closeModal }) => { console.log(err); console.log('failed saving snippet'); }); -<<<<<<< HEAD -======= - - // setTitle(''); - // setLanguage(''); - // setComments(''); - // setStoredCode(''); ->>>>>>> dev } // wrapper function for setTags to send to TagInput @@ -74,21 +66,18 @@ const AddSnippet = ({ closeModal }) => { } return ( -
-
+
+
closeModal(false)} - size='xl' - aria-labelledby='contained-modal-title-vcenter' - centered> + size="xl" + aria-labelledby="contained-modal-title-vcenter" + centered + > -<<<<<<< HEAD - -======= ->>>>>>> dev Add a snippet @@ -102,14 +91,9 @@ const AddSnippet = ({ closeModal }) => { value={title} onChange={(e) => { setTitle(e.target.value); -<<<<<<< HEAD - }}> - {error && Title is required!} -======= }} > {error && Title is required!} ->>>>>>> dev

@@ -118,7 +102,8 @@ const AddSnippet = ({ closeModal }) => { id="newSnippetLanguage" className={styles.language} value={language} - onChange={(e) => setLanguage(e.target.value)}> + onChange={(e) => setLanguage(e.target.value)} + > {LANGUAGES.map((language) => (
- - - {openModal && } - From 3bcc6f4cef99d94cf5930da41514fa558b654d5b Mon Sep 17 00:00:00 2001 From: LeRocque Date: Tue, 16 May 2023 19:27:10 -0400 Subject: [PATCH 11/48] pulling --- server/routes/snippetsRouter.js | 6 +++--- server/server.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/routes/snippetsRouter.js b/server/routes/snippetsRouter.js index 374d681..8137b67 100644 --- a/server/routes/snippetsRouter.js +++ b/server/routes/snippetsRouter.js @@ -1,9 +1,9 @@ const express = require('express'); const { requiresAuth } = require('express-openid-connect'); -app.get('/profile', requiresAuth(), (req, res) => { - res.send(JSON.stringify(req.oidc.user)); -}); +// app.get('/profile', requiresAuth(), (req, res) => { +// res.send(JSON.stringify(req.oidc.user)); +// }); const snippetsController = require('../controllers/snippetsController'); diff --git a/server/server.js b/server/server.js index 8cf11ca..60cea49 100644 --- a/server/server.js +++ b/server/server.js @@ -43,9 +43,9 @@ app.use(auth(config)); // testing auth confid -// app.get('/', (req, res) => { -// res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out'); -// }); +app.get('/', (req, res) => { + res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out'); +}); //Point relevant requests to snippet and authentication routers app.use('/snippets', snippetsRouter); From e577a05d68357d3a26ba0e1b4c8b1e40290d0064 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Tue, 16 May 2023 18:31:15 -0500 Subject: [PATCH 12/48] The GET test keeps failing, so I changed the test. --- __tests__/routes.js | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/__tests__/routes.js b/__tests__/routes.js index 5abc219..e9d8fce 100644 --- a/__tests__/routes.js +++ b/__tests__/routes.js @@ -1,52 +1,51 @@ const request = require('supertest'); +const Users = require('../server/models/userModel'); const server = 'http://localhost:3000'; describe('Snippets route', () => { describe('GET', () => { - xit('responds with 200 status and json', () => { + //before all test: + //Create a new user in the db + //save _id in a variable + + //after all test: + //delete user + + it('responds with 200 status and json', () => { return request(server) - .get('/snippets') + .get('/snippets/?_id=6463eb52ab99bf89a84a3ebd') .expect(200) - .expect('Content-Type', 'application/json') - .end((err, res) => { - if (err) throw err; - }); + .expect('Content-Type', 'application/json; charset=utf-8'); }); - xit('responds with data that has keys: title, comments, storedCode, language', () => { + it('responds with data that has keys: title, comments, storedCode, language', () => { return request(server) - .get('/snippets') + .get('/snippets/?_id=6463eb52ab99bf89a84a3ebd') .expect((res) => { - if (!res.body.hasOwnProperty('title')) { + if (!res.body[0].hasOwnProperty('title')) { throw new Error("Expected 'title' key!"); } - if (!res.body.hasOwnProperty('comments')) { + if (!res.body[0].hasOwnProperty('comments')) { throw new Error("Expected 'comments' key!"); } - if (!res.body.hasOwnProperty('language')) { + if (!res.body[0].hasOwnProperty('language')) { throw new Error("Expected 'language' key!"); } - if (!res.body.hasOwnProperty('storedCode')) { + if (!res.body[0].hasOwnProperty('storedCode')) { throw new Error("Expected 'storedCode' key!"); } - }) - .end((err, res) => { - if (err) throw err; }); }); - xit('responds with data that has key, tags, and value of an array', () => { + it('responds with data that has key, tags, and value of an array', () => { return request(server) - .get('/snippets') + .get('/snippets/?_id=6463eb52ab99bf89a84a3ebd') .expect((res) => { - if (!res.body.hasOwnProperty('tags')) { + if (!res.body[0].hasOwnProperty('tags')) { throw new Error("Expected 'tags' key!"); } - if (!Array.isArray(res.body.tags)) { + if (!Array.isArray(res.body[0].tags)) { throw new Error("Expected 'tags' to be an array!"); } - }) - .end((err, res) => { - if (err) throw err; }); }); }); From 1fe3c92fcb7418ed6751405e5aca9501e5b1a0bf Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Tue, 16 May 2023 19:37:05 -0400 Subject: [PATCH 13/48] Fixing loading issues --- client/src/containers/Sidebar/Sidebar.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/containers/Sidebar/Sidebar.jsx b/client/src/containers/Sidebar/Sidebar.jsx index 25f454a..fa059dc 100644 --- a/client/src/containers/Sidebar/Sidebar.jsx +++ b/client/src/containers/Sidebar/Sidebar.jsx @@ -15,13 +15,16 @@ import styles from './Sidebar.module.scss'; import arrow from '../../assets/arrow.png'; import img from '../../assets/star nose mole.jpeg'; -const Sidebar = ({handleLogin}) => { +const Sidebar = ({ handleLogin }) => { const [snippets, setSnippets] = useState([]); - const [selectedSnippet, setSelectedSnippet] = useState(); + const [selectedSnippet, setSelectedSnippet] = useState({}); const [openModal, setOpenModal] = useState(false); const [collapse, setCollapse] = useState(false); const [loading, setLoading] = useState(true); + useEffect(() => { + getSnippet(); + }, []); //Get all snippets stored under user's account //TODO: Get user ID from global state to include in request //FIXME: HARD CODING ID FOR NOW From 20b5e9c326dcf9de697fcb8dd59ecc8d3b6dff2c Mon Sep 17 00:00:00 2001 From: Wan Wang Date: Tue, 16 May 2023 20:13:10 -0400 Subject: [PATCH 14/48] testing with anthentication url --- .../MainContainer/MainContainer.jsx | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index c65b2bb..025ca62 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -17,23 +17,23 @@ const MainContainer = () =>{ const passwordInputValue = document.getElementById('password').value; document.getElementById('password').value = ''; - // fetch(URLtoGetUser, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify({ - // username: usernameInputValue, - // password: passwordInputValue, - // }), - // }) - // .then((result) => result.json()) - // .then((result) => { - // console.log('result is: ', result); - // }) - // .catch((err) => { - // console.log(err); - // }); + fetch('/authentication', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: usernameInputValue, + password: passwordInputValue, + }), + }) + .then((result) => result.json()) + .then((result) => { + console.log('result is: ', result); + }) + .catch((err) => { + console.log(err); + }); setLogin(true); }; From ebdd48f7ccb47c53db0184aa715ddc8c84fd5fe5 Mon Sep 17 00:00:00 2001 From: LeRocque Date: Tue, 16 May 2023 20:52:51 -0400 Subject: [PATCH 15/48] auth started --- package.json | 5 +++ server/authConfig/config.js | 11 ++++++ server/authConfig/passport.js | 38 +++++++++++++++++++ .../controllers/authenticationController.js | 31 +++++++++++++++ server/routes/authenticationRouter.js | 6 +++ server/routes/snippetsRouter.js | 5 --- server/server.js | 30 ++------------- 7 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 server/authConfig/config.js create mode 100644 server/authConfig/passport.js diff --git a/package.json b/package.json index f923515..a1f3a13 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,19 @@ "dependencies": { "@uiw/codemirror-extensions-langs": "^4.19.16", "@uiw/react-codemirror": "^4.19.16", + "bcrypt": "^5.1.0", "bootstrap": "^5.2.3", "codemirror": "^6.0.1", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", "express-openid-connect": "^2.16.0", + "jsonwebtoken": "^9.0.0", "mongodb": "^5.5.0", "mongoose": "^7.1.1", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "react": "^18.2.0", "react-bootstrap": "^2.7.4", "react-copy-to-clipboard": "^5.1.0", diff --git a/server/authConfig/config.js b/server/authConfig/config.js new file mode 100644 index 0000000..a38c3bc --- /dev/null +++ b/server/authConfig/config.js @@ -0,0 +1,11 @@ +const bcrypt = require('bcrypt'); + +const generateJwtSecret = () => { + return crypto.randomBytes(32).toString('hex'); +}; + + +module.exports = { + jwtSecret: generateJwtSecret(), + jwtExpiration: '1d', // Token expiration time +}; diff --git a/server/authConfig/passport.js b/server/authConfig/passport.js new file mode 100644 index 0000000..b901877 --- /dev/null +++ b/server/authConfig/passport.js @@ -0,0 +1,38 @@ +const bcrypt = require('bcrypt'); +const User = require('../models/userModel.js'); +const LocalStrategy = require('passport-local').Strategy; + +module.exports = function (passport) { + passport.use( + 'signup', + new LocalStrategy( + { + usernameField: 'email', + passwordField: 'password', + passReqToCallback: true, + }, + async (req, email, password, done) => { + try { + const existingUser = await User.findOne({ email }); + + if (existingUser) { + return done(null, false, {message: 'Email already exists'}) + } + + // password encryption + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password,salt); + + const newUser = User.create({ + username: req.body.username, + password: hashedPassword, + }); + + return done(null, newUser); + } catch (err) { + return done(err); + } + } + ) + ); +}; \ No newline at end of file diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 4e200ab..dcf1dce 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -1,3 +1,4 @@ +const passport = require('../authConfig/passport.js'); const User = require('../models/userModel.js'); const authenticationController = {}; @@ -48,4 +49,34 @@ authenticationController.getUserData = (req, res, next) => { }); }; +authenticationController.signUp = async (req, res, next) => { + try { + await passport.authenticate('signup', {session: false}, (err, user) => { + if (err) { + throw err; + } + if (!user) { + return res.status(400).json({message: 'Signup failed, please input a valid username'}); + } + const jwt = require('jsonwebtoken'); + const { jwtSecret, jwtExpirtation } = require('../authConfig/config') ; + + const token = jwt.sign({ id: user._id}, jwtSecret, { + expiresIn: jwtExpirtation, + }); + + req.token = token; + req.user = user; + res.locals.user = user; + + return next(); + }) (req, res, next); + } catch (err) { + return next( + createError('signUp', `Error with signUp ${err}`, 500) + ); + } +}; + + module.exports = authenticationController; diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index fb92d4e..b614842 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -1,8 +1,14 @@ const express = require('express'); const router = express.Router(); +const passport = require('passport'); const authenticationController = require('../controllers/authenticationController'); +router.post('/signup', passport.authenticate('signup', {session:false}), authenticationController.signUp, (req, res) => { + console.log('At signup router'); + res.status(200).json(res.locals.user); +}); + router.post('/', authenticationController.createUser, (req, res) => { res.status(200).json(res.locals.newUserInfo); }); diff --git a/server/routes/snippetsRouter.js b/server/routes/snippetsRouter.js index 8137b67..3b5571c 100644 --- a/server/routes/snippetsRouter.js +++ b/server/routes/snippetsRouter.js @@ -1,9 +1,4 @@ const express = require('express'); -const { requiresAuth } = require('express-openid-connect'); - -// app.get('/profile', requiresAuth(), (req, res) => { -// res.send(JSON.stringify(req.oidc.user)); -// }); const snippetsController = require('../controllers/snippetsController'); diff --git a/server/server.js b/server/server.js index 4038ce5..847f4d3 100644 --- a/server/server.js +++ b/server/server.js @@ -1,12 +1,14 @@ const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); -const { auth } = require('express-openid-connect'); +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; const snippetsRouter = require('./routes/snippetsRouter'); const authenticationRouter = require('./routes/authenticationRouter'); require('dotenv').config(); +require('./authConfig/passport')(passport); //Create express app and set constants const app = express(); @@ -17,35 +19,11 @@ const mongoURI = process.env.MONGO_URI; mongoose.connect(mongoURI); //Call default middleware +app.use(passport.initialize()); app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); - -// Auth0 config -const secret = process.env.SECRET; -const clientID = process.env.CLIENT_ID; -const issuerBaseURL = process.env.ISSUER_BASE_URL; - -const config = { - authRequired: false, - auth0Logout: true, - secret: secret, - baseURL: 'http://localhost:3000', - clientID: clientID, - issuerBaseURL: issuerBaseURL -}; - - -// auth router attaches /login, /logout, and /callback routes to the baseURL -app.use(auth(config)); - -// testing auth confid - -app.get('/', (req, res) => { - res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out'); -}); - //Point relevant requests to snippet and authentication routers app.use('/snippets', snippetsRouter); app.use('/authentication', authenticationRouter); From 46fd371584073c5d64ce62657894d765bfb3972f Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Wed, 17 May 2023 08:56:56 -0500 Subject: [PATCH 16/48] Added lifecycle, setup methods to test suite --- __tests__/routes.js | 51 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/__tests__/routes.js b/__tests__/routes.js index e9d8fce..6ebb035 100644 --- a/__tests__/routes.js +++ b/__tests__/routes.js @@ -1,26 +1,55 @@ const request = require('supertest'); +const mongoose = require('mongoose'); const Users = require('../server/models/userModel'); +const Snippets = require('../server/models/snippetModel'); + +require('dotenv').config(); const server = 'http://localhost:3000'; +const mongoURI = process.env.MONGO_URI; describe('Snippets route', () => { + let user_id; + let snippet_id; + const username = '__DummyData__'; + const password = 'codesmith'; + describe('GET', () => { - //before all test: - //Create a new user in the db - //save _id in a variable + //before all GET test: + const snippet = { + title: 'FAKE DATA', + comments: 'FUN COMMENTS!', + storedCode: 'cry()', + tags: [1, 2, 3], + language: 'Klingon', + }; + beforeAll(async () => { + await mongoose.connect(mongoURI); + + const user = await Users.create({ username, password }); + user_id = user._id; - //after all test: - //delete user + const fakeSnippet = await Snippets.create(snippet); + snippet_id = fakeSnippet._id; + user.snippets.push(fakeSnippet._id); + return user.save(); + }); + + afterAll(async () => { + await Users.findByIdAndDelete(user_id); + await Snippets.findByIdAndDelete(snippet_id); - it('responds with 200 status and json', () => { + return await mongoose.connection.close(); + }); + xit('responds with 200 status and json', () => { return request(server) - .get('/snippets/?_id=6463eb52ab99bf89a84a3ebd') + .get(`/snippets/?_id=${user_id}`) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8'); }); - it('responds with data that has keys: title, comments, storedCode, language', () => { + xit('responds with data that has keys: title, comments, storedCode, language', () => { return request(server) - .get('/snippets/?_id=6463eb52ab99bf89a84a3ebd') + .get(`/snippets/?_id=${user_id}`) .expect((res) => { if (!res.body[0].hasOwnProperty('title')) { throw new Error("Expected 'title' key!"); @@ -36,9 +65,9 @@ describe('Snippets route', () => { } }); }); - it('responds with data that has key, tags, and value of an array', () => { + xit('responds with data that has key, tags, and value of an array', () => { return request(server) - .get('/snippets/?_id=6463eb52ab99bf89a84a3ebd') + .get(`/snippets/?_id=${user_id}`) .expect((res) => { if (!res.body[0].hasOwnProperty('tags')) { throw new Error("Expected 'tags' key!"); From afbe9c3dcff9adfdfae8be7770eec55d8d97ce05 Mon Sep 17 00:00:00 2001 From: LeRocque Date: Wed, 17 May 2023 10:43:54 -0400 Subject: [PATCH 17/48] signup refactored-ready --- server/authConfig/passport.js | 35 +------------- .../controllers/authenticationController.js | 47 ++++++++++++------- server/routes/authenticationRouter.js | 9 +--- 3 files changed, 33 insertions(+), 58 deletions(-) diff --git a/server/authConfig/passport.js b/server/authConfig/passport.js index b901877..3eb616c 100644 --- a/server/authConfig/passport.js +++ b/server/authConfig/passport.js @@ -2,37 +2,6 @@ const bcrypt = require('bcrypt'); const User = require('../models/userModel.js'); const LocalStrategy = require('passport-local').Strategy; -module.exports = function (passport) { - passport.use( - 'signup', - new LocalStrategy( - { - usernameField: 'email', - passwordField: 'password', - passReqToCallback: true, - }, - async (req, email, password, done) => { - try { - const existingUser = await User.findOne({ email }); - - if (existingUser) { - return done(null, false, {message: 'Email already exists'}) - } - - // password encryption - const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(password,salt); - - const newUser = User.create({ - username: req.body.username, - password: hashedPassword, - }); - - return done(null, newUser); - } catch (err) { - return done(err); - } - } - ) - ); +module.exports = function authenticate(passport) { + console.log('Passport Authenticate called'); }; \ No newline at end of file diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index dcf1dce..105dc83 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -1,7 +1,9 @@ const passport = require('../authConfig/passport.js'); const User = require('../models/userModel.js'); +const bcrypt = require('bcrypt'); const authenticationController = {}; + //Error creator method specific to this controller const createError = (method, log, status, message = log) => { return { @@ -13,25 +15,34 @@ const createError = (method, log, status, message = log) => { //Authentication and user creation methods go here. //Feel free to change these as you see fit -- I just have them in here for testing purposes. -authenticationController.createUser = (req, res, next) => { +authenticationController.signUp = async (req, res, next) => { const { username, password } = req.body; - User.create( - //Information goes here... - { username, password } - ) - .then((user) => { - res.locals.newUserInfo = user; - return next(); - }) - .catch((err) => { - return next( - createError( - 'createUser', - `Error creating user with provided credentials: ${err}`, - 500 - ) - ); + try { + const existingUser = await User.findOne({ username }); + + if (existingUser) { + return next({ + log: 'Error occured in authenticationController.signUp', + status: 400, + message: 'Username already exists, please select another' + }); + } + + // password encryption + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password,salt); + + const newUser = await User.create({ + username: req.body.username, + password: hashedPassword, }); + + console.log('User created, signup complete'); + res.locals.newUser = await newUser; + return next(); + } catch (err) { + return next('Im triggered'); + } }; authenticationController.getUserData = (req, res, next) => { @@ -49,7 +60,7 @@ authenticationController.getUserData = (req, res, next) => { }); }; -authenticationController.signUp = async (req, res, next) => { +authenticationController.Null = async (req, res, next) => { try { await passport.authenticate('signup', {session: false}, (err, user) => { if (err) { diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index b614842..e8213bb 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -4,13 +4,8 @@ const passport = require('passport'); const authenticationController = require('../controllers/authenticationController'); -router.post('/signup', passport.authenticate('signup', {session:false}), authenticationController.signUp, (req, res) => { - console.log('At signup router'); - res.status(200).json(res.locals.user); -}); - -router.post('/', authenticationController.createUser, (req, res) => { - res.status(200).json(res.locals.newUserInfo); +router.post('/signup', authenticationController.signUp, (req, res) => { + res.status(201).json(res.locals.newUser); }); router.get('/', authenticationController.getUserData, (req, res) => { From 5f011f0ff1004de76152bda2ad631a92b981bf26 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Wed, 17 May 2023 10:13:46 -0500 Subject: [PATCH 18/48] completed POST request for snippets endpoint --- __tests__/routes.js | 92 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/__tests__/routes.js b/__tests__/routes.js index 6ebb035..d95150e 100644 --- a/__tests__/routes.js +++ b/__tests__/routes.js @@ -9,25 +9,25 @@ const server = 'http://localhost:3000'; const mongoURI = process.env.MONGO_URI; describe('Snippets route', () => { - let user_id; + let user; let snippet_id; const username = '__DummyData__'; const password = 'codesmith'; - - describe('GET', () => { + const snippet = { + title: 'FAKE DATA', + comments: 'FUN COMMENTS!', + storedCode: 'cry()', + tags: ['1', '2', '3'], + language: 'Klingon', + }; + xdescribe('GET', () => { //before all GET test: - const snippet = { - title: 'FAKE DATA', - comments: 'FUN COMMENTS!', - storedCode: 'cry()', - tags: [1, 2, 3], - language: 'Klingon', - }; beforeAll(async () => { + console.log('Connecting to the database!'); await mongoose.connect(mongoURI); - const user = await Users.create({ username, password }); - user_id = user._id; + console.log('Creating dummy data!'); + user = await Users.create({ username, password }); const fakeSnippet = await Snippets.create(snippet); snippet_id = fakeSnippet._id; @@ -36,20 +36,22 @@ describe('Snippets route', () => { }); afterAll(async () => { - await Users.findByIdAndDelete(user_id); + console.log('Deleting dummy data!'); + await Users.findByIdAndDelete(user._id); await Snippets.findByIdAndDelete(snippet_id); + console.log('Disconnecting from the database!'); return await mongoose.connection.close(); }); - xit('responds with 200 status and json', () => { + it('responds with 200 status and json', () => { return request(server) - .get(`/snippets/?_id=${user_id}`) + .get(`/snippets/?_id=${user._id}`) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8'); }); - xit('responds with data that has keys: title, comments, storedCode, language', () => { + it('responds with data that has keys: title, comments, storedCode, language', () => { return request(server) - .get(`/snippets/?_id=${user_id}`) + .get(`/snippets/?_id=${user._id}`) .expect((res) => { if (!res.body[0].hasOwnProperty('title')) { throw new Error("Expected 'title' key!"); @@ -65,9 +67,9 @@ describe('Snippets route', () => { } }); }); - xit('responds with data that has key, tags, and value of an array', () => { + it('responds with data that has key, tags, and value of an array', () => { return request(server) - .get(`/snippets/?_id=${user_id}`) + .get(`/snippets/?_id=${user._id}`) .expect((res) => { if (!res.body[0].hasOwnProperty('tags')) { throw new Error("Expected 'tags' key!"); @@ -78,7 +80,57 @@ describe('Snippets route', () => { }); }); }); - describe('POST', () => {}); + describe('POST', () => { + beforeAll(async () => { + console.log('Connecting to the database!'); + await mongoose.connect(mongoURI); + + console.log('Creating dummy data!'); + user = await Users.create({ username, password }); + + return user.save(); + }); + afterAll(async () => { + console.log('Deleting dummy data!'); + + await Users.findByIdAndDelete(user._id); + + console.log('Disconnecting from the database!'); + return await mongoose.connection.close(); + }); + afterEach(async () => { + user = await Users.findById(user._id); + return await Snippets.findByIdAndDelete(user.snippets[0]); + }); + xit('responds with 200 status and json', () => { + return request(server) + .post('/snippets') + .send({ ...snippet, userId: user._id }) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8'); + }); + xit('responds with the newly created document', () => { + return request(server) + .post('/snippets') + .send({ ...snippet, userId: user._id }) + .expect((res) => { + expect(res.body.title).toBe(snippet.title); + expect(res.body.comments).toBe(snippet.comments); + expect(res.body.storedCode).toBe(snippet.storedCode); + expect(res.body.language).toBe(snippet.language); + expect(res.body.tags).toEqual(snippet.tags); + }); + }); + xit("pushes newly created document to user's snippets array", () => { + return request(server) + .post('/snippets') + .send({ ...snippet, userId: user._id }) + .expect(async (res) => { + user = await Users.findById(user._id); + expect(user.snippets.length).toEqual(1); + }); + }); + }); describe('PUT', () => {}); describe('DELETE', () => {}); }); From 3840695451c5150ce736351d4f44070cedbfbbc0 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Wed, 17 May 2023 10:54:48 -0500 Subject: [PATCH 19/48] Wrote tests for DELETE to snippets endpoint --- __tests__/routes.js | 50 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/__tests__/routes.js b/__tests__/routes.js index d95150e..d91d773 100644 --- a/__tests__/routes.js +++ b/__tests__/routes.js @@ -80,7 +80,7 @@ describe('Snippets route', () => { }); }); }); - describe('POST', () => { + xdescribe('POST', () => { beforeAll(async () => { console.log('Connecting to the database!'); await mongoose.connect(mongoURI); @@ -132,7 +132,53 @@ describe('Snippets route', () => { }); }); describe('PUT', () => {}); - describe('DELETE', () => {}); + xdescribe('DELETE', () => { + beforeAll(async () => { + console.log('Connecting to the database!'); + await mongoose.connect(mongoURI); + + console.log('Creating dummy data!'); + return (user = await Users.create({ username, password })); + }); + beforeEach(async () => { + const fakeSnippet = await Snippets.create(snippet); + snippet_id = fakeSnippet._id; + user.snippets.push(fakeSnippet._id); + return user.save(); + }); + afterAll(async () => { + console.log('Deleting dummy data!'); + await Users.findByIdAndDelete(user._id); + + console.log('Disconnecting from the database!'); + return await mongoose.connection.close(); + }); + xit('responds with 200 status and json', () => { + return request(server) + .delete(`/snippets/?userId=${user._id}&snippetId=${snippet_id}`) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8'); + }); + xit('responds with the deleted document', () => { + return request(server) + .delete(`/snippets/?userId=${user._id}&snippetId=${snippet_id}`) + .expect((res) => { + expect(res.body.title).toBe(snippet.title); + expect(res.body.comments).toBe(snippet.comments); + expect(res.body.storedCode).toBe(snippet.storedCode); + expect(res.body.language).toBe(snippet.language); + expect(res.body.tags).toEqual(snippet.tags); + }); + }); + xit("removes delete document from user's snippets array", () => { + return request(server) + .delete(`/snippets/?userId=${user._id}&snippetId=${snippet_id}`) + .expect(async () => { + user = await Users.findById(user._id); + expect(user.snippets.length).toEqual(0); + }); + }); + }); }); describe('Authentication route', () => { From a8cc9d031c5e684b9c31cab8c4a77d7ff708d57b Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Wed, 17 May 2023 12:16:42 -0400 Subject: [PATCH 20/48] Latest updates to React and associated routes --- .../src/components/AddSnippet/AddSnippet.jsx | 6 +- .../SnippetDisplay/SnippetDisplay.jsx | 360 +++++++++++------- .../SnippetDisplay/SnippetDisplay.module.scss | 29 +- client/src/containers/Sidebar/Sidebar.jsx | 8 +- .../SnippetsRadioList/SnippetsRadioList.jsx | 70 ++-- server/controllers/snippetsController.js | 89 +++-- server/routes/snippetsRouter.js | 14 +- 7 files changed, 344 insertions(+), 232 deletions(-) diff --git a/client/src/components/AddSnippet/AddSnippet.jsx b/client/src/components/AddSnippet/AddSnippet.jsx index 5869d85..b65d255 100644 --- a/client/src/components/AddSnippet/AddSnippet.jsx +++ b/client/src/components/AddSnippet/AddSnippet.jsx @@ -30,6 +30,10 @@ const AddSnippet = ({ closeModal }) => { const [error, setError] = useState(false); const [openModal, setOpenModal] = useState(false); + //TODO: Pull user info from global state + //FIXME: HARCODING USER INFO FOR NOW + const userId = '6463eb52ab99bf89a84a3ebd'; + function handleSubmit(e) { e.preventDefault(); if (title === '') { @@ -40,7 +44,7 @@ const AddSnippet = ({ closeModal }) => { setError(false); } - fetch('/snippets', { + fetch('/snippets?' + new URLSearchParams({ userId }), { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index b84942d..8ffa77a 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; // importing child components import TagInput from '../../components/ui/TagInput/TagInput'; @@ -14,26 +14,26 @@ import { langs } from '@uiw/codemirror-extensions-langs'; import { Card, Button } from 'react-bootstrap'; const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { - // copy code state + const defaultDisplayValues = { + title: '', + language: '', + comments: '', + storedCode: '', + tags: [] + }; + const [copied, setCopied] = useState(false); const [editButtonState, setEditButtonState] = useState(false); + const [currentDisplay, setCurrentDisplay] = useState(defaultDisplayValues); //TODO: Pull userId from global state //FIXME: HARDCODING USER ID FOR NOW const userId = '6463eb52ab99bf89a84a3ebd'; // indSnippet = this.props // create delete method using fetch request - let snippetTitle = selectedSnippet.title ? selectedSnippet.title : ''; - let snippetLanguage = selectedSnippet.language - ? selectedSnippet.language - : ''; - let snippetComments = selectedSnippet.comments - ? selectedSnippet.comments - : ''; - let snippetStoredCode = selectedSnippet.storedCode - ? selectedSnippet.storedCode - : ''; - let snippetTagList = selectedSnippet.tags ? selectedSnippet.tags : []; + useEffect(() => { + setCurrentDisplay(selectedSnippet); + }, [selectedSnippet, getSnippet]); const deleteSnippet = (snippetId, userId) => { fetch('/snippets?' + new URLSearchParams({ snippetId, userId }), { @@ -53,22 +53,18 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { }); }; - const editSnippet = (id) => { - const updatedSnippet = { - title: snippetTitle, - comments: snippetComments, - storedCode: snippetStoredCode, - tags: snippetTagList, - language: snippetLanguage - }; - - fetch('/snippets?' + new URLSearchParams({ _id: id }), { + const editSnippet = (snippetId) => { + fetch(`/snippets?${new URLSearchParams({ snippetId })}`, { method: 'PUT', - body: JSON.stringify(updatedSnippet) + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify(currentDisplay) }) .then((response) => { //Are we using this response anywhere? IF not, delete this. - //response.json(); + return response.json(); + }) + .then((data) => { + console.log(data); getSnippet(); }) .catch((err) => { @@ -81,125 +77,211 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { }); }; - const checkEdit = () => { - if (editButtonState === true) { - return ( -
-
- Title: - { - snippetTitle = e.target.value; - }} - > - - Language: - { - snippetLanguage = e.target.value; - }} - > - - Comments: - { - snippetComments = e.target.value; - }} - > - - (snippetTagList = e)} - tags={snippetTagList} - /> - {/* {setTags}}> Title: {snippetTagList} */} -
- - {\n console.log(\'Hello World!)\n}'} - onChange={(e) => (snippetStoredCode = e)} - > - setCopied(true)} - > - - - + const displayContent = ( +
+
+ Title: + { + if (editButtonState) { + setCurrentDisplay({ ...currentDisplay, title: e.target.value }); + } + }} + > - -
- ); - } - - if (editButtonState === false) { - return ( -
-
-

- {snippetTitle} - Title: {snippetTitle} -

-

- {snippetLanguage} - Language: {snippetLanguage} -

-

- {snippetComments} - Comments: {snippetComments} -

- - {/*
{renderTags()}
*/} -
- - {\n console.log(\'Hello World!)\n}'} - options={{ - readOnly: true - }} - onChange={(e) => (snippetStoredCode = e)} - > - setCopied(true)} - > - - - -
- ); - } - }; + Language: + { + if (editButtonState) { + setCurrentDisplay({ + ...currentDisplay, + language: e.target.value + }); + } + }} + > + + Comments: + { + if (editButtonState) { + setCurrentDisplay({ + ...currentDisplay, + comments: e.target.value + }); + } + }} + > + + { + if (editButtonState) { + setCurrentDisplay({ ...currentDisplay, tags: e }); + } + }} + tags={currentDisplay.tags} + /> + {/* {setTags}}> Title: {snippetTagList} */} +
+ + {\n console.log(\'Hello World!)\n}'} + onChange={(e) => { + setCurrentDisplay({ ...currentDisplay, storedCode: e }); + }} + > + setCopied(true)} + > + + + + + +
+ ); + // const checkEdit = () => { + // if (editButtonState === true) { + // return ( + //
+ //
+ // Title: + // { + // snippetTitle = e.target.value; + // }} + // > + + // Language: + // { + // snippetLanguage = e.target.value; + // }} + // > + + // Comments: + // { + // snippetComments = e.target.value; + // }} + // > + + // (snippetTagList = e)} + // tags={snippetTagList} + // /> + // {/* {setTags}}> Title: {snippetTagList} */} + //
+ + // {\n console.log(\'Hello World!)\n}'} + // onChange={(e) => (snippetStoredCode = e)} + // > + // setCopied(true)} + // > + // + // + // + + // + //
+ // ); + // } + + // if (editButtonState === false) { + // return ( + //
+ //
+ //

+ // {snippetTitle} + // Title: {snippetTitle} + //

+ //

+ // {snippetLanguage} + // Language: {snippetLanguage} + //

+ //

+ // {snippetComments} + // Comments: {snippetComments} + //

+ // + // {/*
{renderTags()}
*/} + //
+ + // {\n console.log(\'Hello World!)\n}'} + // options={{ + // readOnly: true + // }} + // onChange={(e) => (snippetStoredCode = e)} + // > + // setCopied(true)} + // > + // + // + // + //
+ // ); + // } + // }; return ( - {checkEdit()} + {displayContent}
diff --git a/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx b/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx index 4867018..d5b93f1 100644 --- a/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx +++ b/client/src/containers/Sidebar/SnippetsRadioList/SnippetsRadioList.jsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import React, { Fragment, useEffect } from 'react'; // importing utils import PropTypes from 'prop-types'; @@ -6,12 +6,11 @@ import PropTypes from 'prop-types'; // importing styles import styles from './SnippetsRadioList.module.scss'; -function SnippetsRadioList({ snippets, onChange }) { +function SnippetsRadioList({ snippets, setSelectedSnippet }) { // call passed in function on changed button, should find a way to do this without having to iterate through snippet array. store the snippet on the input itself somehow? const onChangeValue = (e) => { - console.log(e); - if (!onChange) return; - onChange(buttonMapper[e.target.id]); + if (!setSelectedSnippet) return; + setSelectedSnippet(buttonMapper[e.target.id]); }; const buttonMapper = {}; @@ -19,45 +18,26 @@ function SnippetsRadioList({ snippets, onChange }) { if (snippets) { snippets.forEach((el, i) => { - const currButton = - i === 0 ? ( - - - -
-
- ) : ( - - - -
-
- ); + const currButton = ( + + + +
+
+ ); + buttonMapper[`snippet-input-${i}`] = el; buttonList.push(currButton); }); @@ -77,7 +57,7 @@ function SnippetsRadioList({ snippets, onChange }) { SnippetsRadioList.propTypes = { snippets: PropTypes.array, - onChange: PropTypes.func + setSelectedSnippet: PropTypes.func }; export default SnippetsRadioList; diff --git a/server/controllers/snippetsController.js b/server/controllers/snippetsController.js index 8de73d7..23f9c98 100644 --- a/server/controllers/snippetsController.js +++ b/server/controllers/snippetsController.js @@ -13,10 +13,10 @@ const createError = (method, log, status, message = log) => { //Retrieves all snippets associated with a user by looking up user (by ID) and referencing all snippets in the associated list snippetsController.getSnippetsByUser = (req, res, next) => { - const { _id } = req.query; + const { userId } = req.query; //const userId = '645fee9104d1f0acef95a002'; - User.findById(_id) + User.findById(userId) .populate('snippets') .exec() .then((user) => { @@ -47,7 +47,7 @@ snippetsController.createSnippet = (req, res, next) => { //Associates snippet with a particular user snippetsController.saveSnippetToUser = (req, res, next) => { - const userId = req.body.userId; + const { userId } = req.query; User.findById(userId) .then((user) => { user.snippets.push(res.locals.newSnippet._id); @@ -80,7 +80,9 @@ snippetsController.saveSnippetToUser = (req, res, next) => { //Updates snippet with provided properties snippetsController.updateSnippet = (req, res, next) => { - const { _id } = req.query; + console.log(req.query); + console.log(req.body); + const { snippetId } = req.query; const { title, comments, storedCode, tags, language } = req.body; const updatedSnippet = { title, comments, storedCode, tags, language }; @@ -93,13 +95,20 @@ snippetsController.updateSnippet = (req, res, next) => { //Need to work out how best to update user tags, languages under this new approach Snippet.findByIdAndUpdate( - _id, + snippetId, { ...updatedSnippet }, - { new: true, upsert: true } + { new: false, upsert: true } ) .exec() .then((result) => { + //Compare tags, languages in original and update to enable user refresh res.locals.updatedSnippet = result; + let removedTags, addedTags, oldLang, newLang; + + if (result.tags !== tags || result.language !== language) { + res.locals.changeFlag = true; + } + return next(); }) .catch((err) => { @@ -163,28 +172,58 @@ snippetsController.deleteSnippet = (req, res, next) => { }); }; -//Not using this at present... -const recalcTagsAndLang = function (user) { - const tagList = {}; - const languageList = {}; +snippetsController.recalcTagsAndLang = (req, res, next) => { + if (!res.locals.changeFlag) { + return next(); + } + + const { userId } = req.query; + const tagList = new Set(); + const languageList = new Set(); + + User.findById(userId) + .populate('snippets') + .exec() + .then((user) => { + user.snippets.forEach((snippet) => { + snippet.tags.forEach((tag) => { + if (!tagList.has(tag)) { + tagList.add(tag); + } + }); - for (const snippet of user.snippets) { - if (Array.isArray(snippet.tags)) { - for (const tag of snippet.tags) { - if (!tagList[tag]) { - tagList[tag] = []; + if (!languageList.has(snippet.language)) { + languageList.add(snippet.language); } - tagList[tag].push(snippet); - } + }); - if (!languageList[snippet.language]) { - languageList[snippet.language] = []; - } - languageList[snippet.language].push(snippet); - } - } - //return something here. - return [tagList, languageList]; + user.tags = Array.from(tagList); + user.languages = Array.from(languageList); + user + .save() + .then((usr) => { + res.locals.updatedUserRecord = usr; + return next(); + }) + .catch((err) => { + return next( + createError( + '.recalcTagsAndLang', + `Error saving new tags, languages to user: ${err}`, + 500 + ) + ); + }); + }) + .catch((err) => { + return next( + createError( + '.recalcTagsAndLanguages', + `Error locating user for update: ${err}`, + 500 + ) + ); + }); }; module.exports = snippetsController; diff --git a/server/routes/snippetsRouter.js b/server/routes/snippetsRouter.js index 3b5571c..239e40f 100644 --- a/server/routes/snippetsRouter.js +++ b/server/routes/snippetsRouter.js @@ -15,12 +15,18 @@ router.post( (req, res) => res.status(200).json(res.locals.newSnippet) ); -router.put('/', snippetsController.updateSnippet, (req, res) => - res.status(200).json(res.locals.updatedSnippet) +router.put( + '/', + snippetsController.updateSnippet, + snippetsController.recalcTagsAndLang, + (req, res) => res.status(200).json(res.locals.updatedSnippet) ); -router.delete('/', snippetsController.deleteSnippet, (req, res) => - res.status(200).json(res.locals.deletedSnippet) +router.delete( + '/', + snippetsController.deleteSnippet, + snippetsController.recalcTagsAndLang, + (req, res) => res.status(200).json(res.locals.deletedSnippet) ); router.use((req, res) => res.status(404).send('Invalid endpoint')); From 7535ca40cf84a2cfb150311fb37d9daa8d504aba Mon Sep 17 00:00:00 2001 From: LeRocque Date: Wed, 17 May 2023 13:53:51 -0400 Subject: [PATCH 21/48] login-ready --- package.json | 1 - server/authConfig/config.js | 11 ------ server/authConfig/passport.js | 35 +++++++++++++++++-- .../controllers/authenticationController.js | 4 +-- server/models/userModel.js | 2 +- server/routes/authenticationRouter.js | 16 ++++++++- server/server.js | 3 +- 7 files changed, 50 insertions(+), 22 deletions(-) delete mode 100644 server/authConfig/config.js diff --git a/package.json b/package.json index a1f3a13..5383ef7 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "mongodb": "^5.5.0", "mongoose": "^7.1.1", "passport": "^0.6.0", - "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "react": "^18.2.0", "react-bootstrap": "^2.7.4", diff --git a/server/authConfig/config.js b/server/authConfig/config.js deleted file mode 100644 index a38c3bc..0000000 --- a/server/authConfig/config.js +++ /dev/null @@ -1,11 +0,0 @@ -const bcrypt = require('bcrypt'); - -const generateJwtSecret = () => { - return crypto.randomBytes(32).toString('hex'); -}; - - -module.exports = { - jwtSecret: generateJwtSecret(), - jwtExpiration: '1d', // Token expiration time -}; diff --git a/server/authConfig/passport.js b/server/authConfig/passport.js index 3eb616c..d92f109 100644 --- a/server/authConfig/passport.js +++ b/server/authConfig/passport.js @@ -1,7 +1,36 @@ const bcrypt = require('bcrypt'); +const passport = require('passport'); const User = require('../models/userModel.js'); const LocalStrategy = require('passport-local').Strategy; +const jwt = require('jsonwebtoken'); -module.exports = function authenticate(passport) { - console.log('Passport Authenticate called'); -}; \ No newline at end of file +passport.use(new LocalStrategy({ + usernameField: 'username', // field name for username in req body + passwordField: 'password', // field name for password in req body +}, async (username, password, done) => { + try { + const user = await User.findOne({ username }); + + if (!user) { + // User not found + return done(null, false, {message: 'Incorrect username or password'}); + } + + // unhash stored password with bcrypt and compare to input password + const passwordMatch = await bcrypt.compare(password, user.password); + + if (!passwordMatch) { + // incorrect password + return done(null, false, {message: 'Incorrect username or password'}); + } + + // Auth successful, return the authenticated user + return done(null, user); + } catch (err) { + // Error occured during the auth process + return done(`Error occured during the auth process ${err}`); + } +} +)); + +// module.exports = passport.use; \ No newline at end of file diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 105dc83..47481a0 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -36,12 +36,10 @@ authenticationController.signUp = async (req, res, next) => { username: req.body.username, password: hashedPassword, }); - - console.log('User created, signup complete'); res.locals.newUser = await newUser; return next(); } catch (err) { - return next('Im triggered'); + return next(err); } }; diff --git a/server/models/userModel.js b/server/models/userModel.js index 68cbf2c..912910d 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -2,7 +2,7 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const userSchema = new Schema({ - username: { type: String, required: true }, + username: { type: String, required: true, unique: true }, password: { type: String, required: true }, tags: { type: [String], default: [] }, languages: { type: [String], default: [] }, diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index e8213bb..6bd2b41 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -1,11 +1,25 @@ const express = require('express'); const router = express.Router(); const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; +const jwt = require('jsonwebtoken'); const authenticationController = require('../controllers/authenticationController'); +require('dotenv').config(); +const secret = process.env.JWT_SECRET; + router.post('/signup', authenticationController.signUp, (req, res) => { - res.status(201).json(res.locals.newUser); + return res + .status(201) + .json(res.locals.newUser); +}); + +router.post('/login', passport.authenticate('local', {session: false}), (req, res) => { + const token = jwt.sign({ userId: req.user.id}, secret, {expiresIn: '1d'}); + return res + .status(202) + .json({ token }); }); router.get('/', authenticationController.getUserData, (req, res) => { diff --git a/server/server.js b/server/server.js index 847f4d3..7fb2c23 100644 --- a/server/server.js +++ b/server/server.js @@ -2,13 +2,12 @@ const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const passport = require('passport'); -const LocalStrategy = require('passport-local').Strategy; const snippetsRouter = require('./routes/snippetsRouter'); const authenticationRouter = require('./routes/authenticationRouter'); + require('dotenv').config(); -require('./authConfig/passport')(passport); //Create express app and set constants const app = express(); From 8f28815be5ca3c2b395756b7864a0d85095f6648 Mon Sep 17 00:00:00 2001 From: Wan Wang Date: Wed, 17 May 2023 14:11:41 -0400 Subject: [PATCH 22/48] completed css styling for login, signup and main content, and built connection with authenrication process and database --- client/App.jsx | 1 + .../SnippetDisplay/SnippetDisplay.jsx | 4 +- .../src/components/ui/TagInput/TagInput.jsx | 2 +- client/src/components/userStart/Login.jsx | 10 +-- client/src/components/userStart/Signup.jsx | 10 +-- .../MainContainer/MainContainer.jsx | 77 +++++++++++++------ client/src/containers/Sidebar/Sidebar.jsx | 21 ++--- client/src/scss/_global.scss | 63 +++++++++++---- 8 files changed, 127 insertions(+), 61 deletions(-) diff --git a/client/App.jsx b/client/App.jsx index 1862db4..1c4a51f 100644 --- a/client/App.jsx +++ b/client/App.jsx @@ -5,6 +5,7 @@ import MainContainer from './src/containers/MainContainer/MainContainer.jsx'; const App = () => (
+

CODESNIPPET

); diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index b84942d..55a6438 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -84,7 +84,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { const checkEdit = () => { if (editButtonState === true) { return ( -
+
Title: { return ( - + {checkEdit()}
diff --git a/client/src/components/ui/TagInput/TagInput.jsx b/client/src/components/ui/TagInput/TagInput.jsx index 67a5a6a..8c40570 100644 --- a/client/src/components/ui/TagInput/TagInput.jsx +++ b/client/src/components/ui/TagInput/TagInput.jsx @@ -70,7 +70,7 @@ const TagInput = (props) => { }, [tags]); return ( - {

USER LOGIN

- +    
-
- +
+    
- +
- +
); }; diff --git a/client/src/components/userStart/Signup.jsx b/client/src/components/userStart/Signup.jsx index c3be27e..c34b310 100644 --- a/client/src/components/userStart/Signup.jsx +++ b/client/src/components/userStart/Signup.jsx @@ -4,14 +4,14 @@ import React, {useState} from 'react'; const Signup = ({handleSigned}) => { return (
-

Create Your Account Here

-
- +

Create Your Account

+
+
-
+
- +
diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 025ca62..e51985f 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -1,14 +1,12 @@ - -import React, {useState} from 'react'; +import React, { useState } from 'react'; import Sidebar from '../Sidebar/Sidebar.jsx'; import styles from './MainContainer.module.scss'; import Login from '../../components/userStart/Login.jsx'; import Signup from '../../components/userStart/Signup.jsx'; -const MainContainer = () =>{ - +const MainContainer = () => { const [login, setLogin] = useState(false); - const [haveAccount,setHaveAccount] = useState(true); + const [haveAccount, setHaveAccount] = useState(true); const handleLogin = (e) => { e.preventDefault(); @@ -17,7 +15,7 @@ const MainContainer = () =>{ const passwordInputValue = document.getElementById('password').value; document.getElementById('password').value = ''; - fetch('/authentication', { + fetch('/authentication/login', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -29,34 +27,63 @@ const MainContainer = () =>{ }) .then((result) => result.json()) .then((result) => { - console.log('result is: ', result); + console.log('result from login request: ', result); + setLogin(true); }) .catch((err) => { console.log(err); }); + //todo: remove this after testing setLogin(true); }; - + //functino to handle showing the signup page const handleHaveAccount = () => setHaveAccount(false); - const handleSigned = () => setHaveAccount(true); - - - return login ? - ( -
- -
- ) : haveAccount ? ( -
- -
- ) : ( -
- -
- ); + //function to handle sign-up if username was not already taken + const handleSigned = (e) => { + e.preventDefault(); + const nameValue = document.getElementById('user').value; + document.getElementById('user').value = ''; + const passwordValue = document.getElementById('psw').value; + document.getElementById('psw').value = ''; + + fetch('/authentication/signup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: nameValue, + password: passwordValue, + }), + }) + .then((result) => result.json()) + .then((result) => { + console.log('result from signup request: ', result); + setHaveAccount(true); + }) + .catch((err) => { + console.log(err); + }); + + //todo: remove this after testing + setHaveAccount(true); + }; + + return login ? ( +
+ +
+ ) : haveAccount ? ( +
+ +
+ ) : ( +
+ +
+ ); }; export default MainContainer; diff --git a/client/src/containers/Sidebar/Sidebar.jsx b/client/src/containers/Sidebar/Sidebar.jsx index fa059dc..4071ae7 100644 --- a/client/src/containers/Sidebar/Sidebar.jsx +++ b/client/src/containers/Sidebar/Sidebar.jsx @@ -67,32 +67,33 @@ const Sidebar = ({ handleLogin }) => { return ( {/*----- SIDE BAR -----*/} - + -

Code Snippets

- {/* Changes the collapse state, which will render/unrender the sidebar*/}
{/* Renders the list of snippets fetched from DB */} - + {/* Animation while app is fetching data from DB */}
{loading && ( -
+
)} @@ -112,7 +113,7 @@ const Sidebar = ({ handleLogin }) => { setOpenModal(true); }} > - img + img diff --git a/client/src/scss/_global.scss b/client/src/scss/_global.scss index 82290be..a0dde6a 100644 --- a/client/src/scss/_global.scss +++ b/client/src/scss/_global.scss @@ -1,17 +1,31 @@ body { - color: red; + background: url(https://wallpaperaccess.com/full/266479.png) no-repeat center + center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; } - // this might be a bug. react tag library wasnt going inline .ReactTags__selected { display: flex; } -.cm-editor {outline: none !important} +.cm-editor { + outline: none !important; +} + +#right { + background: radial-gradient(150px 40px at 195px bottom, #666, #222); + + color: rgb(186, 127, 241); +} -.entireSnippetDisplay { - z-index: 50; +#card { + background: radial-gradient(150px 40px at 195px bottom, #666, #222); + color: white; + margin-top: 100px; } .login { @@ -20,7 +34,7 @@ body { min-height: 500px; width: 60%; margin: auto; - // margin-top: 300px; + margin-top: 100px; justify-content: center; align-items: center; } @@ -28,16 +42,19 @@ body { .form { display: flex; flex-direction: column; - justify-content:space-between; + justify-content: space-around; align-items: flex-end; - margin-top: 20px; + margin-top: 10px; } +#secondDiv { + margin-top: 15px; +} #go { margin-top: 20px; width: 80px; - border: 1px solid green; - background-color: green; + border: 1px solid; + background-color: rgb(4, 99, 129); border-radius: 10px; color: white; } @@ -51,20 +68,40 @@ body { } #loginBox { - + background-color: rgb(18, 64, 84); + opacity: 0.9; + background: -webkit-linear-gradient(#eee, #333); height: 260px; + color: black; + font-size: 16px; + font-weight: bold; width: 500px; border: 2px solid rgb(42, 41, 41); display: flex; justify-content: center; - align-items:center; + align-items: center; flex-direction: column; border-radius: 12px; border-style: double; } +#bottomMessage { + font-size: 20px; + background: -webkit-linear-gradient(#eee, #333); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +#headerTitle { + font-size: 72px; + background: -webkit-linear-gradient(#eee, #333); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} .signup { width: 30%; + color: lightgrey; margin: auto; -} \ No newline at end of file + margin-top: 200px; +} From c2d15adcbf24aabaaad3917516b8b5f07c7b30fa Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Wed, 17 May 2023 13:26:56 -0500 Subject: [PATCH 23/48] Refactored Snippets test to be run consecutively Wrote tests for GET authentication endpoint --- __tests__/routes.js | 66 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/__tests__/routes.js b/__tests__/routes.js index d91d773..28a21d9 100644 --- a/__tests__/routes.js +++ b/__tests__/routes.js @@ -8,7 +8,7 @@ require('dotenv').config(); const server = 'http://localhost:3000'; const mongoURI = process.env.MONGO_URI; -describe('Snippets route', () => { +xdescribe('Snippets route', () => { let user; let snippet_id; const username = '__DummyData__'; @@ -20,7 +20,7 @@ describe('Snippets route', () => { tags: ['1', '2', '3'], language: 'Klingon', }; - xdescribe('GET', () => { + describe('GET', () => { //before all GET test: beforeAll(async () => { console.log('Connecting to the database!'); @@ -80,7 +80,7 @@ describe('Snippets route', () => { }); }); }); - xdescribe('POST', () => { + describe('POST', () => { beforeAll(async () => { console.log('Connecting to the database!'); await mongoose.connect(mongoURI); @@ -100,16 +100,18 @@ describe('Snippets route', () => { }); afterEach(async () => { user = await Users.findById(user._id); - return await Snippets.findByIdAndDelete(user.snippets[0]); + snippet_id = user.snippets.pop(); + await user.save(); + return await Snippets.findByIdAndDelete(snippet_id); }); - xit('responds with 200 status and json', () => { + it('responds with 200 status and json', () => { return request(server) .post('/snippets') .send({ ...snippet, userId: user._id }) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8'); }); - xit('responds with the newly created document', () => { + it('responds with the newly created document', () => { return request(server) .post('/snippets') .send({ ...snippet, userId: user._id }) @@ -121,7 +123,7 @@ describe('Snippets route', () => { expect(res.body.tags).toEqual(snippet.tags); }); }); - xit("pushes newly created document to user's snippets array", () => { + it("pushes newly created document to user's snippets array", () => { return request(server) .post('/snippets') .send({ ...snippet, userId: user._id }) @@ -132,7 +134,7 @@ describe('Snippets route', () => { }); }); describe('PUT', () => {}); - xdescribe('DELETE', () => { + describe('DELETE', () => { beforeAll(async () => { console.log('Connecting to the database!'); await mongoose.connect(mongoURI); @@ -153,13 +155,13 @@ describe('Snippets route', () => { console.log('Disconnecting from the database!'); return await mongoose.connection.close(); }); - xit('responds with 200 status and json', () => { + it('responds with 200 status and json', () => { return request(server) .delete(`/snippets/?userId=${user._id}&snippetId=${snippet_id}`) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8'); }); - xit('responds with the deleted document', () => { + it('responds with the deleted document', () => { return request(server) .delete(`/snippets/?userId=${user._id}&snippetId=${snippet_id}`) .expect((res) => { @@ -170,7 +172,7 @@ describe('Snippets route', () => { expect(res.body.tags).toEqual(snippet.tags); }); }); - xit("removes delete document from user's snippets array", () => { + it("removes delete document from user's snippets array", () => { return request(server) .delete(`/snippets/?userId=${user._id}&snippetId=${snippet_id}`) .expect(async () => { @@ -182,7 +184,47 @@ describe('Snippets route', () => { }); describe('Authentication route', () => { - describe('GET', () => {}); + let user; + const username = '__DummyData__'; + const password = 'codesmith'; + const languages = ['klingon']; + const tags = ['1', '2', '3']; + describe('GET', () => { + beforeAll(async () => { + console.log('Connecting to the database!'); + await mongoose.connect(mongoURI); + + console.log('Creating dummy data!'); + return (user = await Users.create({ + username, + password, + languages, + tags, + })); + }); + afterAll(async () => { + console.log('Deleting dummy data!'); + await Users.findByIdAndDelete(user._id); + + console.log('Disconnecting from database!'); + return await mongoose.connection.close(); + }); + xit('responds with 200 status and json', () => { + return request(server) + .get(`/authentication/?_id=${user._id}`) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8'); + }); + xit('responds with the correct user data', () => { + return request(server) + .get(`/authentication/?_id=${user._id}`) + .expect((res) => { + expect(res.body.username).toEqual(username); + expect(res.body.languages[0]).toBe(languages[0]); + expect(res.body.tags[0]).toBe(tags[0]); + }); + }); + }); describe('POST', () => {}); }); From aa6397759ea382ed2a545f9208b6f2ea0470464a Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Wed, 17 May 2023 14:39:53 -0400 Subject: [PATCH 24/48] Revised editing; working on tags display --- .../SnippetDisplay/SnippetDisplay.jsx | 4 +- .../src/components/ui/TagInput/TagInput.jsx | 28 +++--- server/controllers/snippetsController.js | 99 +------------------ server/routes/snippetsRouter.js | 12 ++- 4 files changed, 28 insertions(+), 115 deletions(-) diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index 8ffa77a..6ba09ff 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -54,7 +54,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { }; const editSnippet = (snippetId) => { - fetch(`/snippets?${new URLSearchParams({ snippetId })}`, { + fetch(`/snippets?${new URLSearchParams({ snippetId, userId })}`, { method: 'PUT', headers: { 'Content-type': 'application/json' }, body: JSON.stringify(currentDisplay) @@ -126,7 +126,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { setCurrentDisplay({ ...currentDisplay, tags: e }); } }} - tags={currentDisplay.tags} + defaultTags={currentDisplay.tags} /> {/* {setTags}}> Title: {snippetTagList} */}
diff --git a/client/src/components/ui/TagInput/TagInput.jsx b/client/src/components/ui/TagInput/TagInput.jsx index 67a5a6a..ecdfb9b 100644 --- a/client/src/components/ui/TagInput/TagInput.jsx +++ b/client/src/components/ui/TagInput/TagInput.jsx @@ -12,32 +12,24 @@ import { TAGS } from '../../../data/data'; const suggestions = TAGS.map((tag) => { return { id: tag, - text: tag, + text: tag }; }); const KeyCodes = { comma: 188, - enter: 13, + enter: 13 }; const delimiters = [KeyCodes.comma, KeyCodes.enter]; -const TagInput = (props) => { +const TagInput = ({ onChange, defaultTags }) => { const [tags, setTags] = useState([]); const handleDelete = (i) => { setTags(tags.filter((tag, index) => index !== i)); }; - const initialTags = () => { - const newTagList = []; - if (props.tags) { - props.tags.forEach((tag) => newTagList.push({ id: tag, text: tag })); - setTags(newTagList); - } - }; - // useEffect(() => { // console.log('hello'); // initialTags(); @@ -61,11 +53,17 @@ const TagInput = (props) => { console.log('The tag at index ' + index + ' was clicked'); }; + useEffect(() => { + if (defaultTags) { + setTags(defaultTags); + } + }, []); + useEffect(() => { const tagStringList = []; - if (props.onChange) { - tags.forEach((tag) => tagStringList.push(tag.text)); - props.onChange(tagStringList); + if (onChange) { + tags.forEach((tag) => tagStringList.push(tag)); + onChange(tagStringList); } }, [tags]); @@ -86,7 +84,7 @@ const TagInput = (props) => { TagInput.propTypes = { onChange: PropTypes.func, - tags: PropTypes.array, + defaultTags: PropTypes.array }; export default TagInput; diff --git a/server/controllers/snippetsController.js b/server/controllers/snippetsController.js index 23f9c98..96bf898 100644 --- a/server/controllers/snippetsController.js +++ b/server/controllers/snippetsController.js @@ -138,6 +138,7 @@ snippetsController.deleteSnippet = (req, res, next) => { user .save() .then((updatedUser) => { + res.locals.changeFlag = true; res.locals.updatedUserRecord = updatedUser; return next(); }) @@ -180,11 +181,12 @@ snippetsController.recalcTagsAndLang = (req, res, next) => { const { userId } = req.query; const tagList = new Set(); const languageList = new Set(); - + console.log(userId); User.findById(userId) .populate('snippets') .exec() .then((user) => { + console.log(user); user.snippets.forEach((snippet) => { snippet.tags.forEach((tag) => { if (!tagList.has(tag)) { @@ -227,98 +229,3 @@ snippetsController.recalcTagsAndLang = (req, res, next) => { }; module.exports = snippetsController; - -// snippetsController.createSnippet = (req, res, next) => { -// const { title, comments, storedCode, tags, language } = req.body; -// const snippet = { title, comments, storedCode, tags, language }; -// const userId = '645fee9104d1f0acef95a002'; - -// User.findById(userId) -// .then((user) => { -// // Increment the lastId and assign it to the new snippet -// const newSnippetId = user.lastId + 1; -// user.lastId = newSnippetId; - -// // Create the new snippet object with the assigned ID -// const newSnippet = { -// id: newSnippetId, -// ...snippet -// }; - -// // Push the new snippet to the snippets array -// user.snippets.push(newSnippet); - -// const [tags, languages] = recalcTagsAndLang(user); -// user.tags = tags; -// user.languages = languages; - -// // Save the updated user document -// return user.save().then((updatedUser) => { -// res.locals.createdSnippet = newSnippet; -// next(); -// }); -// }) -// .catch((error) => { -// console.error('Creating a snippet has failed:', error); -// next(error); -// }); -// }; - -// snippetsController.updateSnippet = (req, res, next) => { -// const { id, title, comments, storedCode, tags, language } = req.body; -// const updatedSnippet = { id, title, comments, storedCode, tags, language }; -// const userId = '645fee9104d1f0acef95a002'; - -// User.findOneAndUpdate( -// { _id: userId, 'snippets.id': updatedSnippet.id }, -// { -// $set: { 'snippets.$': updatedSnippet } -// }, -// { new: true } -// ) -// .then((updatedUser) => { -// const [tags, languages] = recalcTagsAndLang(updatedUser); -// updatedUser.tags = tags; -// updatedUser.languages = languages; -// return updatedUser.save(); -// }) -// .then((savedUser) => { -// res.locals.updatedSnippet = updatedSnippet; -// next(); -// }) -// .catch((err) => { -// console.log('Updating the snippet has failed:', err); -// next(err); -// }); -// }; - -// snippetsController.deleteSnippet = (req, res, next) => { -// const { id } = req.query; -// const userId = '645fee9104d1f0acef95a002'; - -// User.findOne({ _id: userId }) -// .then((user) => { -// const deletedSnippet = user.snippets.find((snippet) => { -// return `${snippet.id}` === id; -// }); - -// // Remove the snippet from the user's snippets array -// user.snippets = user.snippets.filter((snippet) => `${snippet.id}` !== id); - -// //recalculate the tags and languages. -// const [tags, languages] = recalcTagsAndLang(user); -// user.tags = tags; -// user.languages = languages; - -// // Save the updated user document -// return user.save().then(() => { -// res.locals.deletedSnippet = deletedSnippet; -// next(); -// }); -// }) -// .catch((error) => { -// console.error('Error deleting snippet:', error); -// next(error); -// }); -// }; -// helper function to re-calculate taglist/language counts? diff --git a/server/routes/snippetsRouter.js b/server/routes/snippetsRouter.js index 239e40f..02ff000 100644 --- a/server/routes/snippetsRouter.js +++ b/server/routes/snippetsRouter.js @@ -19,14 +19,22 @@ router.put( '/', snippetsController.updateSnippet, snippetsController.recalcTagsAndLang, - (req, res) => res.status(200).json(res.locals.updatedSnippet) + (req, res) => + res.status(200).json({ + snippet: res.locals.updatedSnippet, + userData: res.locals.updatedUserRecord + }) ); router.delete( '/', snippetsController.deleteSnippet, snippetsController.recalcTagsAndLang, - (req, res) => res.status(200).json(res.locals.deletedSnippet) + (req, res) => + res.status(200).json({ + snippet: res.locals.deletedSnippet, + userData: res.locals.updatedUserRecord + }) ); router.use((req, res) => res.status(404).send('Invalid endpoint')); From b4756356698121ceababce2302b30b2a94c4be1a Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Wed, 17 May 2023 14:03:55 -0500 Subject: [PATCH 25/48] wrote test for error handling --- __tests__/routes.js | 53 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/__tests__/routes.js b/__tests__/routes.js index 28a21d9..89c00a2 100644 --- a/__tests__/routes.js +++ b/__tests__/routes.js @@ -183,7 +183,7 @@ xdescribe('Snippets route', () => { }); }); -describe('Authentication route', () => { +xdescribe('Authentication route', () => { let user; const username = '__DummyData__'; const password = 'codesmith'; @@ -209,13 +209,13 @@ describe('Authentication route', () => { console.log('Disconnecting from database!'); return await mongoose.connection.close(); }); - xit('responds with 200 status and json', () => { + it('responds with 200 status and json', () => { return request(server) .get(`/authentication/?_id=${user._id}`) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8'); }); - xit('responds with the correct user data', () => { + it('responds with the correct user data', () => { return request(server) .get(`/authentication/?_id=${user._id}`) .expect((res) => { @@ -225,9 +225,50 @@ describe('Authentication route', () => { }); }); }); - describe('POST', () => {}); + describe('POST', () => { + beforeAll(async () => { + console.log('Connecting to the database!'); + return await mongoose.connect(mongoURI); + }); + afterEach(async () => { + console.log('Deleting dummy data!'); + return await Users.findByIdAndDelete(user._id); + }); + afterAll(async () => { + console.log('Disconnecting from database!'); + return await mongoose.connection.close(); + }); + it('responds with 200 status and json', () => { + return request(server) + .post('/authentication') + .send({ username, password }) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect((res) => { + user = res.body; + }); + }); + it('responds with newly created user document', () => { + return request(server) + .post('/authentication') + .send({ username, password }) + .expect((res) => { + user = res.body; + expect(res.body.username).toEqual(username); + }); + }); + }); }); -describe('Invalid route', () => { - describe('GET', () => {}); +xdescribe('Error handling', () => { + describe('Invalid route', () => { + it('it returns a 404 status and error message', () => { + return request(server) + .get('/lorem_ipsum') + .expect(404) + .expect((res) => { + expect(res.text).toEqual('Invalid endpoint'); + }); + }); + }); }); From a7574ee46e5f89f10e0cc9f187271ad9a8fbb0ee Mon Sep 17 00:00:00 2001 From: LeRocque Date: Wed, 17 May 2023 15:05:08 -0400 Subject: [PATCH 26/48] pulling dev --- .../controllers/authenticationController.js | 29 ------------------- server/routes/authenticationRouter.js | 4 +++ 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 47481a0..61e77fa 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -58,34 +58,5 @@ authenticationController.getUserData = (req, res, next) => { }); }; -authenticationController.Null = async (req, res, next) => { - try { - await passport.authenticate('signup', {session: false}, (err, user) => { - if (err) { - throw err; - } - if (!user) { - return res.status(400).json({message: 'Signup failed, please input a valid username'}); - } - const jwt = require('jsonwebtoken'); - const { jwtSecret, jwtExpirtation } = require('../authConfig/config') ; - - const token = jwt.sign({ id: user._id}, jwtSecret, { - expiresIn: jwtExpirtation, - }); - - req.token = token; - req.user = user; - res.locals.user = user; - - return next(); - }) (req, res, next); - } catch (err) { - return next( - createError('signUp', `Error with signUp ${err}`, 500) - ); - } -}; - module.exports = authenticationController; diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index 6bd2b41..3972186 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -22,6 +22,10 @@ router.post('/login', passport.authenticate('local', {session: false}), (req, re .json({ token }); }); +router.get('/protected', passport.authenticate('jwt', {session: false }), (req, res) => { + res.send('Protected route accessed!'); +}); + router.get('/', authenticationController.getUserData, (req, res) => { res.status(200).json(res.locals.userData); }); From 9e3caa624dd8a48c10525a4c5c9fb67e0933b8a3 Mon Sep 17 00:00:00 2001 From: Wan Wang Date: Wed, 17 May 2023 15:24:09 -0400 Subject: [PATCH 27/48] completed all functionality for front-end to interact with the back-end for authentication and connection with database --- .../MainContainer/MainContainer.jsx | 10 ++------ .../controllers/authenticationController.js | 24 +++++++++---------- server/routes/authenticationRouter.js | 20 +++++++++------- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index e51985f..f450085 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -15,7 +15,7 @@ const MainContainer = () => { const passwordInputValue = document.getElementById('password').value; document.getElementById('password').value = ''; - fetch('/authentication/login', { + fetch('http://localhost:3000/authentication/login', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -33,9 +33,6 @@ const MainContainer = () => { .catch((err) => { console.log(err); }); - - //todo: remove this after testing - setLogin(true); }; //functino to handle showing the signup page const handleHaveAccount = () => setHaveAccount(false); @@ -48,7 +45,7 @@ const MainContainer = () => { const passwordValue = document.getElementById('psw').value; document.getElementById('psw').value = ''; - fetch('/authentication/signup', { + fetch('http://localhost:3000/authentication/signup', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -66,9 +63,6 @@ const MainContainer = () => { .catch((err) => { console.log(err); }); - - //todo: remove this after testing - setHaveAccount(true); }; return login ? ( diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 47481a0..82b10f7 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -3,13 +3,12 @@ const User = require('../models/userModel.js'); const bcrypt = require('bcrypt'); const authenticationController = {}; - //Error creator method specific to this controller const createError = (method, log, status, message = log) => { return { log: `Error occurred in authenticationController.${method}: ${log}`, status, - message: { err: message } + message: { err: message }, }; }; @@ -24,13 +23,13 @@ authenticationController.signUp = async (req, res, next) => { return next({ log: 'Error occured in authenticationController.signUp', status: 400, - message: 'Username already exists, please select another' + message: 'Username already exists, please select another', }); } // password encryption const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(password,salt); + const hashedPassword = await bcrypt.hash(password, salt); const newUser = await User.create({ username: req.body.username, @@ -60,17 +59,19 @@ authenticationController.getUserData = (req, res, next) => { authenticationController.Null = async (req, res, next) => { try { - await passport.authenticate('signup', {session: false}, (err, user) => { + await passport.authenticate('signup', { session: false }, (err, user) => { if (err) { throw err; } if (!user) { - return res.status(400).json({message: 'Signup failed, please input a valid username'}); + return res + .status(400) + .json({ message: 'Signup failed, please input a valid username' }); } const jwt = require('jsonwebtoken'); - const { jwtSecret, jwtExpirtation } = require('../authConfig/config') ; + const { jwtSecret, jwtExpirtation } = require('../authConfig/config'); - const token = jwt.sign({ id: user._id}, jwtSecret, { + const token = jwt.sign({ id: user._id }, jwtSecret, { expiresIn: jwtExpirtation, }); @@ -79,13 +80,10 @@ authenticationController.Null = async (req, res, next) => { res.locals.user = user; return next(); - }) (req, res, next); + })(req, res, next); } catch (err) { - return next( - createError('signUp', `Error with signUp ${err}`, 500) - ); + return next(createError('signUp', `Error with signUp ${err}`, 500)); } }; - module.exports = authenticationController; diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index 6bd2b41..160d349 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -10,17 +10,19 @@ require('dotenv').config(); const secret = process.env.JWT_SECRET; router.post('/signup', authenticationController.signUp, (req, res) => { - return res - .status(201) - .json(res.locals.newUser); + return res.status(201).json(res.locals.newUser); }); -router.post('/login', passport.authenticate('local', {session: false}), (req, res) => { - const token = jwt.sign({ userId: req.user.id}, secret, {expiresIn: '1d'}); - return res - .status(202) - .json({ token }); -}); +router.post( + '/login', + passport.authenticate('local', { session: false }), + (req, res) => { + const token = jwt.sign({ userId: req.user.id }, secret, { + expiresIn: '1d', + }); + return res.status(202).json({ token }); + } +); router.get('/', authenticationController.getUserData, (req, res) => { res.status(200).json(res.locals.userData); From b17f22df2cc880322a9a56c2360f4cd5005e9f91 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Wed, 17 May 2023 15:20:46 -0500 Subject: [PATCH 28/48] Finished backend testing suite --- __tests__/routes.js | 92 ++++++++++++++----- .../controllers/authenticationController.js | 2 +- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/__tests__/routes.js b/__tests__/routes.js index 89c00a2..368b611 100644 --- a/__tests__/routes.js +++ b/__tests__/routes.js @@ -8,7 +8,7 @@ require('dotenv').config(); const server = 'http://localhost:3000'; const mongoURI = process.env.MONGO_URI; -xdescribe('Snippets route', () => { +describe('Snippets route', () => { let user; let snippet_id; const username = '__DummyData__'; @@ -20,6 +20,13 @@ xdescribe('Snippets route', () => { tags: ['1', '2', '3'], language: 'Klingon', }; + const newSnippet = { + title: 'NEW FAKE DATA', + comments: 'MORE FUN COMMENTS!', + storedCode: 'cryAgain()', + tags: ['4', '5', '6'], + language: 'Huttese', + }; describe('GET', () => { //before all GET test: beforeAll(async () => { @@ -32,6 +39,7 @@ xdescribe('Snippets route', () => { const fakeSnippet = await Snippets.create(snippet); snippet_id = fakeSnippet._id; user.snippets.push(fakeSnippet._id); + return user.save(); }); @@ -45,13 +53,13 @@ xdescribe('Snippets route', () => { }); it('responds with 200 status and json', () => { return request(server) - .get(`/snippets/?_id=${user._id}`) + .get(`/snippets/?userId=${user._id}`) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8'); }); it('responds with data that has keys: title, comments, storedCode, language', () => { return request(server) - .get(`/snippets/?_id=${user._id}`) + .get(`/snippets/?userId=${user._id}`) .expect((res) => { if (!res.body[0].hasOwnProperty('title')) { throw new Error("Expected 'title' key!"); @@ -69,7 +77,7 @@ xdescribe('Snippets route', () => { }); it('responds with data that has key, tags, and value of an array', () => { return request(server) - .get(`/snippets/?_id=${user._id}`) + .get(`/snippets/?userId=${user._id}`) .expect((res) => { if (!res.body[0].hasOwnProperty('tags')) { throw new Error("Expected 'tags' key!"); @@ -106,15 +114,15 @@ xdescribe('Snippets route', () => { }); it('responds with 200 status and json', () => { return request(server) - .post('/snippets') - .send({ ...snippet, userId: user._id }) + .post(`/snippets/?userId=${user._id}`) + .send(snippet) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8'); }); it('responds with the newly created document', () => { return request(server) - .post('/snippets') - .send({ ...snippet, userId: user._id }) + .post(`/snippets/?userId=${user._id}`) + .send(snippet) .expect((res) => { expect(res.body.title).toBe(snippet.title); expect(res.body.comments).toBe(snippet.comments); @@ -125,15 +133,57 @@ xdescribe('Snippets route', () => { }); it("pushes newly created document to user's snippets array", () => { return request(server) - .post('/snippets') - .send({ ...snippet, userId: user._id }) + .post(`/snippets/?userId=${user._id}`) + .send(snippet) .expect(async (res) => { user = await Users.findById(user._id); expect(user.snippets.length).toEqual(1); }); }); }); - describe('PUT', () => {}); + describe('PUT', () => { + beforeAll(async () => { + console.log('Connecting to the database!'); + await mongoose.connect(mongoURI); + + console.log('Creating dummy data!'); + user = await Users.create({ username, password }); + + const fakeSnippet = await Snippets.create(snippet); + snippet_id = fakeSnippet._id; + + return; + }); + afterEach(async () => { + return await Snippets.findByIdAndUpdate(snippet_id, snippet); + }); + afterAll(async () => { + console.log('Deleting dummy data!'); + await Snippets.findByIdAndDelete(snippet_id); + await Users.findByIdAndDelete(user._id); + + console.log('Disconnecting from the database!'); + return await mongoose.connection.close(); + }); + it('responds with 200 status and json', () => { + return request(server) + .put(`/snippets/?snippetId=${snippet_id}&userId=${user._id}`) + .send(newSnippet) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8'); + }); + it('responds with unupdated document', () => { + return request(server) + .put(`/snippets/?snippetId=${snippet_id}&userId=${user._id}`) + .send(newSnippet) + .expect((res) => { + console.log(res.body); + expect(res.body.snippet.title).toEqual(snippet.title); + expect(res.body.snippet.comments).toEqual(snippet.comments); + expect(res.body.snippet.language).toEqual(snippet.language); + }); + }); + }); describe('DELETE', () => { beforeAll(async () => { console.log('Connecting to the database!'); @@ -165,11 +215,11 @@ xdescribe('Snippets route', () => { return request(server) .delete(`/snippets/?userId=${user._id}&snippetId=${snippet_id}`) .expect((res) => { - expect(res.body.title).toBe(snippet.title); - expect(res.body.comments).toBe(snippet.comments); - expect(res.body.storedCode).toBe(snippet.storedCode); - expect(res.body.language).toBe(snippet.language); - expect(res.body.tags).toEqual(snippet.tags); + expect(res.body.snippet.title).toBe(snippet.title); + expect(res.body.snippet.comments).toBe(snippet.comments); + expect(res.body.snippet.storedCode).toBe(snippet.storedCode); + expect(res.body.snippet.language).toBe(snippet.language); + expect(res.body.snippet.tags).toEqual(snippet.tags); }); }); it("removes delete document from user's snippets array", () => { @@ -183,7 +233,7 @@ xdescribe('Snippets route', () => { }); }); -xdescribe('Authentication route', () => { +describe('Authentication route', () => { let user; const username = '__DummyData__'; const password = 'codesmith'; @@ -240,9 +290,9 @@ xdescribe('Authentication route', () => { }); it('responds with 200 status and json', () => { return request(server) - .post('/authentication') + .post('/authentication/signup') .send({ username, password }) - .expect(200) + .expect(201) .expect('Content-Type', 'application/json; charset=utf-8') .expect((res) => { user = res.body; @@ -250,7 +300,7 @@ xdescribe('Authentication route', () => { }); it('responds with newly created user document', () => { return request(server) - .post('/authentication') + .post('/authentication/signup') .send({ username, password }) .expect((res) => { user = res.body; @@ -260,7 +310,7 @@ xdescribe('Authentication route', () => { }); }); -xdescribe('Error handling', () => { +describe('Error handling', () => { describe('Invalid route', () => { it('it returns a 404 status and error message', () => { return request(server) diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 47481a0..b98845d 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -36,7 +36,7 @@ authenticationController.signUp = async (req, res, next) => { username: req.body.username, password: hashedPassword, }); - res.locals.newUser = await newUser; + res.locals.newUser = newUser; return next(); } catch (err) { return next(err); From 4af6a0f627ce996f223c2dba6c9593115f57c981 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Wed, 17 May 2023 17:00:32 -0400 Subject: [PATCH 29/48] Additional styling, plus start of tags display --- client/src/assets/arrow.png | Bin 528 -> 4859 bytes .../SnippetDisplay/SnippetDisplay.jsx | 234 +++++------------- .../SnippetDisplay/SnippetDisplay.module.scss | 25 +- .../src/components/ui/TagInput/TagInput.jsx | 29 ++- .../src/components/ui/TagInput/TagInput.scss | 26 ++ .../MainContainer/MainContainer.module.scss | 5 +- client/src/containers/Sidebar/Sidebar.jsx | 37 ++- .../containers/Sidebar/Sidebar.module.scss | 58 +++-- client/src/scss/_global.scss | 11 +- 9 files changed, 202 insertions(+), 223 deletions(-) create mode 100644 client/src/components/ui/TagInput/TagInput.scss diff --git a/client/src/assets/arrow.png b/client/src/assets/arrow.png index 0302fecd1a0e0d547f2df4a180a36295013a13a9..79dbf61c5ab7f266e914bfe600055ebe8672a420 100644 GIT binary patch literal 4859 zcmeHKX;c$g7A_P~5k>;2j4fCOHekp;DF?;z4v=x z-ILlF5gz1dKi(dKAje>dC=&c4mScn+`1bJIa2$e$bT5yNHAbSTuuiX45-J=vrs;4P zHxWt*GTnTdH!tVzbcern^6ETla-5v&YvTWL-!A?2n|1EmX?xPT+KWrt%j&1zq_{b+IGjCF#CU!Z}5bft<4^6K>NNIIkfG$K6eg^9U6OcHDbN9Ybmw!LE`m zFVJUqRr*$5`{ApSi=jCU?NvLbUCSGJ`pvH~S6Yve{2HX;QAGNs3}3@v_9ZpNiW`QN zP+JN-LgPHUp6W=Yf||vx4%z3{9$hfcy`mhOx#_5O*3#k-hlSe_$EB=?u`7zVI58(~ ztjXFvtHGInL-x%1wLaRcio`dpkuSM%@>lm!l<$%s6KMvMhplX?o9CI|?wT`t&*d+3 z-N30btoweKXX^;hi{epir~PSDWHFf&e=oC*u?g7hT~8{JJjpQM*zvYkZa=e7!kKDWfcXw4iK^*XcyBx{!rgO|^QYOs>>iH-DWFL(@E4 zTq!Hf42?=EnP2(y&GBDxVl}3Ufc<5AQd@gU30lR|vn`d$s(pJa{(bFe`mst!4@9?e z6QX)~sL-#kKJ=q_^SF|TQ{Rj) zQjT_UZP{qYgzn~!tfe@`3k~$PeEofRT;sP{kLTu+itJ7X1UT6C1RUA!@x*zbXw5J0 z?(FpTTXy$d2z6Nb#GF@`pSqWkmIzmnX3a$>C%fd&?qa39zFqMseP4!8ck^4UYw-ID zW3r2%$DOL`mC(0A#dyf(gG(XEW-TEUMg$9m@5K)!pTA<8Kyu!HLfw+6Q1J|LBYbD; z{^S$zj5~*9(*xH<%slldes=FL>G&MiaX~d@w{F$Tuh-PY#SD)LaC!|bOI{Xf+~)i) z&vikqN%u>3>I;w5=TK>!jCD9+!c}8iV^K~ZwQ9!NC8TqZNkqzXZP?{f$NH+iMKDBF z8TwD}cZQW)GRoq;{Qcp*GKc1=9q`_cQ;rv2)t-sm8GXlahq0Y1d418?de`T|lsw+f zOW(9M`ZDHv=a$DllOIqmt&OT>$eO+OHKZLuI^;)Wk3(MVeX?#_=GhaCMzf~`C51Xo z@}J{ACK~k&I2@j}&f)2_6|c{iE}5JcR%-96_x`(`W6tB@uhw+7FQ~gTlIE3T8%W#I zLmGar!vFAXZfE_LX1-14mFgWG-7V(o#%T{XZy7slNI_e5U2@R&7pofF zlXY57GWiv1e;B&vJO55|%a`dBi%0wKEDz~~hOe(q+<2m8MMryEds|h)%{OnLg*)|o zrB{AF4~Eou0t};AX&4{Vs;Q_#E61rOwGIp`2=eta=};^QH^Oo}fzSxZ&nv3PFrg5T zW7txA81oPG#!B_JmnjXFM!mYSMMO=VKG z`UDz-$K%oH2n|6fz=L8)(-=_`MPu-^K=fgVa08|%bVfp}fi0M*T$^kZkjY>jeqWzj zCzTGsYYhD?06u6YR7Yb_=`^*PHaNmy6sG`?{(}B6!VnE+IxP}6Xp{9AE>6KUM$f?z z3Tz-=m#kM=+fiUNT!pKFs{v$Xe3UXMSQ;@9VWA*_Q0uHwfb5SvjfC<8Ss&GAS+TY= zxDXINfcug6``)e2z)LFSi?mp>rMzH~fNaUnS7<3V>4yfTQ5?xF{1>P%t@NNny!(7)6e<k*7%ee>#Y%q=jURg|pLE*8uC`BRn@nJJ~95zSZ z*Z2UsL8~;TqIx_a0dxww0Rpmi15dY7>Gg3fsfoCSCpyBU(AgA*Pc(zW2Ooq>p|kjO zI+@nrFwJsTzwg+W_CK8XS{(+B0T9<`1LFluE84)g>gUWt=dTUr<+ z%$4^bKWemFkE+Dsbob%{dtUdVf)Nj0!kt}OJce#s#njoBpV*bX|JjLM)e&a5`1Z+- z7u>K-j0Px$%1J~)RA@u(*iA~sFc84;|HF-nE(E=S zy3+%Qcn9?e>JM&g(sxVz4YA_NI6&NW%=&U?q_eW54B%=lHr*KpVP=@`R> z*Z|HwpDX%!v+gcPKAgakwdqANrau?d{{zSwow#l9mkh*{viCWy|6*ovq_ z#6S*!{YqBb=82@0_=_d9wd}trW(*aGn2OjCHHa9}AW}eHfr*M%WS(ftpmb}QcV^6bXck0@imU==6J?Hd;G!*)Qs<>BA{AHg tI0X`%vcxx(iX={O@bIM~!6{38!!KVrk~916r6m9W002ovPDHLkV1nE_<97f6 diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index 20c4776..be5818a 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -41,6 +41,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { }) .then((response) => { if (response.ok) { + setCurrentDisplay(defaultDisplayValues); getSnippet(); } }) @@ -80,53 +81,65 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { const displayContent = (
- Title: - { - if (editButtonState) { - setCurrentDisplay({ ...currentDisplay, title: e.target.value }); - } - }} - > - - Language: - { - if (editButtonState) { - setCurrentDisplay({ - ...currentDisplay, - language: e.target.value - }); - } - }} - > - - Comments: - { - if (editButtonState) { - setCurrentDisplay({ - ...currentDisplay, - comments: e.target.value - }); - } - }} - > +
+
+ Title: + { + if (editButtonState) { + setCurrentDisplay({ + ...currentDisplay, + title: e.target.value + }); + } + }} + > +
+
+ Language: + { + if (editButtonState) { + setCurrentDisplay({ + ...currentDisplay, + language: e.target.value + }); + } + }} + > +
+
+
+
+ Comments: + { + if (editButtonState) { + setCurrentDisplay({ + ...currentDisplay, + comments: e.target.value + }); + } + }} + > +
+
{ if (editButtonState) { setCurrentDisplay({ ...currentDisplay, tags: e }); } }} defaultTags={currentDisplay.tags} + readOnly={!editButtonState} /> {/* {setTags}}> Title: {snippetTagList} */}
@@ -149,134 +162,8 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { - -
); - // const checkEdit = () => { - // if (editButtonState === true) { - // return ( - //
- //
- // Title: - // { - // snippetTitle = e.target.value; - // }} - // > - - // Language: - // { - // snippetLanguage = e.target.value; - // }} - // > - - // Comments: - // { - // snippetComments = e.target.value; - // }} - // > - - // (snippetTagList = e)} - // tags={snippetTagList} - // /> - // {/* {setTags}}> Title: {snippetTagList} */} - //
- - // {\n console.log(\'Hello World!)\n}'} - // onChange={(e) => (snippetStoredCode = e)} - // > - // setCopied(true)} - // > - // - // - // - - // - //
- // ); - // } - - // if (editButtonState === false) { - // return ( - //
- //
- //

- // {snippetTitle} - // Title: {snippetTitle} - //

- //

- // {snippetLanguage} - // Language: {snippetLanguage} - //

- //

- // {snippetComments} - // Comments: {snippetComments} - //

- // - // {/*
{renderTags()}
*/} - //
- - // {\n console.log(\'Hello World!)\n}'} - // options={{ - // readOnly: true - // }} - // onChange={(e) => (snippetStoredCode = e)} - // > - // setCopied(true)} - // > - // - // - // - //
- // ); - // } - // }; return ( @@ -296,10 +183,23 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { className="editButton" onClick={() => { //editSnippet(selectedSnippet.id); - setEditButtonState(true); + editButtonState + ? setEditButtonState(false) + : setEditButtonState(true); + }} + > + {editButtonState ? 'Close Editor' : 'Edit Snippet'} + +
diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.module.scss b/client/src/components/SnippetDisplay/SnippetDisplay.module.scss index 705f93c..4d8e8ce 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.module.scss +++ b/client/src/components/SnippetDisplay/SnippetDisplay.module.scss @@ -7,6 +7,28 @@ padding-bottom: 10px; } +.displayRow { + display: flex; + flex-direction: row; + padding-top: 10px !important; + padding-bottom: 10px !important; + gap: 10px; + input { + background-color: transparent; + color: whitesmoke; + border: none; + border-bottom: 1.5pt double whitesmoke; + } + span { + margin: 5px; + } + + .aspect-entry { + display: flex; + align-items: baseline; + } +} + .title { width: 185px; } @@ -39,7 +61,8 @@ .card { padding: 15px; - margin: 50px; + margin-left: 50px; + margin-right: 50px; display: flex; } diff --git a/client/src/components/ui/TagInput/TagInput.jsx b/client/src/components/ui/TagInput/TagInput.jsx index 4e1d5b4..38cc13a 100644 --- a/client/src/components/ui/TagInput/TagInput.jsx +++ b/client/src/components/ui/TagInput/TagInput.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import './TagInput.scss'; // importing utils import { WithContext as ReactTags } from 'react-tag-input'; @@ -23,20 +24,19 @@ const KeyCodes = { const delimiters = [KeyCodes.comma, KeyCodes.enter]; -const TagInput = ({ onChange, defaultTags }) => { +const TagInput = ({ onChange, defaultTags, readOnly }) => { const [tags, setTags] = useState([]); const handleDelete = (i) => { setTags(tags.filter((tag, index) => index !== i)); }; - // useEffect(() => { - // console.log('hello'); - // initialTags(); - // }, [tags]); - const handleAddition = (tag) => { - setTags([...tags, tag]); + console.log('Added tag:'); + console.dir(tag); + const newTags = tags.map((el) => el); + newTags.push(tag); + setTags(newTags); }; const handleDrag = (tag, currPos, newPos) => { @@ -55,20 +55,27 @@ const TagInput = ({ onChange, defaultTags }) => { useEffect(() => { if (defaultTags) { - setTags(defaultTags); + const tagArr = []; + defaultTags.forEach((tag) => { + tagArr.push({ id: tag, text: tag }); + }); + setTags(tagArr); } - }, []); + }, [defaultTags]); useEffect(() => { const tagStringList = []; if (onChange) { - tags.forEach((tag) => tagStringList.push(tag)); + tags.forEach((tag) => tagStringList.push(tag.text)); onChange(tagStringList); } }, [tags]); return ( - { {/*----- SIDE BAR -----*/} {/* Changes the collapse state, which will render/unrender the sidebar*/} - +
+ + + + +
{/* Renders the list of snippets fetched from DB */} - + {/* Animation while app is fetching data from DB */}
{loading && ( -
+
)} @@ -104,16 +120,13 @@ const Sidebar = ({ handleLogin }) => {
-

- Click me to add a new snippet! -

diff --git a/client/src/containers/Sidebar/Sidebar.module.scss b/client/src/containers/Sidebar/Sidebar.module.scss index b3215b1..cbca283 100644 --- a/client/src/containers/Sidebar/Sidebar.module.scss +++ b/client/src/containers/Sidebar/Sidebar.module.scss @@ -9,19 +9,37 @@ // border-color: black; // } +.displayTypeSelectosr { + display: flex; + justify-content: space-around; + gap: 10px; + width: 100%; + input { + display: none; + } + label { + } + .displayTypeButton { + .active { + color: whitesmoke; + border-bottom: 1pt double whitesmoke; + } + .inactive { + } + } +} + .sidebar { width: 300px; - height: 100vh !important; + height: inherit; position: absolute; left: -300px; top: 0; - + transition: 200ms ease-in; background-color: rgb(10, 25, 41); - padding-top: 28px; - display: flex; flex-direction: column; justify-content: space-evenly; @@ -31,12 +49,12 @@ z-index: 20; right: -50px; top: 35px; - transform: scale(.5); + transform: scale(0.5); background: none; border: none; .arrow { - + color: whitesmoke; } .arrowOpen { @@ -45,7 +63,7 @@ } .cardBody { - + padding: 8px 16px; } } @@ -58,11 +76,13 @@ // z-index: -20; left: -250px; transition: 200ms ease-in; + width: 100%; } .snippetDisplayOpen { left: 0; transition: 200ms ease-in; + width: 100%; } .logo { @@ -97,17 +117,17 @@ .addButton { border: transparent; - height: 50px; - width: 270px; - // padding-left: 7px; - // padding-right: 7px; - border-radius: 10px; - margin-left: 10px; - margin-right: 10px; - margin-bottom: 10px; - font-size: 1.5rem; - background-color: transparent; - color: white; + height: 50px; + width: 270px; + // padding-left: 7px; + // padding-right: 7px; + border-radius: 10px; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; + font-size: 1.5rem; + background-color: transparent; + color: white; } .img { @@ -122,4 +142,4 @@ margin-bottom: 110px; margin-left: 3px; font-size: 21px; -} \ No newline at end of file +} diff --git a/client/src/scss/_global.scss b/client/src/scss/_global.scss index a0dde6a..da7d9dc 100644 --- a/client/src/scss/_global.scss +++ b/client/src/scss/_global.scss @@ -7,15 +7,6 @@ body { background-size: cover; } -// this might be a bug. react tag library wasnt going inline -.ReactTags__selected { - display: flex; -} - -.cm-editor { - outline: none !important; -} - #right { background: radial-gradient(150px 40px at 195px bottom, #666, #222); @@ -25,7 +16,7 @@ body { #card { background: radial-gradient(150px 40px at 195px bottom, #666, #222); color: white; - margin-top: 100px; + margin-left: 20px; } .login { From 993258608555f39353b5174cdbb46e20c42ab3b6 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Wed, 17 May 2023 17:33:32 -0400 Subject: [PATCH 30/48] Minor stylistic changes --- .../containers/MainContainer/MainContainer.jsx | 15 +++++++++------ client/src/containers/Sidebar/Sidebar.module.scss | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index f450085..1dfa4e2 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -18,12 +18,12 @@ const MainContainer = () => { fetch('http://localhost:3000/authentication/login', { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, body: JSON.stringify({ username: usernameInputValue, - password: passwordInputValue, - }), + password: passwordInputValue + }) }) .then((result) => result.json()) .then((result) => { @@ -33,6 +33,9 @@ const MainContainer = () => { .catch((err) => { console.log(err); }); + + //Bypass login requirement: + setLogin(true); }; //functino to handle showing the signup page const handleHaveAccount = () => setHaveAccount(false); @@ -48,12 +51,12 @@ const MainContainer = () => { fetch('http://localhost:3000/authentication/signup', { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, body: JSON.stringify({ username: nameValue, - password: passwordValue, - }), + password: passwordValue + }) }) .then((result) => result.json()) .then((result) => { diff --git a/client/src/containers/Sidebar/Sidebar.module.scss b/client/src/containers/Sidebar/Sidebar.module.scss index cbca283..a795c8f 100644 --- a/client/src/containers/Sidebar/Sidebar.module.scss +++ b/client/src/containers/Sidebar/Sidebar.module.scss @@ -16,6 +16,7 @@ width: 100%; input { display: none; + appearance: none; } label { } From aabdcca66c8901cbdcf78e5eb551d4847a422b24 Mon Sep 17 00:00:00 2001 From: LeRocque Date: Wed, 17 May 2023 20:21:03 -0400 Subject: [PATCH 31/48] removed logs --- server/authConfig/jwt.config.js | 5 ----- server/authConfig/passport.js | 1 - 2 files changed, 6 deletions(-) diff --git a/server/authConfig/jwt.config.js b/server/authConfig/jwt.config.js index 57e6d4a..cab6b5a 100644 --- a/server/authConfig/jwt.config.js +++ b/server/authConfig/jwt.config.js @@ -8,7 +8,6 @@ const JwtStrategy = require('passport-jwt').Strategy, require('dotenv').config(); const secret = process.env.JWT_SECRET; -console.log('outside passport jwt config'); // Passport JWT config const options = {}; options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); @@ -17,14 +16,10 @@ options.secretOrKey = secret; // new JWT strategy const jwtStrategy = new JwtStrategy(options, async (payload, done) => { - console.log('JwtStrat called', options); try{ // find user based on JWT payload const user = await User.findById(payload.userId); - console.log('payload', payload); - console.log('payload.userId', payload.userId); if (user) { - // if user is found, return user return done(null, user); } else { diff --git a/server/authConfig/passport.js b/server/authConfig/passport.js index 141bdf2..4d06a1a 100644 --- a/server/authConfig/passport.js +++ b/server/authConfig/passport.js @@ -11,7 +11,6 @@ passport.use(new LocalStrategy({ usernameField: 'username', // field name for username in req body passwordField: 'password', // field name for password in req body }, async (username, password, done) => { - console.log('local strat called'); try { const user = await User.findOne({ username }); From 85823e6777204013b7600c31956f43147f2132e7 Mon Sep 17 00:00:00 2001 From: LeRocque Date: Wed, 17 May 2023 20:29:03 -0400 Subject: [PATCH 32/48] removed unneeded require --- server/controllers/authenticationController.js | 1 - server/server.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 31de766..1f847df 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -1,5 +1,4 @@ const passport = require('../authConfig/passport.js'); -const iNeed = require('../authConfig/jwt.config'); const User = require('../models/userModel.js'); const bcrypt = require('bcrypt'); const authenticationController = {}; diff --git a/server/server.js b/server/server.js index c9b8940..5cec8a2 100644 --- a/server/server.js +++ b/server/server.js @@ -40,7 +40,7 @@ app.use((err, req, res, next) => { message: { err: 'An error occurred' } }; const errorObj = Object.assign({}, defaultErr, err); - console.log(errorObj); + console.log(errorObj.log); return res.status(errorObj.status).json(errorObj.message); }); From 74d1a9d20a4e075836193ca0bdb74f3bd65aeb58 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Wed, 17 May 2023 20:38:06 -0400 Subject: [PATCH 33/48] Added preliminary tag support, though not yet filtering --- .../src/components/AddSnippet/AddSnippet.jsx | 15 +- client/src/containers/Sidebar/Sidebar.jsx | 138 +++++++++++++----- .../containers/Sidebar/Sidebar.module.scss | 35 +++-- .../containers/Sidebar/TagsList/TagsList.jsx | 41 ++++++ .../Sidebar/TagsList/TagsList.module.scss | 16 ++ server/controllers/snippetsController.js | 3 + server/routes/snippetsRouter.js | 10 +- 7 files changed, 196 insertions(+), 62 deletions(-) create mode 100644 client/src/containers/Sidebar/TagsList/TagsList.jsx create mode 100644 client/src/containers/Sidebar/TagsList/TagsList.module.scss diff --git a/client/src/components/AddSnippet/AddSnippet.jsx b/client/src/components/AddSnippet/AddSnippet.jsx index b65d255..c92bfa8 100644 --- a/client/src/components/AddSnippet/AddSnippet.jsx +++ b/client/src/components/AddSnippet/AddSnippet.jsx @@ -7,8 +7,6 @@ import TagInput from '../../components/ui/TagInput/TagInput'; // importing external functionality import CodeMirror from '@uiw/react-codemirror'; import PropTypes from 'prop-types'; -import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; -import { languages } from '@codemirror/language-data'; import { langs } from '@uiw/codemirror-extensions-langs'; // importing utils @@ -21,12 +19,12 @@ import styles from './AddSnippet.module.scss'; // importing data import { LANGUAGES } from '../../data/data.js'; -const AddSnippet = ({ closeModal }) => { +const AddSnippet = ({ closeModal, getUserData }) => { const [title, setTitle] = useState(''); const [language, setLanguage] = useState(''); const [comments, setComments] = useState(''); const [storedCode, setStoredCode] = useState(''); - const [tagList, setTags] = useState(''); + const [tagList, setTags] = useState([]); const [error, setError] = useState(false); const [openModal, setOpenModal] = useState(false); @@ -57,7 +55,11 @@ const AddSnippet = ({ closeModal }) => { storedCode: storedCode }) }) - .then((data) => data.json()) + .then((data) => { + getUserData(); + closeModal(false); + data.json(); + }) .catch((err) => { console.log(err); console.log('failed saving snippet'); @@ -178,7 +180,8 @@ const AddSnippet = ({ closeModal }) => { }; AddSnippet.propTypes = { - closeModal: PropTypes.func + closeModal: PropTypes.func, + getUserData: PropTypes.func }; export default AddSnippet; diff --git a/client/src/containers/Sidebar/Sidebar.jsx b/client/src/containers/Sidebar/Sidebar.jsx index ca6a0b5..8abb0ee 100644 --- a/client/src/containers/Sidebar/Sidebar.jsx +++ b/client/src/containers/Sidebar/Sidebar.jsx @@ -4,6 +4,7 @@ import React, { useState, useEffect } from 'react'; import SnippetDisplay from '../../components/SnippetDisplay/SnippetDisplay.jsx'; import AddSnippet from '../../components/AddSnippet/AddSnippet.jsx'; import SnippetsRadioList from './SnippetsRadioList/SnippetsRadioList.jsx'; +import TagsList from './TagsList/TagsList.jsx'; // importing utils import { Card, Spinner } from 'react-bootstrap'; @@ -16,27 +17,35 @@ import arrow from '../../assets/arrow.png'; import img from '../../assets/star nose mole.jpeg'; const Sidebar = ({ handleLogin }) => { + //Snippets and selected snippet const [snippets, setSnippets] = useState([]); const [selectedSnippet, setSelectedSnippet] = useState({}); + + //Tags and selected tags + const [userTags, setUserTags] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); + const [openModal, setOpenModal] = useState(false); const [collapse, setCollapse] = useState(false); const [loading, setLoading] = useState(true); + const [displayType, setDisplayType] = useState('snippets'); useEffect(() => { - getSnippet(); + getUserData(); }, []); //Get all snippets stored under user's account //TODO: Get user ID from global state to include in request //FIXME: HARD CODING ID FOR NOW const userId = '6463eb52ab99bf89a84a3ebd'; - const getSnippet = () => { + const getUserData = () => { setLoading(true); fetch('/snippets?' + new URLSearchParams({ userId: userId })) .then((res) => res.json()) - .then((newSnippetArray) => { + .then((data) => { //As structured in snippets route, should receive an array of snippet objects - setSnippets(newSnippetArray); + setSnippets(data.snippets); + setUserTags([...data.tagsLangs.tags, ...data.tagsLangs.languages]); setLoading(false); }) .catch((error) => @@ -60,10 +69,69 @@ const Sidebar = ({ handleLogin }) => { setSelectedSnippet(e); }; + const selectDeselectTag = (tagValue) => { + const newTagList = new Set(selectedTags); + if (!newTagList.has(tagValue)) { + newTagList.add(tagValue); + } else { + newTagList.delete(tagValue); + } + + setSelectedTags(Array.from(newTagList)); + }; + const toggleSidebar = () => { setCollapse(() => !collapse); }; + const toggleDisplayType = (event) => { + console.log(event.target.value); + setDisplayType(event.target.value); + }; + + const snippetsDisplay = ( + + {/* Animation while app is fetching data from DB */} +
+ {loading && ( +
+ +
+ )} + +
+
+ ); + + const tagsDisplay = ( + + {/* Animation while app is fetching data from DB */} +
+ {loading && ( +
+ +
+ )} + +
+
+ ); + return ( {/*----- SIDE BAR -----*/} @@ -74,21 +142,28 @@ const Sidebar = ({ handleLogin }) => { {/* Changes the collapse state, which will render/unrender the sidebar*/}
- - - - + className={ + displayType === 'snippets' + ? styles.displayTypeButtonActive + : styles.displayTypeButtonInactive + } + onClick={toggleDisplayType} + > + Snippets + +
{/*----- ADD SNIPPET MODAL -----*/} - {openModal && } + {openModal && ( + + )} {/*----- SNIPPET DISPLAY -----*/} @@ -144,7 +204,7 @@ const Sidebar = ({ handleLogin }) => { {snippets && ( )}
diff --git a/client/src/containers/Sidebar/Sidebar.module.scss b/client/src/containers/Sidebar/Sidebar.module.scss index a795c8f..f29bab6 100644 --- a/client/src/containers/Sidebar/Sidebar.module.scss +++ b/client/src/containers/Sidebar/Sidebar.module.scss @@ -9,25 +9,29 @@ // border-color: black; // } -.displayTypeSelectosr { +.displayTypeSelector { display: flex; justify-content: space-around; + text-align: center; gap: 10px; width: 100%; - input { - display: none; - appearance: none; - } - label { - } - .displayTypeButton { - .active { - color: whitesmoke; - border-bottom: 1pt double whitesmoke; - } - .inactive { - } - } +} + +.displayTypeButtonActive { + background-color: transparent; + box-shadow: none; + width: 100%; + color: whitesmoke !important; + border: none; + border-bottom: 1.5pt solid whitesmoke !important; +} + +.displayTypeButtonInactive { + background-color: transparent; + box-shadow: none; + width: 100%; + color: rgb(124, 124, 124) !important; + border: none !important; } .sidebar { @@ -119,7 +123,6 @@ .addButton { border: transparent; height: 50px; - width: 270px; // padding-left: 7px; // padding-right: 7px; border-radius: 10px; diff --git a/client/src/containers/Sidebar/TagsList/TagsList.jsx b/client/src/containers/Sidebar/TagsList/TagsList.jsx new file mode 100644 index 0000000..5522660 --- /dev/null +++ b/client/src/containers/Sidebar/TagsList/TagsList.jsx @@ -0,0 +1,41 @@ +import React, { useEffect } from 'react'; + +// importing utils +import PropTypes from 'prop-types'; + +// importing styles +import styles from './TagsList.module.scss'; + +function TagsList({ allTags, selectedTags, selectDeselectTag }) { + const buttonList = []; + + const onTagClick = (e) => { + selectDeselectTag(e.target.value); + }; + + if (allTags) { + allTags.forEach((el) => { + const currButton = ( + + ); + buttonList.push(currButton); + }); + } + + return
{buttonList}
; +} +TagsList.propTypes = { + allTags: PropTypes.array, + selectedTags: PropTypes.array, + selectDeselectTag: PropTypes.func +}; +export default TagsList; diff --git a/client/src/containers/Sidebar/TagsList/TagsList.module.scss b/client/src/containers/Sidebar/TagsList/TagsList.module.scss new file mode 100644 index 0000000..17a0433 --- /dev/null +++ b/client/src/containers/Sidebar/TagsList/TagsList.module.scss @@ -0,0 +1,16 @@ +.activeTag { + border: none; + background-color: whitesmoke; + color: black; + border-radius: 10px; +} + +.inactiveTag { + border: 1pt solid whitesmoke; + background-color: black; + color: whitesmoke; + border-radius: 10px; +} + +.tagsListDisplay { +} diff --git a/server/controllers/snippetsController.js b/server/controllers/snippetsController.js index 96bf898..8295cc4 100644 --- a/server/controllers/snippetsController.js +++ b/server/controllers/snippetsController.js @@ -12,6 +12,7 @@ const createError = (method, log, status, message = log) => { }; //Retrieves all snippets associated with a user by looking up user (by ID) and referencing all snippets in the associated list +//NOTE: WE SHOULD REALLY SEPARATE OUT STUFF LIKE THIS INTO A SEPARATE USER ROUTE AND USER CONTROLLER snippetsController.getSnippetsByUser = (req, res, next) => { const { userId } = req.query; //const userId = '645fee9104d1f0acef95a002'; @@ -21,6 +22,7 @@ snippetsController.getSnippetsByUser = (req, res, next) => { .exec() .then((user) => { res.locals.allSnippets = user.snippets; + res.locals.userTagsLangs = { tags: user.tags, languages: user.languages }; return next(); }) .catch((err) => { @@ -55,6 +57,7 @@ snippetsController.saveSnippetToUser = (req, res, next) => { .save() .then((r) => { res.locals.updatedUserRecord = r; + res.locals.changeFlag = true; return next(); }) .catch((err) => { diff --git a/server/routes/snippetsRouter.js b/server/routes/snippetsRouter.js index 02ff000..c6428f5 100644 --- a/server/routes/snippetsRouter.js +++ b/server/routes/snippetsRouter.js @@ -4,14 +4,22 @@ const snippetsController = require('../controllers/snippetsController'); const router = express.Router(); +//NOTE: I'm muddying things here by returning tags and languages alongside snippets +//In the future, this should be refactored as a route to explicitly load all user data +//in the context of a separate user route and user controller + router.get('/', snippetsController.getSnippetsByUser, (req, res) => - res.status(200).json(res.locals.allSnippets) + res.status(200).json({ + snippets: res.locals.allSnippets, + tagsLangs: res.locals.userTagsLangs + }) ); router.post( '/', snippetsController.createSnippet, snippetsController.saveSnippetToUser, + snippetsController.recalcTagsAndLang, (req, res) => res.status(200).json(res.locals.newSnippet) ); From fb7c0a1206587d9ee6e78f0d14339b75b71db53e Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Wed, 17 May 2023 21:26:17 -0500 Subject: [PATCH 34/48] updated styles --- client/src/containers/MainContainer/MainContainer.jsx | 1 + client/src/containers/Sidebar/Sidebar.module.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index f450085..fd3ac20 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -59,6 +59,7 @@ const MainContainer = () => { .then((result) => { console.log('result from signup request: ', result); setHaveAccount(true); + setLogin(true); }) .catch((err) => { console.log(err); diff --git a/client/src/containers/Sidebar/Sidebar.module.scss b/client/src/containers/Sidebar/Sidebar.module.scss index cbca283..059e597 100644 --- a/client/src/containers/Sidebar/Sidebar.module.scss +++ b/client/src/containers/Sidebar/Sidebar.module.scss @@ -33,7 +33,7 @@ width: 300px; height: inherit; position: absolute; - left: -300px; + left: -250px; top: 0; transition: 200ms ease-in; From e8bd1003138103e389c28f5758a07102027a20a6 Mon Sep 17 00:00:00 2001 From: LeRocque Date: Thu, 18 May 2023 09:58:45 -0400 Subject: [PATCH 35/48] cookies-solved --- .../MainContainer/MainContainer.jsx | 2 ++ .../controllers/authenticationController.js | 26 ------------------- server/routes/authenticationRouter.js | 11 +------- server/server.js | 13 +++++++--- 4 files changed, 12 insertions(+), 40 deletions(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index f450085..96e4fda 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -20,6 +20,8 @@ const MainContainer = () => { headers: { 'Content-Type': 'application/json', }, + // include cookies from cross origin request + credentials: 'include', body: JSON.stringify({ username: usernameInputValue, password: passwordInputValue, diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 1f847df..4509d41 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -3,17 +3,6 @@ const User = require('../models/userModel.js'); const bcrypt = require('bcrypt'); const authenticationController = {}; -//Error creator method specific to this controller -const createError = (method, log, status, message = log) => { - return { - log: `Error occurred in authenticationController.${method}: ${log}`, - status, - message: { err: message }, - }; -}; - -//Authentication and user creation methods go here. -//Feel free to change these as you see fit -- I just have them in here for testing purposes. authenticationController.signUp = async (req, res, next) => { const { username, password } = req.body; try { @@ -42,20 +31,5 @@ authenticationController.signUp = async (req, res, next) => { } }; -authenticationController.getUserData = (req, res, next) => { - const { _id } = req.query; - User.findById(_id) - .exec() - .then((user) => { - res.locals.userData = user; - return next(); - }) - .catch((err) => { - return next( - createError('getUserData', `Error retrieving user data: ${err}`, 500) - ); - }); -}; - module.exports = authenticationController; diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index da22d68..35422b8 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -12,9 +12,7 @@ router.post('/signup', authenticationController.signUp, (req, res) => { return res.status(201).json(res.locals.newUser); }); -router.post( - '/login', - passport.authenticate('local', { session: false }), +router.post('/login',passport.authenticate('local', { session: false }), (req, res) => { const token = jwt.sign({ userId: req.user.id }, secret, { expiresIn: '1d', @@ -23,10 +21,6 @@ router.post( expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),// Expires in 30 days httpOnly: true }); - res.cookie('test', 'test', { - domain: 'localhost', - path: '/' - }); return res.status(202).json({ token }); } ); @@ -36,7 +30,4 @@ router.get('/protected', passport.authenticate('jwt', {session: false }), (req, res.send('Protected route accessed!'); }); -router.get('/', authenticationController.getUserData, (req, res) => { - res.status(200).json(res.locals.userData); -}); module.exports = router; diff --git a/server/server.js b/server/server.js index 5cec8a2..c72030b 100644 --- a/server/server.js +++ b/server/server.js @@ -18,13 +18,18 @@ const port = process.env.PORT || 3000; const mongoURI = process.env.MONGO_URI; mongoose.connect(mongoURI); -//Call default middleware +// initialize passport app.use(passport.initialize()); -app.use(cors()); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); // parse incoming cookies to authentication endpoints and store them on req.cookies object app.use(cookieParser()); +// allow cookies to be included in CORS request +app.use(cors({ + origin: 'http://localhost:8080', + credentials: true +})); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + //Point relevant requests to snippet and authentication routers From ec174fb1128bc3b21503b74e33d30f75163832a3 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Thu, 18 May 2023 10:37:20 -0400 Subject: [PATCH 36/48] a --- .../MainContainer/MainContainer.jsx | 2 +- server/routes/authenticationRouter.js | 23 +++++++++++++------ server/routes/snippetsRouter.js | 7 +++--- webpack.config.js | 22 ++++++++---------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 1dfa4e2..e0e5465 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -35,7 +35,7 @@ const MainContainer = () => { }); //Bypass login requirement: - setLogin(true); + //setLogin(true); }; //functino to handle showing the signup page const handleHaveAccount = () => setHaveAccount(false); diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index da22d68..345e6b9 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -16,25 +16,34 @@ router.post( '/login', passport.authenticate('local', { session: false }), (req, res) => { + console.log(req.user); const token = jwt.sign({ userId: req.user.id }, secret, { - expiresIn: '1d', + expiresIn: '1d' }); res.cookie('token', token, { - expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),// Expires in 30 days - httpOnly: true + expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // Expires in 30 days + httpOnly: true }); res.cookie('test', 'test', { domain: 'localhost', path: '/' }); + res.cookie('userID', req.user.id, { + expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // Expires in 30 days + httpOnly: true + }); return res.status(202).json({ token }); } ); -router.get('/protected', passport.authenticate('jwt', {session: false }), (req, res) => { - console.log('at protected router, SUCCESS!'); - res.send('Protected route accessed!'); -}); +router.get( + '/protected', + passport.authenticate('jwt', { session: false }), + (req, res) => { + console.log('at protected router, SUCCESS!'); + res.send('Protected route accessed!'); + } +); router.get('/', authenticationController.getUserData, (req, res) => { res.status(200).json(res.locals.userData); diff --git a/server/routes/snippetsRouter.js b/server/routes/snippetsRouter.js index c6428f5..5cea86f 100644 --- a/server/routes/snippetsRouter.js +++ b/server/routes/snippetsRouter.js @@ -8,12 +8,13 @@ const router = express.Router(); //In the future, this should be refactored as a route to explicitly load all user data //in the context of a separate user route and user controller -router.get('/', snippetsController.getSnippetsByUser, (req, res) => +router.get('/', snippetsController.getSnippetsByUser, (req, res) => { + console.log(req.cookies); res.status(200).json({ snippets: res.locals.allSnippets, tagsLangs: res.locals.userTagsLangs - }) -); + }); +}); router.post( '/', diff --git a/webpack.config.js b/webpack.config.js index 57743ec..07cb8ec 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,7 @@ module.exports = { rules: [ { test: /\.(png|jp(e*)g|svg|gif)$/, - type: 'asset/resource', + type: 'asset/resource' }, { test: /\.jsx?/, @@ -24,27 +24,25 @@ module.exports = { options: { presets: [ '@babel/preset-env', - ['@babel/preset-react', { runtime: 'automatic' }], - ], - }, - }, + ['@babel/preset-react', { runtime: 'automatic' }] + ] + } + } }, { test: /\.s?css/, - use: [ - 'style-loader', 'css-loader', 'sass-loader' - ] + use: ['style-loader', 'css-loader', 'sass-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'Development', - template:'./public/index.html' + template: './public/index.html' }) ], resolve: { - extensions: ['.js', '.jsx', '.scss'], + extensions: ['.js', '.jsx', '.scss'] }, devServer: { static: { @@ -52,8 +50,8 @@ module.exports = { directory: path.resolve(__dirname, 'dist') }, proxy: { - '/snippets': 'http://localhost:3000' + '/*': 'http://localhost:3000' } }, devtool: 'eval-source-map' -}; \ No newline at end of file +}; From 5453e7e85d749c68f649187c3ae1c87414e95dec Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Thu, 18 May 2023 11:06:29 -0400 Subject: [PATCH 37/48] Removed hardcoded user ID; now working with ID cookie --- client/src/components/AddSnippet/AddSnippet.jsx | 6 +----- .../components/SnippetDisplay/SnippetDisplay.jsx | 13 ++++--------- client/src/containers/Sidebar/Sidebar.jsx | 5 +---- server/controllers/snippetsController.js | 12 ++++++------ server/routes/authenticationRouter.js | 2 +- server/routes/snippetsRouter.js | 1 - webpack.config.js | 2 +- 7 files changed, 14 insertions(+), 27 deletions(-) diff --git a/client/src/components/AddSnippet/AddSnippet.jsx b/client/src/components/AddSnippet/AddSnippet.jsx index c92bfa8..c1aa27b 100644 --- a/client/src/components/AddSnippet/AddSnippet.jsx +++ b/client/src/components/AddSnippet/AddSnippet.jsx @@ -28,10 +28,6 @@ const AddSnippet = ({ closeModal, getUserData }) => { const [error, setError] = useState(false); const [openModal, setOpenModal] = useState(false); - //TODO: Pull user info from global state - //FIXME: HARCODING USER INFO FOR NOW - const userId = '6463eb52ab99bf89a84a3ebd'; - function handleSubmit(e) { e.preventDefault(); if (title === '') { @@ -42,7 +38,7 @@ const AddSnippet = ({ closeModal, getUserData }) => { setError(false); } - fetch('/snippets?' + new URLSearchParams({ userId }), { + fetch('/snippets', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index be5818a..85d5f40 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -25,18 +25,13 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { const [copied, setCopied] = useState(false); const [editButtonState, setEditButtonState] = useState(false); const [currentDisplay, setCurrentDisplay] = useState(defaultDisplayValues); - //TODO: Pull userId from global state - //FIXME: HARDCODING USER ID FOR NOW - const userId = '6463eb52ab99bf89a84a3ebd'; - // indSnippet = this.props - // create delete method using fetch request useEffect(() => { setCurrentDisplay(selectedSnippet); }, [selectedSnippet, getSnippet]); - const deleteSnippet = (snippetId, userId) => { - fetch('/snippets?' + new URLSearchParams({ snippetId, userId }), { + const deleteSnippet = (snippetId) => { + fetch('/snippets?' + new URLSearchParams({ snippetId }), { method: 'DELETE' }) .then((response) => { @@ -55,7 +50,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { }; const editSnippet = (snippetId) => { - fetch(`/snippets?${new URLSearchParams({ snippetId, userId })}`, { + fetch(`/snippets?${new URLSearchParams({ snippetId })}`, { method: 'PUT', headers: { 'Content-type': 'application/json' }, body: JSON.stringify(currentDisplay) @@ -174,7 +169,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { ); - // } - - // return tabs; - // }; - // wrapper to send to our snippets radio list for updating selected snippet. probably not 100% needed, but want to be able to console log from Sidebar const setSelectedSnippetWrapper = (e) => { setSelectedSnippet(e); @@ -82,10 +76,20 @@ const Sidebar = ({ handleLogin }) => { }; const toggleDisplayType = (event) => { - console.log(event.target.value); setDisplayType(event.target.value); }; + const filterSnippetsByTags = () => { + const snippetSubset = snippets.filter((sn) => { + for (let i = 0; i < [...sn.tags, sn.lanuage].length; i++) { + if (selectedTags.includes([...sn.tags, sn.lanuage][i])) return true; + } + return false; + }); + + setFilteredSnippets(snippetSubset); + }; + const snippetsDisplay = ( {/* Animation while app is fetching data from DB */} @@ -100,6 +104,7 @@ const Sidebar = ({ handleLogin }) => {
)} @@ -125,6 +130,11 @@ const Sidebar = ({ handleLogin }) => { selectedTags={selectedTags} selectDeselectTag={selectDeselectTag} /> +
); From 4a60f3ed35edd055cd1862995a28059b6d06b088 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Thu, 18 May 2023 13:01:51 -0500 Subject: [PATCH 41/48] greet user, logout button, sidebar position --- .../MainContainer/MainContainer.jsx | 26 ++++++++++++----- .../MainContainer/MainContainer.module.scss | 28 +++++++++++++++++++ .../containers/Sidebar/Sidebar.module.scss | 2 +- server/routes/authenticationRouter.js | 8 +++--- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 0029963..5e50f20 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -18,19 +18,19 @@ const MainContainer = () => { fetch('http://localhost:3000/authentication/login', { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, // include cookies from cross origin request credentials: 'include', body: JSON.stringify({ username: usernameInputValue, - password: passwordInputValue - }) + password: passwordInputValue, + }), }) .then((result) => result.json()) .then((result) => { console.log('result from login request: ', result); - setLogin(true); + setLogin(result.username); }) .catch((err) => { console.log(err); @@ -53,12 +53,12 @@ const MainContainer = () => { fetch('http://localhost:3000/authentication/signup', { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, body: JSON.stringify({ username: nameValue, - password: passwordValue - }) + password: passwordValue, + }), }) .then((result) => result.json()) .then((result) => { @@ -72,6 +72,18 @@ const MainContainer = () => { return login ? (
+
+ +

+ welcome, {login} +

+
) : haveAccount ? ( diff --git a/client/src/containers/MainContainer/MainContainer.module.scss b/client/src/containers/MainContainer/MainContainer.module.scss index 3028ec1..7ce2c77 100644 --- a/client/src/containers/MainContainer/MainContainer.module.scss +++ b/client/src/containers/MainContainer/MainContainer.module.scss @@ -4,3 +4,31 @@ display: flex; } + +.div { + position: absolute; + top: 1rem; + right: 3rem; +} + +.button { + margin-right: 1rem; + border: 1px solid transparent; + border-radius: 5px; +} + +.h2 { + display: inline-block; + background: -webkit-linear-gradient(#ffffff, #333); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.span { + font-family: 'Courier New', Courier, monospace; + font-size: 48px; + text-transform: uppercase; + background: -webkit-linear-gradient(#ffffff, #e2b2b2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} diff --git a/client/src/containers/Sidebar/Sidebar.module.scss b/client/src/containers/Sidebar/Sidebar.module.scss index f29bab6..d0c1f22 100644 --- a/client/src/containers/Sidebar/Sidebar.module.scss +++ b/client/src/containers/Sidebar/Sidebar.module.scss @@ -38,7 +38,7 @@ width: 300px; height: inherit; position: absolute; - left: -300px; + left: -250px; top: 0; transition: 200ms ease-in; diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index be5249b..d984f58 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -18,17 +18,17 @@ router.post( (req, res) => { console.log(req.user); const token = jwt.sign({ userId: req.user.id }, secret, { - expiresIn: '1d' + expiresIn: '1d', }); res.cookie('token', token, { expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // Expires in 30 days - httpOnly: true + httpOnly: true, }); res.cookie('userId', req.user.id, { expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // Expires in 30 days - httpOnly: true + httpOnly: true, }); - return res.status(202).json({ token }); + return res.status(202).json({ username: req.user.username }); } ); From 5b122e61587d1013ea4e7d06ec917b7aac39610d Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Thu, 18 May 2023 13:09:19 -0500 Subject: [PATCH 42/48] auto login after sign up --- client/src/containers/MainContainer/MainContainer.jsx | 2 +- server/routes/authenticationRouter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 8b6dc2b..948e2fb 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -64,7 +64,7 @@ const MainContainer = () => { .then((result) => { console.log('result from signup request: ', result); setHaveAccount(true); - setLogin(true); + setLogin(result.username); }) .catch((err) => { console.log(err); diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index d984f58..60ead2a 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -9,7 +9,7 @@ require('dotenv').config(); const secret = process.env.JWT_SECRET; router.post('/signup', authenticationController.signUp, (req, res) => { - return res.status(201).json(res.locals.newUser); + return res.status(201).json({ username: res.locals.newUser.username }); }); router.post( From 1fc6c31818f33cf4e303af14da48d3c73c40707e Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Thu, 18 May 2023 13:21:01 -0500 Subject: [PATCH 43/48] added feedback for copy --- .../SnippetDisplay/SnippetDisplay.jsx | 92 ++++++++++--------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index 85d5f40..c15fc78 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -12,6 +12,7 @@ import { langs } from '@uiw/codemirror-extensions-langs'; // importing utils import { Card, Button } from 'react-bootstrap'; +import { set } from 'mongoose'; const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { const defaultDisplayValues = { @@ -19,7 +20,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { language: '', comments: '', storedCode: '', - tags: [] + tags: [], }; const [copied, setCopied] = useState(false); @@ -30,9 +31,16 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { setCurrentDisplay(selectedSnippet); }, [selectedSnippet, getSnippet]); + const handleCopy = () => { + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 2000); + }; + const deleteSnippet = (snippetId) => { fetch('/snippets?' + new URLSearchParams({ snippetId }), { - method: 'DELETE' + method: 'DELETE', }) .then((response) => { if (response.ok) { @@ -44,7 +52,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { return { log: `SnippetDisiplay.deleteSnippet: Error: ${err}`, status: err.status, - message: 'There was an error deleting snippet.' + message: 'There was an error deleting snippet.', }; }); }; @@ -53,7 +61,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { fetch(`/snippets?${new URLSearchParams({ snippetId })}`, { method: 'PUT', headers: { 'Content-type': 'application/json' }, - body: JSON.stringify(currentDisplay) + body: JSON.stringify(currentDisplay), }) .then((response) => { //Are we using this response anywhere? IF not, delete this. @@ -68,66 +76,63 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { return { log: `SnippetDisplay.editSnippet: Error: ${err}`, status: err.status, - message: 'There was an error editing code snippet.' + message: 'There was an error editing code snippet.', }; }); }; const displayContent = (
-
+
-
- Title: +
+ Title: { if (editButtonState) { setCurrentDisplay({ ...currentDisplay, - title: e.target.value + title: e.target.value, }); } - }} - > + }}>
-
- Language: +
+ Language: { if (editButtonState) { setCurrentDisplay({ ...currentDisplay, - language: e.target.value + language: e.target.value, }); } - }} - > + }}>
-
- Comments: +
+ Comments: { if (editButtonState) { setCurrentDisplay({ ...currentDisplay, - comments: e.target.value + comments: e.target.value, }); } - }} - > + }}>
{ if (editButtonState) { setCurrentDisplay({ ...currentDisplay, tags: e }); @@ -141,59 +146,56 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { {\n console.log(\'Hello World!)\n}'} onChange={(e) => { setCurrentDisplay({ ...currentDisplay, storedCode: e }); - }} - > - setCopied(true)} - > - + }}> + + + {copied && copied to clipboard!}
); return ( - + {displayContent}
@@ -204,6 +206,6 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { SnippetDisplay.propTypes = { selectedSnippet: PropTypes.object, - getSnippet: PropTypes.func + getSnippet: PropTypes.func, }; export default SnippetDisplay; From 9dd2d36110239129f5925bd1cf18fe26913dbc6d Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Thu, 18 May 2023 15:19:17 -0400 Subject: [PATCH 44/48] Now with filtering by tags and scrolling list displays --- client/src/containers/Sidebar/Sidebar.jsx | 34 ++++++++++++------- .../containers/Sidebar/Sidebar.module.scss | 22 +++++++++++- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/client/src/containers/Sidebar/Sidebar.jsx b/client/src/containers/Sidebar/Sidebar.jsx index 0791e64..97f8046 100644 --- a/client/src/containers/Sidebar/Sidebar.jsx +++ b/client/src/containers/Sidebar/Sidebar.jsx @@ -91,7 +91,7 @@ const Sidebar = ({ handleLogin }) => { }; const snippetsDisplay = ( - + {/* Animation while app is fetching data from DB */}
{loading && ( @@ -113,9 +113,9 @@ const Sidebar = ({ handleLogin }) => { ); const tagsDisplay = ( - + {/* Animation while app is fetching data from DB */} -
+
{loading && (
{ >
)} - - +
+
+ +
+
+
+ +
+
); diff --git a/client/src/containers/Sidebar/Sidebar.module.scss b/client/src/containers/Sidebar/Sidebar.module.scss index f29bab6..fe7350b 100644 --- a/client/src/containers/Sidebar/Sidebar.module.scss +++ b/client/src/containers/Sidebar/Sidebar.module.scss @@ -9,6 +9,19 @@ // border-color: black; // } +.tagsSnippetsDisplayHolder { + display: flex; + flex-direction: column; + overflow: hidden; + max-height: 75vh; +} + +.tagsSnippetsDisplayBox { + padding: 8px 16px; + overflow-y: auto; + max-height: 50%; +} + .displayTypeSelector { display: flex; justify-content: space-around; @@ -34,9 +47,14 @@ border: none !important; } +.cardBodyContent { + //height: min-content; + overflow: hidden; +} .sidebar { width: 300px; - height: inherit; + //height: inherit; + //max-height: 75vh; position: absolute; left: -300px; top: 0; @@ -69,6 +87,8 @@ .cardBody { padding: 8px 16px; + overflow-y: auto; + max-height: 75vh; } } From 8fcbc2b5a75a09cbc951d5ddfdcfdef166b4d4b2 Mon Sep 17 00:00:00 2001 From: Jimpulse48 <107091480+Jimpulse48@users.noreply.github.com> Date: Thu, 18 May 2023 14:24:23 -0500 Subject: [PATCH 45/48] responds to incorrect un or pw --- client/src/components/userStart/Login.jsx | 51 +++++++++++++------ .../components/userStart/Login.module.scss | 8 +++ .../MainContainer/MainContainer.jsx | 9 +++- .../MainContainer/MainContainer.module.scss | 1 + 4 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 client/src/components/userStart/Login.module.scss diff --git a/client/src/components/userStart/Login.jsx b/client/src/components/userStart/Login.jsx index 19449f6..30d9b2e 100644 --- a/client/src/components/userStart/Login.jsx +++ b/client/src/components/userStart/Login.jsx @@ -1,29 +1,48 @@ -import React, {useState} from 'react'; - - +import React, { useState } from 'react'; +import styles from './Login.module.scss'; // eslint-disable-next-line react/prop-types -const Login = ({handleLogin, handleHaveAccount}) => { - - - +const Login = ({ handleLogin, handleHaveAccount, style, error }) => { return (

USER LOGIN

-
-
-     - + +
+     +
-
-     - +
+     +
- + + {error &&

wrong username or password!

}
- +
); }; diff --git a/client/src/components/userStart/Login.module.scss b/client/src/components/userStart/Login.module.scss new file mode 100644 index 0000000..796d61f --- /dev/null +++ b/client/src/components/userStart/Login.module.scss @@ -0,0 +1,8 @@ +.red { + border: 1.5px solid red; +} + +.p { + margin-top: 8px; + color: red; +} diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 948e2fb..b8817d5 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -7,6 +7,7 @@ import Signup from '../../components/userStart/Signup.jsx'; const MainContainer = () => { const [login, setLogin] = useState(false); const [haveAccount, setHaveAccount] = useState(true); + const [error, setError] = useState(false); const handleLogin = (e) => { e.preventDefault(); @@ -33,6 +34,7 @@ const MainContainer = () => { setLogin(result.username); }) .catch((err) => { + setError(true); console.log(err); }); @@ -89,7 +91,12 @@ const MainContainer = () => {
) : haveAccount ? (
- +
) : (
diff --git a/client/src/containers/MainContainer/MainContainer.module.scss b/client/src/containers/MainContainer/MainContainer.module.scss index 7ce2c77..27f568c 100644 --- a/client/src/containers/MainContainer/MainContainer.module.scss +++ b/client/src/containers/MainContainer/MainContainer.module.scss @@ -32,3 +32,4 @@ -webkit-background-clip: text; -webkit-text-fill-color: transparent; } + From 57366e037efabc6442943af9ae883bdfcafd0070 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Thu, 18 May 2023 15:30:40 -0400 Subject: [PATCH 46/48] Commiting prior to dev merge --- client/src/scss/_global.scss | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/client/src/scss/_global.scss b/client/src/scss/_global.scss index da7d9dc..2f114d4 100644 --- a/client/src/scss/_global.scss +++ b/client/src/scss/_global.scss @@ -1,3 +1,18 @@ +*::-webkit-scrollbar { + color: #062a56; +} +*::-webkit-scrollbar-track { + background: transparentize($accent-bkg, 1); +} + +*::-webkit-scrollbar-corner { + background: transparentize($accent-bkg, 1); +} + +*::-webkit-scrollbar-thumb { + background: $accent-bkg; +} + body { background: url(https://wallpaperaccess.com/full/266479.png) no-repeat center center fixed; From 68b508eed1ba3785fbdd50167d60978323ed98e9 Mon Sep 17 00:00:00 2001 From: Kyle Slugg Date: Thu, 18 May 2023 15:42:29 -0400 Subject: [PATCH 47/48] Enhanced scrollbar styling, plus edit protection for all fields when editing turned off --- .../SnippetDisplay/SnippetDisplay.jsx | 77 +++++++++++-------- client/src/scss/_global.scss | 8 +- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/client/src/components/SnippetDisplay/SnippetDisplay.jsx b/client/src/components/SnippetDisplay/SnippetDisplay.jsx index c15fc78..5c98f9b 100644 --- a/client/src/components/SnippetDisplay/SnippetDisplay.jsx +++ b/client/src/components/SnippetDisplay/SnippetDisplay.jsx @@ -20,7 +20,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { language: '', comments: '', storedCode: '', - tags: [], + tags: [] }; const [copied, setCopied] = useState(false); @@ -40,7 +40,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { const deleteSnippet = (snippetId) => { fetch('/snippets?' + new URLSearchParams({ snippetId }), { - method: 'DELETE', + method: 'DELETE' }) .then((response) => { if (response.ok) { @@ -52,7 +52,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { return { log: `SnippetDisiplay.deleteSnippet: Error: ${err}`, status: err.status, - message: 'There was an error deleting snippet.', + message: 'There was an error deleting snippet.' }; }); }; @@ -61,7 +61,7 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { fetch(`/snippets?${new URLSearchParams({ snippetId })}`, { method: 'PUT', headers: { 'Content-type': 'application/json' }, - body: JSON.stringify(currentDisplay), + body: JSON.stringify(currentDisplay) }) .then((response) => { //Are we using this response anywhere? IF not, delete this. @@ -76,63 +76,69 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { return { log: `SnippetDisplay.editSnippet: Error: ${err}`, status: err.status, - message: 'There was an error editing code snippet.', + message: 'There was an error editing code snippet.' }; }); }; const displayContent = (
-
+
-
- Title: +
+ Title: { if (editButtonState) { setCurrentDisplay({ ...currentDisplay, - title: e.target.value, + title: e.target.value }); } - }}> + }} + >
-
- Language: +
+ Language: { if (editButtonState) { setCurrentDisplay({ ...currentDisplay, - language: e.target.value, + language: e.target.value }); } - }}> + }} + >
-
- Comments: +
+ Comments: { if (editButtonState) { setCurrentDisplay({ ...currentDisplay, - comments: e.target.value, + comments: e.target.value }); } - }}> + }} + >
{ if (editButtonState) { setCurrentDisplay({ ...currentDisplay, tags: e }); @@ -145,15 +151,17 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => {
{\n console.log(\'Hello World!)\n}'} onChange={(e) => { setCurrentDisplay({ ...currentDisplay, storedCode: e }); - }}> + }} + >
@@ -206,6 +217,6 @@ const SnippetDisplay = ({ selectedSnippet, getSnippet }) => { SnippetDisplay.propTypes = { selectedSnippet: PropTypes.object, - getSnippet: PropTypes.func, + getSnippet: PropTypes.func }; export default SnippetDisplay; diff --git a/client/src/scss/_global.scss b/client/src/scss/_global.scss index 2f114d4..67d33e7 100644 --- a/client/src/scss/_global.scss +++ b/client/src/scss/_global.scss @@ -1,16 +1,16 @@ *::-webkit-scrollbar { - color: #062a56; + color: #232024; } *::-webkit-scrollbar-track { - background: transparentize($accent-bkg, 1); + background: transparentize(#232024, 1); } *::-webkit-scrollbar-corner { - background: transparentize($accent-bkg, 1); + background: transparentize(#232024, 1); } *::-webkit-scrollbar-thumb { - background: $accent-bkg; + background: whitesmoke; } body { From 4e0d3cd863b234e139dec6359473235e65ac3070 Mon Sep 17 00:00:00 2001 From: Wan Wang Date: Thu, 18 May 2023 15:58:05 -0400 Subject: [PATCH 48/48] added functionalities for user login and signup screen message when input is not valid and also added the ability to show password strength as user sign up their account --- client/src/components/userStart/Login.jsx | 52 +++++++++++++------ client/src/components/userStart/Signup.jsx | 45 +++++++++++----- .../MainContainer/MainContainer.jsx | 39 ++++++++++---- client/src/scss/_global.scss | 15 ++++-- package.json | 3 +- .../controllers/authenticationController.js | 1 - server/routes/authenticationRouter.js | 3 +- 7 files changed, 113 insertions(+), 45 deletions(-) diff --git a/client/src/components/userStart/Login.jsx b/client/src/components/userStart/Login.jsx index 19449f6..fd9354a 100644 --- a/client/src/components/userStart/Login.jsx +++ b/client/src/components/userStart/Login.jsx @@ -1,29 +1,49 @@ -import React, {useState} from 'react'; - - +import React, { useState } from 'react'; // eslint-disable-next-line react/prop-types -const Login = ({handleLogin, handleHaveAccount}) => { - - - +const Login = ({ handleLogin, handleHaveAccount }) => { return (

USER LOGIN

-
-
-     - + +
+     +
-
-     - +
+     +
- + +

- +
); }; diff --git a/client/src/components/userStart/Signup.jsx b/client/src/components/userStart/Signup.jsx index c34b310..6f7fc05 100644 --- a/client/src/components/userStart/Signup.jsx +++ b/client/src/components/userStart/Signup.jsx @@ -1,21 +1,42 @@ -import React, {useState} from 'react'; +import React, { useState } from 'react'; // eslint-disable-next-line react/prop-types -const Signup = ({handleSigned}) => { +const Signup = ({ handleSigned, strengthMessage, handleStrength }) => { return ( -
+

Create Your Account

-
- - +
+ +
-
- - -
- +
+ + handleStrength(e.target.value)} + type='password' + className='form-control' + id='psw' + /> + {strengthMessage === '' ? null : ( + + {strengthMessage} + + )} +
+ - ); }; diff --git a/client/src/containers/MainContainer/MainContainer.jsx b/client/src/containers/MainContainer/MainContainer.jsx index 0029963..725fcc7 100644 --- a/client/src/containers/MainContainer/MainContainer.jsx +++ b/client/src/containers/MainContainer/MainContainer.jsx @@ -3,10 +3,11 @@ import Sidebar from '../Sidebar/Sidebar.jsx'; import styles from './MainContainer.module.scss'; import Login from '../../components/userStart/Login.jsx'; import Signup from '../../components/userStart/Signup.jsx'; - +import validator from 'validator'; const MainContainer = () => { const [login, setLogin] = useState(false); const [haveAccount, setHaveAccount] = useState(true); + const [strengthMessage, setStrengthMessage] = useState(''); const handleLogin = (e) => { e.preventDefault(); @@ -15,6 +16,13 @@ const MainContainer = () => { const passwordInputValue = document.getElementById('password').value; document.getElementById('password').value = ''; + if(usernameInputValue === '' || passwordInputValue === ''){ + document.querySelector('.errorMessage').innerHTML = ' Username and password are required'; + return false; + }; + + + fetch('http://localhost:3000/authentication/login', { method: 'POST', headers: { @@ -33,15 +41,28 @@ const MainContainer = () => { setLogin(true); }) .catch((err) => { - console.log(err); + document.querySelector('.errorMessage').innerHTML = 'Please verify your user information and try again!'; + return false; }); - - //Bypass login requirement: - //setLogin(true); }; //functino to handle showing the signup page const handleHaveAccount = () => setHaveAccount(false); + //function to handle password strength check + const handleStrength = (input) => { + if(validator.isStrongPassword(input, { + minLength: 8, + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 1 + })){ + setStrengthMessage('Strong Password'); + } else { + setStrengthMessage('Not A Strong Password'); + } + }; + //function to handle sign-up if username was not already taken const handleSigned = (e) => { e.preventDefault(); @@ -71,16 +92,16 @@ const MainContainer = () => { }; return login ? ( -
+
) : haveAccount ? ( -
- +
+
) : (
- +
); }; diff --git a/client/src/scss/_global.scss b/client/src/scss/_global.scss index da7d9dc..e57c1cc 100644 --- a/client/src/scss/_global.scss +++ b/client/src/scss/_global.scss @@ -30,12 +30,19 @@ body { align-items: center; } +.errorMessage { + margin-top: 240px; + position: fixed; + color: red; + font-size: 17px; +} .form { + display: flex; flex-direction: column; - justify-content: space-around; - align-items: flex-end; + align-items: flex-start; margin-top: 10px; + height: 200px; } #secondDiv { @@ -62,11 +69,11 @@ body { background-color: rgb(18, 64, 84); opacity: 0.9; background: -webkit-linear-gradient(#eee, #333); - height: 260px; + height: 400px; color: black; font-size: 16px; font-weight: bold; - width: 500px; + width: 600px; border: 2px solid rgb(42, 41, 41); display: flex; justify-content: center; diff --git a/package.json b/package.json index 55ef9e9..02bfcd2 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "react-dom": "^18.2.0", "react-modal": "^3.16.1", "react-tag-input": "^6.8.1", - "sass": "^1.62.1" + "sass": "^1.62.1", + "validator": "^13.9.0" }, "devDependencies": { "@babel/core": "^7.21.8", diff --git a/server/controllers/authenticationController.js b/server/controllers/authenticationController.js index 0026a3b..11a2c43 100644 --- a/server/controllers/authenticationController.js +++ b/server/controllers/authenticationController.js @@ -31,5 +31,4 @@ authenticationController.signUp = async (req, res, next) => { } }; - module.exports = authenticationController; diff --git a/server/routes/authenticationRouter.js b/server/routes/authenticationRouter.js index be5249b..a7da51f 100644 --- a/server/routes/authenticationRouter.js +++ b/server/routes/authenticationRouter.js @@ -12,8 +12,7 @@ router.post('/signup', authenticationController.signUp, (req, res) => { return res.status(201).json(res.locals.newUser); }); -router.post( - '/login', +router.post('/login', passport.authenticate('local', { session: false }), (req, res) => { console.log(req.user);