├── .babelrc ├── .eslintrc.json ├── .gitignore ├── README.md ├── __tests__ ├── db.js ├── react.js └── supertest.js ├── client ├── App.jsx ├── assets │ ├── GitGoodDash.png │ └── GitGoodLogo.png ├── components │ ├── Card.jsx │ ├── ExpandedCard.css │ ├── ExpandedCard.jsx │ ├── GithubLogin.jsx │ └── Topic.jsx ├── containers │ ├── CardContainer.jsx │ ├── Dashboard.jsx │ └── Nav.jsx ├── index.html ├── index.js └── stylesheets │ └── styles.scss ├── package.json ├── server ├── controllers │ ├── OAuthController.js │ ├── sessionController.js │ ├── subtopicController.js │ ├── topicController.js │ └── userController.js ├── db │ └── db.js ├── routes │ ├── api.js │ └── github.js └── server.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/test", "**/__tests__"], 4 | "env": { 5 | "node": true, 6 | "browser": true, 7 | "es2021": true 8 | }, 9 | "plugins": ["react"], 10 | "extends": ["eslint:recommended", "plugin:react/recommended"], 11 | "parserOptions": { 12 | "sourceType": "module", 13 | "ecmaFeatures": { 14 | "jsx": true 15 | } 16 | }, 17 | "rules": { 18 | "indent": ["warn", 2], 19 | "no-unused-vars": ["off", { "vars": "local" }], 20 | "no-case-declarations": "off", 21 | "prefer-const": "warn", 22 | "quotes": ["warn", "single"], 23 | "react/prop-types": "off", 24 | "semi": ["warn", "always"], 25 | "space-infix-ops": "warn" 26 | }, 27 | "settings": { 28 | "react": { "version": "detect"} 29 | } 30 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .env 4 | dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 | # GitGood 7 | 8 | An open source software engineering study tool that allows users to create topic categories and save useful resources as flashcards. 9 | 10 | ## Want to Contribute? 11 | 12 | 1. Clone the repo and make a new branch 13 | 1. Add a feature, fix a bug, or refactor some code :) 14 | 1. Make sure to lint your code! 15 | 1. Write/update tests for the changes you made, if necessary. 16 | 1. Run unit & integration tests and make sure all tests pass: npm test. 17 | 1. Open a Pull Request with a comprehensive description of changes to the dev branch 18 | 19 | ## Technologies 20 | 21 | - React 22 | - Node 23 | - Express 24 | - bCrypt 25 | - JSON Web Tokens 26 | - PostgreSQL 27 | - MaterialUI 28 | - Jest 29 | - React Testing Library 30 | - Webpack 31 | 32 | ## Screenshots 33 | ![Screenshot](client/assets/GitGoodDash.png) 34 | -------------------------------------------------------------------------------- /__tests__/db.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const db = require('../server/db/db'); 4 | const { query } = require('express'); 5 | import regeneratorRuntime from 'regenerator-runtime'; 6 | 7 | /** 8 | * Like many testing frameworks, in Jest we use the "describe" function to 9 | * separate our tests into sections. They make your test outputs readable. 10 | * 11 | * You can place "beforeAll", "beforeEach", "afterAll", and "afterEach" 12 | * functions inside of "describe" blocks and they will only run for tests 13 | * inside that describe block. You can even nest describes within describes! 14 | */ 15 | describe('db unit tests', () => { 16 | /** 17 | * Jest runs the "beforeAll" function once, before any tests are executed. 18 | * Here, we write to the file and then reset our database model. Then, we 19 | * invoke the "done" callback to tell Jest our async operations have 20 | * completed. This way, the tests won't start until the "database" has been 21 | * reset to an empty Array! 22 | */ 23 | 24 | beforeAll(done => { 25 | done() 26 | }) 27 | 28 | // afterAll(done => { 29 | // // Closing the DB connection allows Jest to exit successfully. 30 | // db.connection.close() 31 | // done() 32 | // }) 33 | 34 | describe('#sync', () => { 35 | it('db test is working', () => { 36 | const sqlQuery = `SELECT * FROM Users`; 37 | 38 | db.query(sqlQuery,[],(err, result) => { 39 | // console.log(result) 40 | return expect(typeof result).toEqual('object'); 41 | }); 42 | 43 | }); 44 | 45 | // TODO: Finish unit testing the sync function 46 | 47 | // it('overwrites previously existing markets', () => { 48 | // const sqlQuery = `SELECT * FROM Users`; 49 | 50 | // // console.log(process.env.PG_URI); 51 | // return db.query(sqlQuery) 52 | // .then((data) => expect(typeof data).toBe('object')) 53 | // .finally((done)=>done()); 54 | // }); 55 | 56 | it('db test', () => { 57 | 58 | }); 59 | 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /__tests__/react.js: -------------------------------------------------------------------------------- 1 | import React from 'React'; 2 | import userEvent from '@testing-library/user-event'; 3 | import { render, screen, waitFor, fireEvent } from '@testing-library/react'; 4 | import "@testing-library/jest-dom" 5 | import regeneratorRuntime from 'regenerator-runtime'; 6 | 7 | import App from '../client/App'; 8 | 9 | describe('Unit testing React components', () => { 10 | describe('App', () => { 11 | let app; 12 | 13 | beforeAll(() => { 14 | app = render(); 15 | // console.log(text); 16 | }); 17 | 18 | test('App has a h1 with text "Test"', () => { 19 | // console.log((text.getByText('Mega:'))); 20 | expect(app.getByRole('heading')).toHaveTextContent('Test'); 21 | // expect(text.getByText('Mega:')).toHaveStyle('font-weight: bold'); 22 | }); 23 | }); 24 | 25 | describe('App2', () => { 26 | let app; 27 | 28 | beforeEach(async () => { 29 | let app = await render(); 30 | // console.log(text); 31 | }); 32 | 33 | test('App has a h1 with text "Test"', () => { 34 | // console.log((text.getByText('Mega:'))); 35 | expect(screen.getByRole('heading')).toHaveTextContent('Test'); 36 | // expect(text.getByText('Mega:')).toHaveStyle('font-weight: bold'); 37 | }); 38 | 39 | }) 40 | 41 | }); -------------------------------------------------------------------------------- /__tests__/supertest.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | 3 | const server = 'http://localhost:3000'; 4 | 5 | /** 6 | * Read the docs! https://www.npmjs.com/package/supertest 7 | */ 8 | describe('Route integration', () => { 9 | describe('/', () => { 10 | describe('GET', () => { 11 | // Note that we return the evaluation of `request` here! It evaluates to 12 | // a promise, so Jest knows not to say this test passes until that 13 | // promise resolves. See https://jestjs.io/docs/en/asynchronous 14 | it('responds with 200 status and text/html content type', () => request(server) 15 | .get('/') 16 | .expect('Content-Type', /text\/html/) 17 | .expect(200)); 18 | }); 19 | 20 | }); 21 | 22 | describe('/github', () => { 23 | describe('/auth', () => { 24 | 25 | it('GET: responds with a redirect to github sign in', () => request(server) 26 | .get('/github/auth') 27 | .expect('Location', 'https://github.com/login/oauth/authorize?scope=user,repo&redirect_uri=http://localhost:3000/github/callback&client_id=27337f49cf34f7d2d21b') 28 | .expect(302)); 29 | }); 30 | }); 31 | 32 | describe('/api', () => { 33 | describe('/topic', () => { 34 | describe('GET', () => { 35 | it('responds with json', () => { 36 | return request(server) 37 | .get('/api/topic') 38 | .set('Cookie', ['ssid=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFyaXNob2hhbSIsImlhdCI6MTY0NTM3OTIzNn0.B4yB2wVJL7quFTHATAUPcOD3Gpb_t5hn-R-XvGnk6YY']) 39 | .expect('Content-Type', /application\/json/) 40 | .expect(200) 41 | }); 42 | 43 | it('responds with an array of objects', () => { 44 | return request(server) 45 | .get('/api/topic') 46 | .set('Cookie', ['ssid=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFyaXNob2hhbSIsImlhdCI6MTY0NTM3OTIzNn0.B4yB2wVJL7quFTHATAUPcOD3Gpb_t5hn-R-XvGnk6YY']) 47 | .then((response) => { 48 | expect(Array.isArray(response.body)).toBe(true); 49 | expect(typeof request.body[0]).toBe('object'); 50 | }) 51 | }) 52 | }); 53 | 54 | describe('POST', () => { 55 | it('Creating a new topic responds with an id number', () => { 56 | const body = {name: 'topic_test'} 57 | return request(server) 58 | .put('/markets') 59 | .set('Cookie', ['ssid=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFyaXNob2hhbSIsImlhdCI6MTY0NTM3OTIzNn0.B4yB2wVJL7quFTHATAUPcOD3Gpb_t5hn-R-XvGnk6YY']) 60 | .send(body) 61 | .then(response => { 62 | expect(typeof response.body).toEqual('number'); 63 | }); 64 | }) 65 | }) 66 | }); 67 | 68 | }); 69 | 70 | }); -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CssBaseline from '@mui/material/CssBaseline'; 3 | import { createTheme, ThemeProvider, styled } from '@mui/material/styles'; 4 | import Dashboard from './containers/Dashboard.jsx'; 5 | 6 | function App() { 7 | return ( 8 |
9 | 10 | {/*

Test

*/} 11 | 12 |
13 | ); 14 | } 15 | 16 | export default App; -------------------------------------------------------------------------------- /client/assets/GitGoodDash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitGoodOrg/GitGood/3e01cd70b11d1cabaad9601af2b7d58462d1d223/client/assets/GitGoodDash.png -------------------------------------------------------------------------------- /client/assets/GitGoodLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitGoodOrg/GitGood/3e01cd70b11d1cabaad9601af2b7d58462d1d223/client/assets/GitGoodLogo.png -------------------------------------------------------------------------------- /client/components/Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Button } from '@mui/material'; 3 | 4 | function Card(props) { 5 | function handleClick () { 6 | props.setTrigger(true); 7 | props.setEmojiText(props.cards.emoji); 8 | props.setCardText(props.cards.title); 9 | props.setBodyText(props.cards.text); 10 | props.setCurrentCardId(props.cards._id); 11 | } 12 | 13 | return( 14 |
15 |

{ props.cards.title }

16 |

{ props.cards.emoji + ' ' + props.cards.text}

17 | 18 |
19 | ); 20 | } 21 | 22 | export default Card; -------------------------------------------------------------------------------- /client/components/ExpandedCard.css: -------------------------------------------------------------------------------- 1 | /* .ExpandedCard { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100vh; 7 | background-color: rgba(0, 0, 0, 0.2); 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | } 12 | 13 | .ExpandedCard-inner { 14 | position: relative; 15 | padding: 32px; 16 | width: 100%; 17 | max-width: 640px; 18 | background-color: white 19 | } 20 | 21 | .ExpandedCard-inner .close-btn { 22 | position: absolute; 23 | top: 16px; 24 | right: 16px; 25 | } */ -------------------------------------------------------------------------------- /client/components/ExpandedCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | // import './ExpandedCard.css'; 3 | import { TextField } from '@mui/material'; 4 | import { Button } from '@mui/material'; 5 | 6 | function ExpandedCard(props) { 7 | 8 | const handleSubmit = (e) => { 9 | e.preventDefault(); 10 | props.setTrigger(false); 11 | if(props.currentCardId) { 12 | props.updateCard(props.currentCardId); 13 | } 14 | else { 15 | props.addCard(); 16 | } 17 | }; 18 | 19 | return (props.trigger) ? ( 20 |
21 |
22 | 23 |
24 | props.emojiTextEntry(e)} value={props.emojiText}/> props.cardTextEntry(e)} value={props.cardText}/> 25 |

26 |