├── LICENSE ├── README.md ├── app ├── .gitignore ├── package.json ├── public │ ├── css │ │ └── base.css │ ├── img │ │ └── github.png │ └── js │ │ └── app.js ├── scripts │ ├── createTable.js │ ├── dumpTable.js │ └── unenrolled.js ├── server.js ├── students.csv └── views │ ├── 404.html │ ├── add.html │ ├── base.html │ ├── error.html │ ├── home.html │ └── success.html ├── guides └── git-and-github.md └── secret.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Josh Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | github-plus-university 2 | ====================== 3 | 4 | This source code goes along with my two blog posts: 5 | 6 | - [GitHub + University: How College Coding Assignments Should Work][post1] 7 | - [GitHub + University: A Follow Up][post2] 8 | 9 | ## Install 10 | 11 | To install it, just be sure to have NodeJS installed and run the following: 12 | 13 | ```bash 14 | $ cd app/ 15 | $ npm install 16 | ``` 17 | ## Config 18 | 19 | There are a lot of hardcoded strings within the scripts, if I have time, I will 20 | try to remove these. 21 | 22 | In addition to the hardcoded strings, a **students.csv** file is required. It 23 | should be a CSV file of the list of students in the class. 24 | 25 | It should follow the format below: 26 | 27 | ``` 28 | last_name, first_name, university_id 29 | ``` 30 | 31 | check out the sample [students.csv][students] 32 | 33 | ## Running 34 | 35 | To run it, just run: 36 | 37 | ```bash 38 | $ node server.js 39 | ``` 40 | 41 | or you might want to look into a tool like [forever][forever] which ensures that 42 | your server won't crash if it has an unexpected error. 43 | 44 | ## License 45 | 46 | This code is distributed under the MIT license. For more info, read the 47 | [LICENSE][license] file distributed with the source code. 48 | 49 | [forever]: https://github.com/nodejitsu/forever 50 | [license]: /LICENSE 51 | [post1]: http://joshldavis.com/2014/01/19/github-university-how-college-assignments-should-work/ 52 | [post2]: http://joshldavis.com/2014/06/30/github-university-follow-up/ 53 | [students]: /app/students.csv 54 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | secret.js 3 | students.csv 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "course-bot", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "start": "node app" 6 | }, 7 | 8 | "devDependencies": { 9 | }, 10 | 11 | "dependencies": { 12 | "express": "~3.4", 13 | "swig": "~1.2", 14 | "github": "~0.1", 15 | "sqlite3": "~2.1", 16 | "request": "~2.31", 17 | "csv": "~0.3", 18 | "nodemailer": "~0.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/public/css/base.css: -------------------------------------------------------------------------------- 1 | html, body, div, h1, h2, h3, h4, h5, h6, ul, ol, dl, li, dt, dd, p, blockquote, pre, form, fieldset, table, th, td { margin: 0; padding: 0; } 2 | html, body {height: 100%;} 3 | img { border: none; } 4 | ul { list-style: none; } 5 | a { text-decoration: none; } 6 | 7 | body { 8 | font-family: "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | color: #505050; 10 | line-height: 22px; 11 | background-color: #373B44; 12 | } 13 | 14 | h1, h2, h3 { 15 | margin-bottom: 20px; 16 | } 17 | 18 | a { 19 | color: #BD5532; 20 | border-bottom: 1px dashed #CFB590; 21 | } 22 | 23 | a:hover { 24 | color: #BD5532; 25 | text-decoration: none; 26 | } 27 | 28 | .container { 29 | width: 540px; 30 | margin: 0 auto; 31 | padding-top: 40px; 32 | } 33 | 34 | div.content { 35 | margin: 20px 0; 36 | background-color: #FDFDFD; 37 | padding: 30px 40px; 38 | border-radius: 10px; 39 | } 40 | 41 | div.logo { 42 | width: 150px; 43 | margin: 0 auto; 44 | margin-bottom: 20px; 45 | } 46 | 47 | p { 48 | margin-bottom: 15px; 49 | } 50 | 51 | label { 52 | padding: 10px 0; 53 | font-size: 18px; 54 | font-weight: bold; 55 | } 56 | 57 | .text { 58 | display: block; 59 | width: 200px; 60 | margin: 10px 0; 61 | padding: 6px 12px; 62 | font-size: 14px; 63 | line-height: 1.428571429; 64 | color: #555; 65 | vertical-align: middle; 66 | background-color: #fff; 67 | border: 1px solid #ccc; 68 | border-radius: 4px; 69 | } 70 | 71 | .button { 72 | width: 100%; 73 | height: 50px; 74 | text-align: center; 75 | font-size: 22px; 76 | margin-top: 30px; 77 | display: inline-block; 78 | background-color: #599Bdc; 79 | 80 | -webkit-border-radius: 3px; 81 | -moz-border-radius: 3px; 82 | border-radius: 3px; 83 | 84 | padding: 4px 6px; 85 | display: block; 86 | border-color: #295c8c; 87 | border-width: 1px; 88 | border-style: solid; 89 | font-size: 14px; 90 | color: #FFFFFF; 91 | } 92 | 93 | .button:hover { 94 | background-color: #3072B3; 95 | color: white; 96 | } 97 | 98 | .button:active { 99 | background-color: #5ea3e0; 100 | color: white; 101 | } 102 | 103 | .button img { 104 | margin: 4px 20px 4px 4px; 105 | vertical-align:middle; 106 | height: 32px; 107 | background-color: #fafafa; 108 | padding: 3px; 109 | border-radius: 3px; 110 | } 111 | -------------------------------------------------------------------------------- /app/public/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdavis/github-plus-university/20eb7452b9f0607305c0e69e3d8d4c25da9f6a75/app/public/img/github.png -------------------------------------------------------------------------------- /app/public/js/app.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | }(jQuery)); 4 | -------------------------------------------------------------------------------- /app/scripts/createTable.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('sqlite3').verbose(); 2 | 3 | var db = new sqlite3.Database('accessTokens.db'); 4 | 5 | console.log('Creating DB...'); 6 | 7 | db.serialize(function () { 8 | db.run('CREATE TABLE tokens (username TEXT, token TEXT)'); 9 | }); 10 | 11 | db.close(); 12 | -------------------------------------------------------------------------------- /app/scripts/dumpTable.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('sqlite3').verbose(); 2 | 3 | var db = new sqlite3.Database('accessTokens.db'); 4 | 5 | db.serialize(function () { 6 | db.each("SELECT username, token FROM tokens", function(err, row) { 7 | console.log(row.username + ": " + row.token); 8 | }); 9 | }); 10 | 11 | db.close(); 12 | -------------------------------------------------------------------------------- /app/scripts/unenrolled.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | swig = require('swig'), 3 | crypto = require('crypto'), 4 | util = require('util'), 5 | request = require('request'), 6 | sqlite3 = require('sqlite3').verbose(), 7 | github = require('github'), 8 | csv = require('csv'), 9 | nodemailer = require('nodemailer'), 10 | secret = require('../secret'); 11 | 12 | var app, 13 | db, 14 | config, 15 | handler; 16 | 17 | 18 | // 19 | // Basic Bot Config 20 | // 21 | 22 | config = { 23 | // Course Org 24 | org: 'ComS342-ISU', 25 | 26 | // Prefix for student's repo 27 | repo_prefix: 'hw-answers-', 28 | 29 | // ISU Email 30 | email: '@iastate.edu', 31 | }; 32 | 33 | handler = {}; 34 | 35 | // 36 | // Helper Functions that call GitHub's API 37 | // 38 | 39 | handler.initGitHubAPI = function () { 40 | var gh; 41 | 42 | gh = new github({ 43 | version: '3.0.0', 44 | }); 45 | 46 | gh.authenticate({ 47 | type: 'oauth', 48 | token: secret.TOKEN 49 | }); 50 | 51 | return gh; 52 | }; 53 | 54 | handler.checkMembership = function (err, ret) { 55 | var gh = { 56 | api: handler.initGitHubAPI(), 57 | page: 1, 58 | }, 59 | per_page = 100, 60 | registered = [], 61 | retrieveRepository = function (errr, ret) { 62 | var prevLen = registered.length; 63 | 64 | for (var prop in ret) { 65 | if (prop === 'meta') continue; 66 | 67 | var name = ret[prop].name; 68 | 69 | if (name.indexOf(config.repo_prefix) !== 0) continue; 70 | 71 | // Trim off config.repo_prefix, add it to students registered 72 | registered.push(name.substring(config.repo_prefix.length, name.length)); 73 | } 74 | 75 | if (prevLen + 100 === registered.length) { 76 | page += 1; 77 | getNextPage(page); 78 | } else { 79 | return handler.checkStudentList(registered); 80 | } 81 | }, 82 | getNextPage = function (page) { 83 | gh.api.repos.getFromOrg({ 84 | org: config.org, 85 | type: 'member', 86 | per_page: 100, 87 | page: page, 88 | }, retrieveRepository); 89 | }; 90 | 91 | 92 | getNextPage(1); 93 | } 94 | 95 | handler.checkStudentList = function (students) { 96 | var unregistered = [], 97 | unused = [], 98 | total = 0, 99 | repoCount = 0; 100 | 101 | unused = [].concat(students); 102 | repoCount = students.length; 103 | 104 | csv().from.path('./students.csv', { 105 | columns: ['last_name', 'first_name', 'username'] 106 | }).on('record', function (row, i) { 107 | var id = row.username; 108 | 109 | total += 1; 110 | 111 | if (students.indexOf(id) === -1) { 112 | unregistered.push(id); 113 | } 114 | 115 | var x = unused.indexOf(id); 116 | 117 | if (x !== -1) { 118 | unused.splice(x, 1); 119 | } 120 | }).on('end', function () { 121 | console.log('Students Total: ' + total); 122 | console.log('Students Not on GitHub (' + unregistered.length + '):') 123 | console.log(unregistered.join('\n')); 124 | 125 | console.log('\nEmails:\n' + unregistered.join(config.email + ', ') + config.email); 126 | 127 | console.log(''); 128 | console.log('Repositories Total: ' + repoCount); 129 | console.log('Unused Repos: (' + unused.length + '):') 130 | console.log(unused.join('\n')); 131 | }); 132 | } 133 | 134 | // 135 | // Start script 136 | // 137 | 138 | handler.checkMembership(); 139 | -------------------------------------------------------------------------------- /app/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | swig = require('swig'), 3 | crypto = require('crypto'), 4 | util = require('util'), 5 | request = require('request'), 6 | sqlite3 = require('sqlite3').verbose(), 7 | github = require('github'), 8 | csv = require('csv'), 9 | secret = require('./secret'); 10 | 11 | var app, 12 | db, 13 | config, 14 | handler; 15 | 16 | 17 | // 18 | // Basic Bot Config 19 | // 20 | 21 | config = { 22 | // Course Org 23 | org: 'ComS342-ISU', 24 | 25 | // Main Team 26 | student_team: '634635', 27 | 28 | // Prefix for teams 29 | team_prefix: 'Students: ', 30 | 31 | // Prefix for student's repo 32 | repo_prefix: 'hw-answers-', 33 | }; 34 | 35 | handler = {}; 36 | 37 | // 38 | // Basic DB for storing access tokens 39 | // 40 | 41 | db = new sqlite3.Database('accessTokens.db'); 42 | 43 | // 44 | // Express Setup/Config 45 | // 46 | 47 | app = express(); 48 | 49 | app.set('port', process.env.PORT || 4000); 50 | app.set('host', process.env.IP || '0.0.0.0'); 51 | 52 | // Use Swig for templates 53 | app.engine('html', swig.renderFile); 54 | app.set('view engine', 'html'); 55 | 56 | app.set('views', __dirname + '/views'); 57 | 58 | // Setup sessions 59 | app.use(express.cookieParser()); 60 | app.use(express.session({secret: secret.SESSION_SECRET})); 61 | 62 | // Server up static 63 | app.use(express.static(__dirname + '/public')) 64 | 65 | // Setup POST handling 66 | app.use(express.bodyParser()); 67 | 68 | // 69 | // Routing 70 | // 71 | 72 | // Home: 73 | // Shows the sign in & join button 74 | app.get('/', function (req, res) { 75 | var context = {}, 76 | state; 77 | 78 | crypto.randomBytes(48, function (ex, buf) { 79 | state = buf.toString('hex'); 80 | 81 | // Provided by GitHub for API access 82 | context['client_id'] = secret.CLIENT_ID; 83 | context['state'] = encodeURIComponent(state); 84 | 85 | // Set in session for checking later 86 | req.session.state = state; 87 | 88 | res.render('home', context); 89 | }) 90 | }); 91 | 92 | // GitHub API callback as setup here: 93 | // https://github.com/organizations/ComS342-ISU/settings/applications 94 | app.get('/auth', function (req, res) { 95 | var code = req.query.code; 96 | 97 | var state = req.query.state; 98 | var token = req.session.state; 99 | 100 | console.log('Given Query: ' + req.query.state); 101 | console.log('Given Code: ' + state); 102 | console.log('Token: ' + token); 103 | console.log('Session: ' + util.inspect(req.session)); 104 | 105 | // Show error if invalid token 106 | if (token && token !== state) { 107 | return res.render('error', { 108 | error: 'invalid token' 109 | }); 110 | } 111 | 112 | // Set GitHub temporary code for access after redirect 113 | req.session.tempCode = code; 114 | 115 | handler.requestAccessToken(req, res, code); 116 | }); 117 | 118 | app.get('/add', function (req, res) { 119 | var context = {}; 120 | 121 | if (req.session.status !== true) { 122 | return res.redirect('/'); 123 | } 124 | 125 | context['name'] = req.session.name || req.session.username; 126 | 127 | res.render('add', context); 128 | }); 129 | 130 | app.post('/add', function (req, res) { 131 | var netID = req.body.netID; 132 | 133 | if (req.session.status !== true) { 134 | return res.redirect('/'); 135 | } 136 | 137 | handler.validateNetID(req, res, netID); 138 | }); 139 | 140 | // 141 | // Helper Functions that call GitHub's API 142 | // 143 | 144 | handler.initGitHubAPI = function () { 145 | var gh; 146 | 147 | gh = new github({ 148 | version: '3.0.0', 149 | }); 150 | 151 | gh.authenticate({ 152 | type: 'oauth', 153 | token: secret.TOKEN 154 | }); 155 | 156 | return gh; 157 | }; 158 | 159 | handler.requestAccessToken = function (req, res, code) { 160 | var options = { 161 | method: 'POST', 162 | qs: { 163 | client_id: secret.CLIENT_ID, 164 | client_secret: secret.CLIENT_SECRET, 165 | code: code, 166 | }, 167 | headers: { 168 | 'Accept': 'application/json' 169 | }, 170 | }; 171 | 172 | request( 173 | 'https://github.com/login/oauth/access_token', 174 | options, 175 | handler.retrieveAccessToken.bind(handler, req, res) 176 | ); 177 | }; 178 | 179 | handler.retrieveAccessToken = function (req, res, err, response, body) { 180 | if (err || response.statusCode !== 200) { 181 | return res.render('error', { 182 | error: 'invalid token', 183 | }); 184 | } 185 | 186 | var data = JSON.parse(body), 187 | token; 188 | 189 | token = data.access_token; 190 | 191 | console.log('Body: ' + body); 192 | console.log('Access Token: ' + token); 193 | 194 | if (!token) { 195 | return res.render('error', { 196 | error: 'invalid token', 197 | }); 198 | } 199 | 200 | options = { 201 | qs: { 202 | access_token: token, 203 | }, 204 | headers: { 205 | 'Accept': 'application/json', 206 | 'User-Agent': 'ComS 342 Course Bot', 207 | }, 208 | }; 209 | 210 | request( 211 | 'https://api.github.com/user', 212 | options, 213 | handler.setGitHubInfo.bind(handler, req, res, token) 214 | ); 215 | }; 216 | 217 | handler.setGitHubInfo = function (req, res, token, err, response, body) { 218 | // TODO: Check err 219 | 220 | var data = JSON.parse(body), 221 | found = false; 222 | 223 | console.log('Data: ' + util.inspect(data)); 224 | 225 | req.session.status = true; 226 | req.session.name = data.name; 227 | req.session.username = data.login; 228 | req.session.token = token; 229 | 230 | // Add student info to the database 231 | db.serialize(function () { 232 | db.run('INSERT INTO tokens VALUES (?, ?)', data.login, token); 233 | }); 234 | 235 | return res.redirect('/add'); 236 | }; 237 | 238 | 239 | 240 | handler.validateNetID = function (req, res, netID) { 241 | var student = {}, 242 | found = false; 243 | 244 | if (!netID) { 245 | return res.render('add', { 246 | error: true, 247 | name: req.session.name || req.session.username, 248 | }); 249 | } 250 | 251 | // Trim whitespace just in case 252 | netID = netID.trim(); 253 | 254 | if (netID.indexOf('@iastate.edu') !== -1) { 255 | netID = netID.substring(0, netID.length - '@iastate.edu'.length); 256 | } 257 | 258 | student.username = req.session.username; 259 | student.name = req.session.name; 260 | student.token = req.session.token; 261 | student.netID = netID; 262 | 263 | csv().from.path('./students.csv', { 264 | columns: ['last_name', 'first_name', 'username'] 265 | }).on('record', function (row, i) { 266 | if (netID === row.username) { 267 | found = true; 268 | } 269 | }).on('end', function () { 270 | if (found === false) { 271 | return res.render('error', { 272 | error: 'not on roster', 273 | }); 274 | } else { 275 | handler.addStudent(req, res, student); 276 | } 277 | }); 278 | }; 279 | 280 | handler.addStudent = function (req, res, student) { 281 | var gh = {}; 282 | 283 | gh.student = student; 284 | 285 | gh.api = handler.initGitHubAPI(); 286 | 287 | gh.api.orgs.getMember({ 288 | org: config.org, 289 | user: student.username, 290 | }, handler.checkMembership.bind(handler, req, res, gh)); 291 | } 292 | 293 | handler.checkMembership = function (req, res, gh, err, ret) { 294 | console.log('Checked membership: ' + util.inspect(ret)); 295 | console.log('Checked membership err: ' + util.inspect(err)); 296 | 297 | if (ret && ret.meta && ret.meta.status === '204 No Content') { 298 | return res.render('error', { 299 | error: 'already member', 300 | repo: config.repo_prefix + gh.student.netID, 301 | }); 302 | } 303 | 304 | // Getting an error means they aren't a member 305 | if (!err) { 306 | return res.render('error', { 307 | error: 'failed checking membership', 308 | }); 309 | } 310 | 311 | gh.api.orgs.createTeam({ 312 | org: config.org, 313 | name: config.team_prefix + gh.student.netID, 314 | permission: 'push', 315 | }, handler.addStudentToOwnTeam.bind(handler, req, res, gh)); 316 | }; 317 | 318 | handler.addStudentToOwnTeam = function (req, res, gh, err, ret) { 319 | if (err) { 320 | return res.render('error', { 321 | error: 'failed creating a team', 322 | }); 323 | } 324 | 325 | console.log('Created team: ' + util.inspect(ret)); 326 | 327 | gh.team = ret.id; 328 | 329 | gh.api.orgs.addTeamMember({ 330 | id: gh.team, 331 | user: gh.student.username, 332 | }, handler.addUserToStudentsTeam.bind(handler, req, res, gh)); 333 | }; 334 | 335 | handler.addUserToStudentsTeam = function (req, res, gh, err, ret) { 336 | if (err) { 337 | return res.render('error', { 338 | error: 'failed adding to Students', 339 | }); 340 | } 341 | 342 | console.log('Added student to own team: ' + util.inspect(ret)); 343 | 344 | gh.api.orgs.addTeamMember({ 345 | id: config.student_team, 346 | user: gh.student.username, 347 | }, handler.createSolutionsRepo.bind(handler, req, res, gh)); 348 | }; 349 | 350 | handler.createSolutionsRepo = function (req, res, gh, err, ret) { 351 | if (err) { 352 | return res.render('error', { 353 | error: 'failed adding to team', 354 | }); 355 | } 356 | 357 | console.log('Added student to Students: ' + util.inspect(ret)); 358 | 359 | var name = gh.student.name || gh.student.username; 360 | 361 | gh.api.repos.createFromOrg({ 362 | org: config.org, 363 | name: config.repo_prefix + gh.student.netID, 364 | description: 'Homework solutions for ' + name + ', NetID: ' + gh.student.netID, 365 | private: true, 366 | has_issues: true, 367 | team_id: gh.team, 368 | }, handler.apiFinishSetup.bind(handler, req, res, gh)); 369 | }; 370 | 371 | handler.apiFinishSetup = function (req, res, gh, err, ret) { 372 | var context; 373 | 374 | if (err) { 375 | return res.render('error', { 376 | error: 'failed creating new repo', 377 | }); 378 | } 379 | 380 | console.log('Created new repo:' + util.inspect(ret)); 381 | 382 | context = gh.student; 383 | context.repo = config.repo_prefix + gh.student.netID; 384 | 385 | return res.render('success', context); 386 | }; 387 | 388 | // 389 | // Launch app 390 | // 391 | 392 | app.listen(app.get('port'), app.get('host'), function () { 393 | console.log('CourseBot listening on ' + app.get('host') + ':' + app.get('port')); 394 | }); 395 | -------------------------------------------------------------------------------- /app/students.csv: -------------------------------------------------------------------------------- 1 | LastName,FirstName,UniversityID 2 | Asimov,Isaac,asimov 3 | Barker,Bob,bbarker 4 | Newton,Isaac,newton 5 | -------------------------------------------------------------------------------- /app/views/404.html: -------------------------------------------------------------------------------- 1 |
7 | Hey there, {{ name }}. 8 |
9 | 10 |11 | Now all we need is your NetID to verify you are enrolled in the class. 12 |
13 | 14 |15 | The NetID is the part before @iastate.edu incase you forgot. 16 |
17 | 18 | 31 | 32 |8 | That is an invalid NetID. Why don't you try again? 9 |
10 | 11 |12 | If you are having issues, contact Josh Davis, one of the TAs. 13 |
14 | {% elif error == 'not on roster' %} 15 |18 | It doesn't appear that your NetID was on the class list at this time. 19 |
20 | 21 |22 | This may be the case if you've recently added the course. Contact Josh Davis, one of the TAs, 24 | to get added manually. 25 |
26 | {% elif error == 'failed adding to Students' %} 27 |30 | There was a problem adding your GitHub account to the Students team. 31 |
32 | 33 |34 | Contact Josh Davis, one of 35 | the TAs, to fix things for you. 36 |
37 | {% elif error == 'failed adding to team' %} 38 |41 | There was a problem adding your GitHub account to a team. 42 |
43 | 44 |45 | Contact Josh Davis, one of 46 | the TAs, to fix things for you. 47 |
48 | {% elif error == 'failed creating a team' %} 49 |52 | There was a problem creating a Team for you on GitHub. 53 |
54 | 55 |56 | Contact Josh Davis, one of 57 | the TAs, to fix things for you. 58 |
59 | {% elif error == 'invalid token' %} 60 |63 | There was an unknown problem with GitHub. 64 |
65 | 66 |67 | Contact Josh Davis, one of 68 | the TAs, to fix things for you. 69 |
70 | {% elif error == 'already member' %} 71 |74 | It looks like you are already a member. Your repository is here: 75 | {{ repo }}. 76 |
77 | 78 |79 | The next thing you will want to do is visit the 80 | Course Setup Guide 81 | and follow the instructions to setup Git. 82 |
83 | 84 |85 | If this is a mistake, contact 86 | Josh Davis, one of 87 | the TAs, to fix things for you. 88 |
89 | {% elif error == 'failed checking membership' %} 90 |91 | There was a problem checking to see if you belong to the Organization. 92 |
93 | 94 |95 | Contact Josh Davis, one of 96 | the TAs, to fix things for you. 97 |
98 | {% else %} 99 |100 | Something appears to have gone wrong. 101 |
102 | 103 |104 | Contact Josh Davis, one of 105 | the TAs, to fix things for you. 106 |
107 | {% endif %} 108 | 109 | {% endblock %} 110 | -------------------------------------------------------------------------------- /app/views/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |7 | This will start the process of adding your GitHub account to the 8 | ComS 342 organization. 9 |
10 | 11 |12 | You'll log in using GitHub's OAuth and then you will enter your 13 | NetID. After doing that the system will automatically 14 | add you to the class & setup your repository for you. 15 |
16 | 17 |7 | Your squeaky clean repository has been created: {{ repo }}. 8 |
9 | 10 |11 | The next thing you will want to do is visit the 12 | Course Setup Guide 13 | and follow the instructions to setup Git. 14 |
15 | 16 |17 | View the rest of the ComS 342 repositories. 18 |
19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /guides/git-and-github.md: -------------------------------------------------------------------------------- 1 | Course Setup 2 | ============ 3 | 4 | This will guide you through understanding how we will be using Git & GitHub for 5 | this course. 6 | 7 | ## Contents 8 | 9 | - [Motivation](#motivation) 10 | - [Learning Git](#learning-git) 11 | - [Setting up GitHub](#setting-up-github) 12 | - [Setting up Git](#setting-up-git) 13 | - [Getting Newly Released Homework](#getting-newly-released-homework) 14 | - [Submitting Your Homework](#submitting-your-homework) 15 | - [Word of Caution](#word-of-caution) 16 | - [Help!](#help) 17 | 18 | ## Motivation 19 | 20 | The motivation for using Git and GitHub for ComS 342 has two parts. 21 | 22 | The first part is a convenience factor for the instrutor and TAs. Not 23 | surprisingly, BlackBoard isn't the most friendly for assignments dealing with 24 | code. With Git, we can very easily clone all your solution repositories and pull 25 | in changes as you add to them. Since we make heavy use of unit testing in this 26 | course, we are able to automate things which really helps us as well. 27 | 28 | GitHub acts as a collection point for Git repositories. It makes it easy to 29 | collaborate and host code all while using Git either in a GUI or on the command 30 | line. It also has a very powerful API that we've made use to make our jobs 31 | easier. 32 | 33 | Secondly, we wish for you to get as much out of this course as possible. In 34 | addition to learning about programming languages, there isn't a single more 35 | powerful tool for programmers that can be learned than a version control system. 36 | 37 | ## Learning Git 38 | 39 | There are numerous guides on using Git that are available. They range from being 40 | interactive ones to just text ones. Find one that works and experiment; making 41 | mistakes and fixing them is a great way to learn. Here is a link to resources 42 | that GitHub suggests: 43 | [https://help.github.com/articles/what-are-other-good-resources-for-learning-git-and-github][resources] 44 | 45 | ## Setting Up GitHub 46 | 47 | Assuming you have a solid enough understanding of Git, it's time to get started 48 | with GitHub. 49 | 50 | 1. If you don't already have an account, sign up for one here: 51 | [https://github.com/join][join]. 52 | 53 | 2. Next you need to join the GitHub Organization that we've created for the 54 | course: [ComS342-ISU][cs342] 55 | 56 | To join it, go to the [ComS 342 Registration](http://cs342.joshldavis.com/) 57 | page and click the **Sign in with GitHub** button. 58 | 59 | This application uses OAuth and it will take you to GitHub where you will 60 | have to give your permission to join it. 61 | 62 | 3. Enter your NetID and you'll be automatically added to the organization and 63 | will have a repository created for you. 64 | 65 | 4. If for whatever reason you can't join the organization, contact Josh Davis, 66 | joshuad@iastate.edu and let him know. 67 | 68 | 5. You should now be apart of the [ComS 342 Organization][cs342] and should have access 69 | to a few different repositories. 70 | 71 | You should also now have a repository setup just for your homework solutions. 72 | This should be located in the ComS342-ISU organization and be called 73 | `hw-answers-