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.
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 |
--------------------------------------------------------------------------------