├── test ├── cssStub.js └── setup.js ├── src ├── redux │ ├── actions │ │ ├── types.js │ │ ├── index.js │ │ └── testAction.js │ ├── reducers │ │ ├── index.js │ │ └── testReducer.js │ └── store.js ├── assets │ ├── banner-image.jpg │ ├── katathon01.jpeg │ └── banner-image-gs.jpg ├── index.js ├── index.html ├── common │ ├── Footer │ │ └── Footer.js │ ├── Button │ │ └── Button.js │ ├── Header │ │ └── Header.js │ └── Page │ │ └── Page.js ├── styles │ └── index.scss ├── containers │ ├── HomeContainer │ │ └── HomeContainer.js │ ├── App │ │ └── App.js │ ├── TimerContainer │ │ └── TimerContainer.js │ └── KatathonContainer │ │ └── KatathonContainer.js ├── views │ ├── Katathon │ │ └── Katathon.js │ ├── Home │ │ └── Home.js │ └── About │ │ └── About.js └── components │ ├── KataList │ └── KataList.js │ ├── Timer │ └── Timer.js │ └── LeaderBoard │ └── Leaderboard.js ├── .gitignore ├── favicon.ico ├── server ├── index.js ├── config │ └── index.js ├── helpers │ └── index.js ├── jobs │ └── index.js ├── routes │ └── index.js ├── models │ └── katathonModel.js ├── server.js ├── services │ └── index.js └── controllers │ └── index.js ├── .babelrc ├── webpack.config.js ├── webpack.prod.config.js ├── package.json ├── .eslintrc └── README.md /test/cssStub.js: -------------------------------------------------------------------------------- 1 | module.exports = {} -------------------------------------------------------------------------------- /src/redux/actions/types.js: -------------------------------------------------------------------------------- 1 | export const SET_TEST = 'SET_TEST' 2 | -------------------------------------------------------------------------------- /src/redux/actions/index.js: -------------------------------------------------------------------------------- 1 | export { getTestData } from './testAction' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea/ 4 | .env 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiacomoSorbi/Katathon/HEAD/favicon.ico -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | require('babel-register') 3 | 4 | require('./server') 5 | -------------------------------------------------------------------------------- /src/assets/banner-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiacomoSorbi/Katathon/HEAD/src/assets/banner-image.jpg -------------------------------------------------------------------------------- /src/assets/katathon01.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiacomoSorbi/Katathon/HEAD/src/assets/katathon01.jpeg -------------------------------------------------------------------------------- /src/assets/banner-image-gs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiacomoSorbi/Katathon/HEAD/src/assets/banner-image-gs.jpg -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme' 2 | import Adapter from 'enzyme-adapter-react-16' 3 | 4 | configure({ adapter: new Adapter() }) -------------------------------------------------------------------------------- /src/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import testReducer from './testReducer' 3 | 4 | export default combineReducers({ 5 | testReducer 6 | }) 7 | -------------------------------------------------------------------------------- /server/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getDBConnection() { 3 | return 'mongodb://' + process.env.DB_USER + ':' + process.env.DB_PASS + process.env.DB_ADDRESS 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './containers/App/App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Katathon 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /src/common/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Footer = () => { 4 | return ( 5 | 8 | ) 9 | } 10 | 11 | export default Footer 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ], 6 | "plugins": [ 7 | "react-hot-loader/babel", 8 | "transform-class-properties", 9 | "transform-object-rest-spread", 10 | "transform-runtime" 11 | ] 12 | } -------------------------------------------------------------------------------- /src/common/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Button = ({ children, className, handleClick }) => ( 4 | 7 | ) 8 | 9 | export default Button 10 | -------------------------------------------------------------------------------- /src/common/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Header = () => { 4 | return ( 5 |
6 | 9 |
10 | ) 11 | } 12 | 13 | export default Header 14 | -------------------------------------------------------------------------------- /server/helpers/index.js: -------------------------------------------------------------------------------- 1 | export const getNextEvent = events => { 2 | const newEvents = events.filter(event => event.date >= Date.now().valueOf()) 3 | return newEvents.concat().sort((a, b) => a.date < b.date ? -1 : 1)[0] 4 | } 5 | 6 | export const dateToTimestamp = date => new Date(date).valueOf() 7 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Overpass+Mono'); 2 | @import url('https://fonts.googleapis.com/css?family=Muli:200,300'); 3 | 4 | html { 5 | font-family: 'Muli', sans-serif; 6 | } 7 | 8 | body { 9 | padding: 0; 10 | margin: 0; 11 | } 12 | 13 | h1, h2 { 14 | font-weight: 200; 15 | } -------------------------------------------------------------------------------- /src/redux/reducers/testReducer.js: -------------------------------------------------------------------------------- 1 | import { SET_TEST } from '../actions/types' 2 | 3 | export default function (state = {}, action) { 4 | switch(action.type) { 5 | case SET_TEST: 6 | return { 7 | ...state, 8 | testData: action.payload.title 9 | } 10 | default: 11 | return state 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/common/Page/Page.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Footer from '../Footer/Footer' 4 | import Header from '../Header/Header' 5 | 6 | import '../../styles/index.scss' 7 | 8 | const Page = (props) => { 9 | return ( 10 |
11 |
12 |
13 | {props.children} 14 |
15 |
16 |
17 | ) 18 | } 19 | 20 | export default Page 21 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import rootReducer from './reducers' 4 | const initialState = {} 5 | const middleware = [thunk] 6 | 7 | const store = createStore( 8 | rootReducer, 9 | initialState, 10 | compose( 11 | applyMiddleware(...middleware), 12 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 13 | ) 14 | ) 15 | 16 | export default store 17 | -------------------------------------------------------------------------------- /src/containers/HomeContainer/HomeContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import Home from '../../views/Home/Home' 4 | 5 | import * as actions from '../../redux/actions/index' 6 | 7 | const mapStateToProps = state => ({ 8 | test: state.testReducer.testData 9 | }) 10 | 11 | const mapDispatchToProps = dispatch => ({ 12 | onLoadTestData: () => dispatch(actions.getTestData()) 13 | }) 14 | 15 | export default connect(mapStateToProps, mapDispatchToProps)(Home) 16 | -------------------------------------------------------------------------------- /src/views/Katathon/Katathon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Page from '../../common/Page/Page' 4 | import Leaderboard from '../../components/LeaderBoard/Leaderboard' 5 | import KataList from '../../components/KataList/KataList' 6 | 7 | const Katathon = (props) => ( 8 | 9 |

Welcome to today's Katathon!

10 | 11 | 12 |
13 | ) 14 | 15 | export default Katathon 16 | -------------------------------------------------------------------------------- /server/jobs/index.js: -------------------------------------------------------------------------------- 1 | import { scheduleJob } from 'node-schedule' 2 | 3 | import { updateAllScores } from '../services/index' 4 | 5 | // Ensures job only runs during Katathon 6 | export const newKatathonJob = (timestamp, katathonId) => { 7 | const start = new Date(timestamp) 8 | const end = new Date(timestamp) 9 | end.setHours(end.getHours() + 24) 10 | scheduleJob( 11 | { 12 | start, 13 | end, 14 | rule: '*/10 * * * *' 15 | }, 16 | () => { 17 | updateAllScores(katathonId) 18 | } 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/redux/actions/testAction.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | import { SET_TEST } from './types' 4 | 5 | const testDataUrl = 'https://jsonplaceholder.typicode.com/posts/1' 6 | 7 | export const setTestData = (testData) => { 8 | return { 9 | type: SET_TEST, 10 | payload: testData 11 | } 12 | } 13 | 14 | export const getTestData = () => { 15 | return dispatch => { 16 | axios.get(testDataUrl) 17 | .then(response => { 18 | dispatch(setTestData(response.data)) 19 | }) 20 | .catch(error => { 21 | dispatch(error) 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | const router = express.Router() 3 | 4 | 5 | import * as controller from '../controllers' 6 | 7 | router.post('/events', controller.newKatathon) 8 | 9 | router.get('/events', controller.listKatathons) 10 | 11 | router.put('/events/:katathonId', controller.updateKatathon) 12 | 13 | router.post('/addkata/:katathonId', controller.addKata) 14 | 15 | router.put('/:katathonId/:_id', controller.updateKata) 16 | 17 | router.get('/', controller.nextKatathon) 18 | 19 | router.post('/adduser/:katathonId', controller.addUser) 20 | 21 | router.get('/:katathonId', controller.getLeaderBoard) 22 | 23 | export default router 24 | -------------------------------------------------------------------------------- /src/components/KataList/KataList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const KataList = (props) => ( 4 |
5 |

{props.title}

6 | {props.data.map((item, index) => ( 7 |
8 |
9 | {index + 1} 10 |
11 |
12 | {item.title} 13 |
14 |
15 | {item.score} 16 |
17 |
))} 18 |
19 | ) 20 | 21 | export default KataList 22 | -------------------------------------------------------------------------------- /src/components/Timer/Timer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Link } from 'react-router-dom' 4 | 5 | import Button from '../../common/Button/Button' 6 | 7 | const Timer = (props) => ( 8 |
9 |

Hello Coders!

10 |

Time until the next Katathon:

11 |

{props.days} days, {props.hours}:{props.minutes}:{props.seconds}

12 | 17 |
18 | ) 19 | 20 | export default Timer 21 | -------------------------------------------------------------------------------- /src/components/LeaderBoard/Leaderboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Leaderboard = (props) => ( 4 |
5 |

{props.title}

6 | {props.data.map((item, index) => { 7 | return (
8 |
9 | {index + 1} 10 |
11 |
12 | {item.username} 13 |
14 |
15 | {item.score} 16 |
17 |
) 18 | })} 19 |
20 | ) 21 | 22 | export default Leaderboard 23 | -------------------------------------------------------------------------------- /src/containers/App/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, BrowserRouter, Switch } from 'react-router-dom' 3 | import { Provider } from 'react-redux' 4 | import { hot } from 'react-hot-loader' 5 | import store from '../../redux/store' 6 | 7 | import Home from '../HomeContainer/HomeContainer' 8 | import About from '../../views/About/About' 9 | import KatathonData from '../KatathonContainer/KatathonContainer' 10 | 11 | import '../../styles/index.scss' 12 | 13 | const App = () => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default hot(module)(App) 26 | -------------------------------------------------------------------------------- /src/views/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | 5 | import Page from '../../common/Page/Page' 6 | import Timer from '../../containers/TimerContainer/TimerContainer' 7 | import Button from '../../common/Button/Button' 8 | 9 | export default class Home extends Component { 10 | componentDidMount() { 11 | this.props.onLoadTestData() 12 | } 13 | 14 | render() { 15 | return ( 16 | 17 | 18 |
19 |

{this.props.test}

20 |

At katathon.org we are all software developers who seek constant learning. Our primary aim is to help good developers become awesome developers, while still offering a great platform into the world of software development for the aspiring coder.

21 | 22 |
23 |
24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/models/katathonModel.js: -------------------------------------------------------------------------------- 1 | 2 | import mongoose, { Schema } from 'mongoose' 3 | 4 | const KatathonSchema = new Schema({ 5 | date: { 6 | type: String, 7 | required: true 8 | }, 9 | katas: [ 10 | { 11 | kataId: { 12 | type: String 13 | }, 14 | name: { 15 | type: String, 16 | required: true 17 | }, 18 | slug: { 19 | type: String, 20 | required: true 21 | }, 22 | link: { 23 | type: String, 24 | required: true 25 | }, 26 | score: { 27 | type: Number, 28 | required: true 29 | } 30 | } 31 | ], 32 | users: [ 33 | { 34 | userId: { 35 | type: String 36 | }, 37 | userName: { 38 | type: String 39 | }, 40 | userScore: { 41 | type: Number, 42 | default: 0 43 | } 44 | } 45 | ], 46 | completed: { 47 | type: Boolean, 48 | default: false 49 | }, 50 | date_created: { 51 | type: Date, 52 | default: Date.now 53 | } 54 | }) 55 | 56 | const Katathon = mongoose.model('Katathon', KatathonSchema) 57 | 58 | export default Katathon 59 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import webpack from 'webpack' 3 | import webpackDevMiddleware from 'webpack-dev-middleware' 4 | import mongoose from 'mongoose' 5 | import bodyParser from 'body-parser' 6 | 7 | import config from '../webpack.config.js' 8 | 9 | import dbConfig from './config' 10 | 11 | import routes from './routes/index' 12 | 13 | const compiler = webpack(config) 14 | const app = express() 15 | 16 | app.use(webpackDevMiddleware(compiler, { 17 | publicPath: config.output.publicPath 18 | })) 19 | 20 | app.use(require('webpack-hot-middleware')(compiler)) 21 | 22 | compiler.plugin('done', () => { 23 | Object.keys(require.cache).forEach((id) => { 24 | if (!/[\/\\]node_modules[\/\\]/.test(id)) { 25 | delete require.cache[id] 26 | } 27 | }) 28 | }) 29 | 30 | app.use(bodyParser.urlencoded({ extended: false })) 31 | app.use(bodyParser.json()) 32 | 33 | mongoose.connect(dbConfig.getDBConnection()) 34 | mongoose.set('debug', true) 35 | 36 | // After allllll that above middleware, we finally handle our own routes! 37 | app.use('/api/katathon', routes) 38 | 39 | app.listen(3000, (err) => { 40 | if (err) { 41 | // console.error(err) 42 | } 43 | // console.log('The src is listening on port 3000') 44 | }) 45 | -------------------------------------------------------------------------------- /src/containers/TimerContainer/TimerContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import Timer from '../../components/Timer/Timer' 4 | 5 | export default class TimerContainer extends Component { 6 | nextDate = new Date('2018-03-13 22:10:00') 7 | 8 | state = { 9 | timeLeft: (this.nextDate - Date.now() > 0) ? this.nextDate - Date.now() : 0 10 | } 11 | 12 | static stringifyTime = (t) => { 13 | return (t < 10) ? '0' + t : t 14 | } 15 | 16 | componentDidMount() { 17 | this.timer = (this.state.timeLeft > 0) ? setInterval(this.tick, 1000) : null 18 | } 19 | 20 | componentWillUpdate() { 21 | if (this.state.timeLeft <= 1000) { 22 | clearInterval(this.timer) 23 | } 24 | } 25 | 26 | componentWillUnmount() { 27 | clearInterval(this.timer) 28 | } 29 | 30 | tick = () => { 31 | this.setState({ timeLeft: Math.floor((this.nextDate - Date.now()) / 1000) * 1000 }) 32 | } 33 | 34 | render() { 35 | const days = Math.floor(this.state.timeLeft / 86400000) 36 | const hours = Math.floor(this.state.timeLeft % 86400000 / 3600000) 37 | const minutes = Math.floor(this.state.timeLeft % 3600000 / 60000) 38 | const seconds = Math.floor(this.state.timeLeft % 60000 / 1000) 39 | 40 | return ( 41 | 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/views/About/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | import Page from '../../common/Page/Page' 5 | import Button from '../../common/Button/Button' 6 | 7 | const About = () => ( 8 | 9 |

More Info

10 | 11 | 14 | 15 |

At katathon.org we are all software developers who seek constant learning. Our primary aim is to help good developers become awesome developers, while still offering a great platform into the world of software development for the aspiring coder.

16 |

We do this using coding challenge platforms such as codewars.com through meetups organised on meetup.com. To join us, start by [joining one of our meetups].

17 |

Why do we focus on coding challenges over project-based learning?

18 |

First of all, there are plenty of platforms and meetups offering project-based learning and these should not be overlooked by new developers wishing to build a great portfolio. They are a great place to start, but coding challenges are a learning accelerant. Today’s software developers are required to be extremely adaptable problem-solvers. Coding challenges help developers to explore and improve their knowledge of programming concepts outside of a project environment and contribute to a deeper knowledge of individual languages and the components in software design patterns. The abstract problems presented by coding challenges also allow programmers to develop better problem solving strategies, which can help them not only with programming itself, but in developing more effective learning strategies for the huge array of tools used in modern software projects.

19 |

Coding challenges are a great supplement to project-based learning and, here at katathon.org, we believe their value should not be overlooked.

20 |
21 | ) 22 | 23 | export default About 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | 6 | module.exports = { 7 | mode: 'development', 8 | entry: ['webpack-hot-middleware/client', './src/index.js'], 9 | devtool: 'source-map', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx?$/, 14 | exclude: /node_modules/, 15 | use: [ 16 | { 17 | loader: 'babel-loader' 18 | }, 19 | { 20 | loader: 'eslint-loader' 21 | } 22 | ] 23 | }, 24 | { 25 | test: /\.s?css$/, 26 | exclude: /node_modules/, 27 | use: [ 28 | { 29 | loader: 'style-loader' 30 | }, 31 | { 32 | loader: 'css-loader', 33 | options: { 34 | sourcemaps: true 35 | } 36 | }, 37 | { 38 | loader: 'sass-loader', 39 | options: { 40 | sourcemaps: true 41 | } 42 | } 43 | ] 44 | }, 45 | { 46 | test: /\.(jpe?g|png|gif|svg|mp4)$/i, 47 | loader: 'file-loader', 48 | options: { 49 | name: 'assets/[path][name].[ext]', 50 | context: './src/assets' 51 | } 52 | } 53 | ] 54 | }, 55 | resolve: { 56 | alias: { 57 | app: path.resolve(__dirname, 'src') 58 | }, 59 | extensions: ['.js', '.json', '.jsx'] 60 | }, 61 | plugins: [ 62 | new CleanWebpackPlugin(['dist']), 63 | new HtmlWebpackPlugin({ 64 | title: 'Output Management', 65 | template: __dirname + '/src/index.html', 66 | filename: 'index.html', 67 | inject: 'body' 68 | }), 69 | new webpack.HotModuleReplacementPlugin(), 70 | new webpack.NoEmitOnErrorsPlugin() 71 | ], 72 | output: { 73 | filename: 'bundle.js', 74 | path: path.resolve(__dirname, 'dist'), 75 | publicPath: '/' 76 | } 77 | }; -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | const NODE_ENV = process.env.NODE_ENV || 'development' 6 | 7 | module.exports = { 8 | mode: 'production', 9 | entry: ['webpack-hot-middleware/client', './src/index.js'], 10 | devtool: 'source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx?$/, 15 | exclude: /node_modules/, 16 | use: [ 17 | { 18 | loader: 'babel-loader' 19 | }, 20 | { 21 | loader: 'eslint-loader' 22 | } 23 | ] 24 | }, 25 | { 26 | test: /\.s?css$/, 27 | exclude: /node_modules/, 28 | use: [ 29 | { 30 | loader: 'style-loader' 31 | }, 32 | { 33 | loader: 'css-loader', 34 | options: { 35 | sourcemaps: true 36 | } 37 | }, 38 | { 39 | loader: 'sass-loader', 40 | options: { 41 | sourcemaps: true 42 | } 43 | } 44 | ] 45 | }, 46 | { 47 | test: /\.(jpe?g|png|gif|svg|mp4)$/i, 48 | loader: 'file-loader', 49 | options: { 50 | name: 'assets/[path][name].[hash].[ext]', 51 | context: './src/assets' 52 | } 53 | } 54 | ] 55 | }, 56 | resolve: { 57 | alias: { 58 | app: path.resolve(__dirname, 'src') 59 | }, 60 | extensions: ['.js', '.json', '.jsx'] 61 | }, 62 | plugins: [ 63 | new CleanWebpackPlugin(['dist']), 64 | new HtmlWebpackPlugin({ 65 | title: 'Output Management', 66 | template: __dirname + '/src/index.html', 67 | filename: 'index.html', 68 | inject: 'body' 69 | }), 70 | new webpack.DefinePlugin({ 71 | NODE_ENV: JSON.stringify(NODE_ENV) 72 | }) 73 | ], 74 | output: { 75 | filename: 'bundle.[hash].js', 76 | path: path.resolve(__dirname, 'dist'), 77 | publicPath: '/' 78 | } 79 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "katathon", 3 | "version": "1.0.0", 4 | "description": "a website for katathon participants", 5 | "main": "app.js", 6 | "scripts": { 7 | "build": "webpack --progress --color --config webpack.prod.config.js", 8 | "dev": "nodemon server/index.js", 9 | "test": "jest", 10 | "test:watch": "jest --watch", 11 | "precommit": "eslint src server --ext=jsx --ext=js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/GiacomoSorbi/Katathon.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/GiacomoSorbi/Katathon/issues" 21 | }, 22 | "homepage": "https://github.com/GiacomoSorbi/Katathon#readme", 23 | "jest": { 24 | "setupTestFrameworkScriptFile": "./test/setup.js", 25 | "testPathIgnorePatterns": [ 26 | "/node_modules/" 27 | ], 28 | "moduleFileExtensions": [ 29 | "js", 30 | "jsx" 31 | ], 32 | "moduleNameMapper": { 33 | "^.+\\.(css|scss|png)$": "/test/cssStub.js", 34 | "app/(.*)$": "/src/$1" 35 | } 36 | }, 37 | "dependencies": { 38 | "axios": "^0.18.0", 39 | "body-parser": "^1.18.3", 40 | "dotenv": "^6.0.0", 41 | "express": "^4.16.3", 42 | "mongoose": "^5.2.17", 43 | "node-schedule": "^1.3.0", 44 | "react": "^16.5.2", 45 | "react-dom": "^16.5.2", 46 | "react-redux": "^5.0.7", 47 | "react-router": "^4.3.1", 48 | "react-router-dom": "^4.3.1", 49 | "redux": "^4.0.0", 50 | "redux-thunk": "^2.3.0" 51 | }, 52 | "devDependencies": { 53 | "babel-core": "^6.26.3", 54 | "babel-eslint": "^9.0.0", 55 | "babel-loader": "^7.1.5", 56 | "babel-plugin-transform-class-properties": "^6.24.1", 57 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 58 | "babel-plugin-transform-runtime": "^6.23.0", 59 | "babel-preset-env": "^1.7.0", 60 | "babel-preset-react": "^6.24.1", 61 | "babel-register": "^6.26.0", 62 | "clean-webpack-plugin": "^0.1.19", 63 | "css-loader": "^1.0.0", 64 | "enzyme": "^3.6.0", 65 | "enzyme-adapter-react-16": "^1.5.0", 66 | "eslint": "^5.6.0", 67 | "eslint-config-airbnb-base": "^13.1.0", 68 | "eslint-loader": "^2.1.1", 69 | "eslint-plugin-babel": "^5.2.0", 70 | "eslint-plugin-import": "^2.14.0", 71 | "eslint-plugin-react": "^7.11.1", 72 | "file-loader": "^2.0.0", 73 | "html-webpack-plugin": "^3.2.0", 74 | "husky": "^1.0.0-rc.15", 75 | "jest": "^23.6.0", 76 | "node-sass": "^4.9.3", 77 | "nodemon": "^1.18.4", 78 | "react-hot-loader": "^4.3.11", 79 | "sass-loader": "^7.1.0", 80 | "style-loader": "^0.23.0", 81 | "webpack": "^4.19.1", 82 | "webpack-cli": "^3.1.1", 83 | "webpack-dev-middleware": "^3.3.0", 84 | "webpack-hot-middleware": "^2.24.2" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/containers/KatathonContainer/KatathonContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import Katathon from '../../views/Katathon/Katathon' 4 | 5 | class KatathonData extends Component { 6 | state = { 7 | katas: [ 8 | { 9 | title: 'Shortest Word', 10 | link: 'https://www.codewars.com/kata/shortest-word', 11 | score: 2 12 | }, 13 | { 14 | title: 'Remove Exclamation Marks', 15 | link: 'https://www.codewars.com/kata/remove-exclamation-marks', 16 | score: 2 17 | }, 18 | { 19 | title: 'Is Really NaN', 20 | link: 'https://www.codewars.com/kata/isreallynan/', 21 | score: 2 22 | }, 23 | { 24 | title: 'n-th Power', 25 | link: 'https://www.codewars.com/kata/n-th-power/', 26 | score: 3 27 | }, 28 | { 29 | title: 'Make a Function That Does Arithmetic', 30 | link: 'https://www.codewars.com/kata/make-a-function-that-does-arithmetic', 31 | score: 3 32 | }, 33 | { 34 | title: 'How Many e-mails We Sent Today', 35 | link: 'https://www.codewars.com/kata/how-many-e-mails-we-sent-today/', 36 | score: 5 37 | }, 38 | { 39 | title: 'String to List of Integers', 40 | link: 'https://www.codewars.com/kata/string-to-list-of-integers/', 41 | score: 5 42 | }, 43 | { 44 | title: 'Circle Area Inside Square', 45 | link: 'https://www.codewars.com/kata/circle-area-inside-square/', 46 | score: 7 47 | }, 48 | { 49 | title: 'What The Biggest Search Keys', 50 | link: 'https://www.codewars.com/kata/what-the-biggest-search-keys/', 51 | score: 7 52 | }, 53 | { 54 | title: 'How Many Points Did The Teams from Los Angeles Score', 55 | link: 'https://www.codewars.com/kata/how-many-points-did-the-teams-from-los-angeles-score/', 56 | score: 10 57 | }, 58 | { 59 | title: 'Cut Array Into Smaller Parts', 60 | link: 'https://www.codewars.com/kata/cut-array-into-smaller-parts/', 61 | score: 10 62 | }, 63 | { 64 | title: 'Remove HTML Tags U sing Regexp', 65 | link: 'https://www.codewars.com/kata/remove-html-tags-using-regexp/', 66 | score: 10 67 | }, 68 | { 69 | title: 'Simple Fun Number 165 Withdraw', 70 | link: 'https://www.codewars.com/kata/simple-fun-number-165-withdraw/', 71 | score: 15 72 | }, 73 | { 74 | title: 'Simple Fun Number 160 Cut The Ropes', 75 | link: 'https://www.codewars.com/kata/simple-fun-number-160-cut-the-ropes/', 76 | score: 20 77 | } 78 | ], 79 | participants: [ 80 | { 81 | username: 'webtechalex', 82 | score: 0 83 | }, 84 | { 85 | username: 'petegarvin1', 86 | score: 0 87 | }, 88 | { 89 | username: 'coudrew', 90 | score: 0 91 | }, 92 | { 93 | username: 'TroyMaeder', 94 | score: 0 95 | }, 96 | { 97 | username: 'marisid', 98 | score: 0 99 | }, 100 | { 101 | username: 'ijelonek', 102 | score: 0 103 | } 104 | ] 105 | } 106 | 107 | render() { 108 | return 109 | } 110 | } 111 | 112 | export default KatathonData 113 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "root": true, 8 | "extends": "airbnb-base", 9 | "parser": "babel-eslint", 10 | "plugins": [ "babel", "react" ], 11 | "parserOptions": { 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "experimentalObjectRestSpread": true 15 | }, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "max-len": 0, 20 | "comma-dangle": [ 21 | 2, 22 | "never" 23 | ], 24 | "dot-notation": 0, 25 | "arrow-parens": 0, 26 | "linebreak-style": [ 27 | 2, 28 | "unix" 29 | ], 30 | "no-param-reassign": 0, 31 | "semi": [ 32 | 2, 33 | "never" 34 | ], 35 | "react/jsx-uses-react": "error", 36 | "react/jsx-uses-vars": "error", 37 | "array-callback-return": 0, 38 | "arrow-body-style": 0, 39 | "block-spacing": 0, 40 | "brace-style": 0, 41 | "consistent-return": 0, 42 | "class-methods-use-this": 0, 43 | "function-paren-newline": ["error", "consistent"], 44 | "new-parens": 0, 45 | "newline-per-chained-call": 0, 46 | "no-bitwise": 0, 47 | "no-case-declarations": 0, 48 | "no-confusing-arrow": 0, 49 | "no-console": 1, 50 | "no-extra-boolean-cast": 0, 51 | "no-nested-ternary": 0, 52 | "no-shadow": 0, 53 | "no-plusplus": 0, 54 | "no-prototype-builtins": 0, 55 | "no-lonely-if": 0, 56 | "no-mixed-operators": 0, 57 | "no-restricted-syntax": 0, 58 | "no-return-assign": 0, 59 | "no-tabs": 0, 60 | "no-undef-init": 0, 61 | "no-underscore-dangle": 0, 62 | "no-unneeded-ternary": 0, 63 | "no-useless-escape": 0, 64 | "no-useless-return": 0, 65 | "object-property-newline": 0, 66 | "object-curly-newline": 0, 67 | "import/first": 0, 68 | "import/no-duplicates": 0, 69 | "import/no-dynamic-require": 0, 70 | "import/no-extraneous-dependencies": 0, 71 | "import/no-named-as-default": 0, 72 | "import/prefer-default-export": 0, 73 | "import/extensions": 0, 74 | "import/newline-after-import": 0, 75 | "import/no-unresolved": 0, 76 | "import/no-webpack-loader-syntax": 0, 77 | "keyword-spacing": 0, 78 | "global-require": 0, 79 | "jsx-a11y/href-no-hash": 0, 80 | "jsx-a11y/img-has-alt": 0, 81 | "jsx-a11y/img-redundant-alt": 0, 82 | "jsx-a11y/no-static-element-interactions": 0, 83 | "jsx-a11y/label-has-for": 0, 84 | "prefer-rest-params": 0, 85 | "prefer-template": 0, 86 | "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], 87 | "rest-spread-spacing": 0, 88 | "space-unary-ops": 0, 89 | "template-curly-spacing": 0, 90 | "valid-typeof": 0, 91 | "react/jsx-no-bind": 0, 92 | "react/no-danger": 0, 93 | "react/no-string-refs": 0, 94 | "react/no-find-dom-node": 0, 95 | "react/jsx-curly-spacing": 0, 96 | "react/jsx-filename-extension": 0, 97 | "react/jsx-space-before-closing": 0, 98 | "react/require-default-props": 0, 99 | "react/forbid-prop-types": 0, 100 | "react/no-array-index-key": 0, 101 | "react/no-danger-with-children": 0, 102 | "react/prefer-stateless-function": 0, 103 | "react/prop-types": 0, 104 | "react/sort-comp": 0, 105 | "react/jsx-tag-spacing": 0, 106 | "react/jsx-equals-spacing": 0, 107 | "react/jsx-indent": 0, 108 | "react/no-unused-prop-types": 0, 109 | "react/jsx-first-prop-new-line": 0, 110 | "react/self-closing-comp": 0, 111 | "react/jsx-wrap-multilines": 0 112 | } 113 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Katathon 2 | 3 | ## General 4 | This project is meant to connect together all the coding aficionados that started to meet together (either physically or remotely) to start solving a bunch of pre-selected problems in a competitive and friendly way. 5 | 6 | ## Local Setup 7 | 8 | ### Prerequisites 9 | To set up this repo for local viewing/testing, first of all make sure you have the latest stable versions of NodeJS and NPM installed. 10 | 11 | Install [Redux DevTools Chrome extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?utm_source=chrome-ntp-icon). It is recommended to use Google Chrome as your development browser for this project and you will need to have the above extension installed to be able to run the project locally. It is recommended for any extension as it makes it easy to debug Redux logic. 12 | 13 | You should also create a sandbox MongoDB database for the project to connect to. You can create one at [mLab](https://mlab.com/) or another online MongoDB host. 14 | 15 | **Important:** You will be asked to set a database admin username and password when you create a database. You will need to remember these credentials, (Not to be confused with your mLab sign in credentials.) 16 | 17 | ### Installation 18 | Once you have cloned the repo, navigate to the root directory in terminal and install the dependencies with: 19 | ``` 20 | npm install 21 | ``` 22 | You will need to add a .env file to the project root to access the test database. You can populate this file with the credentials for your own MongoDB database to test the APIs. The .env file contents should be in the format: 23 | 24 | ``` 25 | DB_USER=username 26 | DB_PASS=password 27 | DB_ADDRESS=address 28 | ``` 29 | 30 | where `username` is your MongoDB database user name, `passsword` is your MongoDB database password and `address` is the remainder of your unique mongodb database address following the username and password. For example: `@ds195639.mlab.com:95639/testdatabase` 31 | 32 | ### Running the Development Server 33 | To start the server: 34 | ``` 35 | npm run dev 36 | ``` 37 | In your browser, navigate to [http://localhost:3000](http://localhost:3000) to view the app. 38 | 39 | ## Aims and Goals 40 | * create the main site of a community of competitive coding enthusiasts 41 | * practice development skills, including: 42 | * front-end development 43 | * back-end development 44 | * UX design 45 | * copy writing 46 | * user profile management 47 | * mailing 48 | * APIs integration 49 | * spread computer literacy and involve minorities in the IT industry 50 | 51 | ## Features 52 | * static pages including content like: 53 | * who we are 54 | * our goals 55 | * how to join us/create your local katathon team 56 | * useful resources to improve your skills 57 | * various disclaimers and legal mumbo jumbo to pretend we thought things through on that side 58 | * dynamic pages including: 59 | * user profiles, including: 60 | * users listing, searching and filtering 61 | * generic user info 62 | * integration with other social platform to share information 63 | * favoured languages 64 | * favoured locations among the ones available 65 | * overall score display 66 | * badges 67 | * gravatar and/or github avatar integration 68 | * new user registration, including: 69 | * stand alone registration 70 | * registration using social media APIs 71 | * referral to invite users 72 | * notification preferences 73 | * other preferences 74 | * disable account feature 75 | * reactive account feature 76 | * delete account feature 77 | * events management, including: 78 | * integration with CodeWars or other competitive coding sites APIs 79 | * creation and management of a list of katas for each single event with relative scores 80 | * check on the solved problems on a single user basis before and after the event to auto-compute scores 81 | * sending newsletter to registered users 82 | * sending reminders to registered users 83 | * locations management, including 84 | * locations listing, searching and filtering 85 | * location rating by registered users according to specific parameters on a 0-10 basis 86 | * new location submission 87 | * old location deletion 88 | * newsletter management 89 | * creation and sending of generic newsletter with tips on competitive coding, next events and more 90 | * subscribe/unsubscribe database integration 91 | * social media integration with: 92 | * integration with automated feed for most important social platforms, including: 93 | * twitter 94 | * google plus (quite good for SEO, do not complain that *you* are not using it ;) ) 95 | * linkedin 96 | * facebook 97 | -------------------------------------------------------------------------------- /server/services/index.js: -------------------------------------------------------------------------------- 1 | import https from 'https' 2 | 3 | import Katathon from '../models/katathonModel' 4 | 5 | const getHistoricCompletedKatas = (userName, totalPages) => { 6 | // If all requests resloved then the Promise.all will resolved and return an array 7 | // of histically completed kata ids. If one fails then the Promise.all will reject. 8 | // This will result in no updated score for that user 9 | return Promise.all( 10 | Array.from({ length: totalPages - 1 }, (el, i) => { 11 | // Request is wrapped in a promise so that Promise.all can run when all requests 12 | // have resolved 13 | return new Promise((resolve, reject) => { 14 | https.get( 15 | `https://www.codewars.com/api/v1/users/${userName}/code-challenges/completed?page=${i}`, 16 | (httpsResponse) => { 17 | try { 18 | if (httpsResponse.statusCode !== 200) { 19 | throw new Error(` 20 | Request Failed. 21 | Status Code: ${httpsResponse.statusCode} 22 | API: code-challenge. 23 | User: ${userName} 24 | Page: ${i} 25 | `) 26 | } 27 | } catch (err) { 28 | // Log err 29 | reject() 30 | } 31 | let data = '' 32 | 33 | httpsResponse.on('data', (chunk) => { 34 | data += chunk 35 | }) 36 | 37 | httpsResponse.on('end', () => { 38 | try { 39 | const response = JSON.parse(data) 40 | if (response.success === false) { 41 | throw new Error('User not found') 42 | } 43 | resolve(response) 44 | } catch (err) { 45 | // Log err 46 | reject() 47 | } 48 | }) 49 | } 50 | ) 51 | }) 52 | }) 53 | ) 54 | .then( 55 | allResponses => allResponses 56 | .map(response => response.data.map(kata => kata.id)) 57 | ) 58 | } 59 | 60 | const getCompletedKatas = (userName) => { 61 | return new Promise((resolve, reject) => { 62 | https.get( 63 | `https://www.codewars.com/api/v1/users/${userName}/code-challenges/completed`, 64 | (httpsResponse) => { 65 | try { 66 | if (httpsResponse.statusCode !== 200) { 67 | throw new Error(` 68 | Request Failed. 69 | Status Code: ${httpsResponse.statusCode} 70 | API: code-challenge. 71 | User: ${userName} 72 | Page: 0 73 | `) 74 | } 75 | } catch (err) { 76 | // Log err 77 | reject() 78 | } 79 | let data = '' 80 | 81 | httpsResponse.on('data', (chunk) => { 82 | data += chunk 83 | }) 84 | 85 | httpsResponse.on('end', () => { 86 | try { 87 | const response = JSON.parse(data) 88 | if (response.success === false) { 89 | throw new Error('User not found') 90 | } 91 | 92 | // Adds completed kata ids to an array 93 | let completed = response.data.map(kata => kata.id) 94 | if (response.totalPages === 1) { 95 | resolve(completed) 96 | } else { 97 | // If there is more than one page of results all other pages must 98 | // also be requested 99 | getHistoricCompletedKatas(userName, response.totalPages) 100 | .then((historicCompleted) => { 101 | completed = [...completed, ...historicCompleted] 102 | resolve(completed) 103 | }) 104 | .catch((err) => { 105 | reject(err) 106 | }) 107 | } 108 | } catch (err) { 109 | // If the user is not found it may indicate that their account has 110 | // been deleted 111 | // Log err 112 | reject() 113 | } 114 | }) 115 | } 116 | ) 117 | }) 118 | } 119 | 120 | export const updateAllScores = async (katathonId) => { 121 | try { 122 | const katathon = await Katathon.findById(katathonId) 123 | const { users, katas } = katathon 124 | if (!users.length || !katas.length) { 125 | return 126 | } 127 | // Compiles a list of all ongoing userScore requests. Once they have all 128 | // updated and resolved the new data is written to db 129 | Promise.all(users.map((user) => { 130 | // This promise must always resolve so that if one user request fails the 131 | // other scores will still be updated. Currently a deleted account will fail 132 | return new Promise(async (resolve) => { 133 | try { 134 | const completed = await getCompletedKatas(user.userName) 135 | 136 | // This checks that no requests for completed katas have failed 137 | if (!Array.isArray(completed)) { 138 | throw new Error(`Failed to get completed katas for ${user.userName}`) 139 | } 140 | 141 | // Loops through katas and adds score when they are found in completed 142 | const userScore = katas.reduce((acc, kata) => completed.includes(kata.kataId) 143 | ? acc + kata.score 144 | : acc, 0) 145 | 146 | // Updates model where appropriate 147 | if (userScore !== user.userScore) { 148 | katathon.users = katathon.users.map((existingUser) => { 149 | return existingUser.userName === user.userName 150 | ? { ...existingUser._doc, userScore } 151 | : existingUser 152 | }) 153 | } 154 | resolve() 155 | } catch (err) { 156 | // Log err 157 | resolve() 158 | } 159 | }) 160 | })) 161 | .then(async () => { 162 | try { 163 | await katathon.save() 164 | } catch (err) { 165 | // Log err 166 | } 167 | }) 168 | } catch (err) { 169 | // Log err 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /server/controllers/index.js: -------------------------------------------------------------------------------- 1 | import https from 'https' 2 | 3 | import Katathon from '../models/katathonModel' 4 | 5 | import { 6 | dateToTimestamp, 7 | getNextEvent 8 | } from '../helpers' 9 | 10 | import { newKatathonJob } from '../jobs/index' 11 | 12 | export const newKatathon = async (req, res) => { 13 | try { 14 | // Should be date from our front end application. 15 | // I am not sure how we would set the date at our front end application so for now 16 | // the format should be yyyy,mm,dd Example: (2018-10-22) 17 | const eventDateTimestamp = await dateToTimestamp(req.body.date) 18 | 19 | // Create Katathon and save it to the db 20 | const newKatathon = await new Katathon({ 21 | date: eventDateTimestamp 22 | }).save() 23 | 24 | if (newKatathon) { 25 | // This will schedule the updateALlScores job 26 | newKatathonJob(eventDateTimestamp, newKatathon._id) 27 | 28 | res.status(200).json({ 29 | result: 'Success', 30 | data: newKatathon, 31 | message: 'The new event has been successfully created. Please add the katas to the event' 32 | }) 33 | } 34 | } catch (err) { 35 | res.status(400).json({ 36 | result: 'Failed', 37 | data: {}, 38 | message: `Unable to create a new event. Error ${err}` 39 | }) 40 | } 41 | } 42 | 43 | export const addKata = async (req, res) => { 44 | try { 45 | const { kataId, name, slug, link, score } = req.body 46 | // Create new kata 47 | const newKata = { 48 | kataId, 49 | name, 50 | slug, 51 | link, 52 | score 53 | } 54 | 55 | const katathon = await Katathon.findById(req.params.katathonId) 56 | 57 | if(katathon) { 58 | katathon.katas = [newKata, ...katathon.katas] 59 | katathon.save() 60 | res.status(200).json({ 61 | result: 'Success', 62 | data: katathon, 63 | message: 'New kata has been added to the Katathon' 64 | }) 65 | } 66 | } catch(err) { 67 | res.status(400).json({ 68 | result: 'Failed', 69 | data: [], 70 | message: `Unable to create a new kata. Error ${err}` 71 | }) 72 | } 73 | } 74 | 75 | export const updateKata = async (req, res) => { 76 | // Update Kata 77 | try { 78 | const { katathonId, _id } = req.params 79 | const { kataId, name, slug, link, score } = req.body 80 | 81 | const bodyCopy = { 82 | kataId, 83 | name, 84 | slug, 85 | link, 86 | score 87 | } 88 | 89 | const katathon = await Katathon.findById(katathonId) 90 | 91 | katathon.katas = katathon.katas.map(kata => kata._id === _id ? Object.assign(kata, bodyCopy) : kata) 92 | katathon.save() 93 | 94 | res.status(200).json({ 95 | result: 'Success', 96 | data: katathon, 97 | message: 'Kata has been updated successfully' 98 | }) 99 | }catch(err) { 100 | res.status(400).json({ 101 | result: 'Failed', 102 | data: [], 103 | message: `Unable to update kata. Error ${err}` 104 | }) 105 | } 106 | } 107 | 108 | export const listKatathons = async (req, res) => { 109 | // Return list of events ordered by recent added 110 | try { 111 | const katathons = await Katathon.find({ completed: false }).sort({ date_created: -1 }) 112 | if(katathons.length > 0) { 113 | res.status(200).json({ 114 | result: 'Success', 115 | data: katathons, 116 | message: 'query list of Katathons successfully' 117 | }) 118 | }else { 119 | res.status(404).json({ 120 | result: 'Not Found', 121 | data: [], 122 | message: 'No Katathon event found' 123 | }) 124 | } 125 | } catch (err) { 126 | res.status(400).json({ 127 | result: 'Failed', 128 | data: [], 129 | message: `query list of Katathons failed. Error ${err}` 130 | }) 131 | } 132 | } 133 | 134 | export const updateKatathon = async (req, res) => { 135 | // Update Katathon 136 | try { 137 | const eventDate = await dateToTimestamp(req.body.date) 138 | 139 | const katathon = await Katathon.findById(req.params.katathonId) 140 | 141 | if(katathon && eventDate) { 142 | katathon.set({ date: eventDate }) 143 | katathon.save() 144 | res.status(200).json({ 145 | result: 'Success', 146 | data: katathon, 147 | message: 'Katathon has been updeted successfully' 148 | }) 149 | } else { 150 | res.status(404).json({ 151 | result: 'Not Found', 152 | data: [], 153 | message: 'No Katathon event found' 154 | }) 155 | } 156 | } catch(err) { 157 | res.status(400).json({ 158 | result: 'Failed', 159 | data: [], 160 | message: `Unable to update Katathon. Error ${err}` 161 | }) 162 | } 163 | } 164 | 165 | 166 | export const nextKatathon = async (req, res) => { 167 | try { 168 | const katathons = await Katathon.find({ completed: false }) 169 | if(katathons.length > 0) { 170 | const nextKatathon = getNextEvent(katathons) 171 | res.status(200).json({ 172 | result: 'Success', 173 | data: nextKatathon, 174 | message: 'Next Katathon found successfully' 175 | }) 176 | }else { 177 | res.status(404).json({ 178 | result: 'Not Found', 179 | data: [], 180 | message: 'No Katathon event found' 181 | }) 182 | } 183 | } catch (err) { 184 | res.status(400).json({ 185 | result: 'Failed', 186 | data: [], 187 | message: `query next Katathon failed. Error ${err}` 188 | }) 189 | } 190 | } 191 | 192 | export const addUser = (req, res) => { 193 | try { 194 | const { userName } = req.body 195 | 196 | https.get(`https://www.codewars.com/api/v1/users/${userName}/code-challenges/completed?page=0`, (httpsResponse) => { 197 | let data = '' 198 | 199 | httpsResponse.on('data', (chunk) => { 200 | data += chunk 201 | }) 202 | 203 | httpsResponse.on('end', async () => { 204 | try { 205 | const userResponse = JSON.parse(data) 206 | 207 | if (userResponse.success === false) { 208 | throw new Error('User not found') 209 | } 210 | const newUser = { 211 | userName 212 | } 213 | const katathon = await Katathon.findById(req.params.katathonId) 214 | 215 | if (!katathon) { 216 | throw new Error('Katathon not found') 217 | } 218 | 219 | const isNew = katathon.users.every(user => user.userName !== userName) 220 | 221 | if (!isNew) { 222 | throw new Error('User already in katathon') 223 | } 224 | 225 | katathon.users = [newUser, ...katathon.users] 226 | katathon.save() 227 | res.status(200).json({ 228 | result: 'Success', 229 | data: katathon, 230 | message: 'New user has been added to the Katathon' 231 | }) 232 | } catch (err) { 233 | res.status(400).json({ 234 | result: 'Failed', 235 | data: [], 236 | message: `Unable to add new user. Error ${err}` 237 | }) 238 | } 239 | }) 240 | }) 241 | .on('error', (err) => { 242 | throw new Error(err) 243 | }) 244 | } catch (err) { 245 | res.status(400).json({ 246 | result: 'Failed', 247 | data: [], 248 | message: `Unable to add new user. Error ${err}` 249 | }) 250 | } 251 | } 252 | 253 | export const getLeaderBoard = async (req, res) => { 254 | try { 255 | const katathon = await Katathon.findById(req.params.katathonId) 256 | 257 | if (!katathon) { 258 | throw new Error('Katathon not found') 259 | } 260 | 261 | const allUsers = katathon.users.map(user => ({ 262 | userName: user.userName, 263 | userScore: user.userScore 264 | })) 265 | 266 | res.status(200).json({ 267 | result: 'Success', 268 | data: allUsers, 269 | message: '' 270 | }) 271 | } catch (err) { 272 | res.status(400).json({ 273 | result: 'Failed', 274 | data: [], 275 | message: `Unable to add get leader board. Error ${err}` 276 | }) 277 | } 278 | } 279 | --------------------------------------------------------------------------------