diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96b94f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +node_modules + diff --git a/3-deploy_web_static.py b/3-deploy_web_static.py new file mode 100755 index 0000000..097f319 --- /dev/null +++ b/3-deploy_web_static.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +"""Fab file to archive web_static content""" +from os import remove +from os.path import isfile +from fabric.api import * +from datetime import datetime + + +env.hosts = ['35.245.121.190'] + + +def do_pack(): + """Pack web_static files into archive""" + try: + dt = datetime.now().strftime("%Y%m%d%H%M%S") + local("mkdir -p versions/") + path = local("tar -cvzf versions/tript_{}.tgz tript" + .format(dt)) + return "versions/tript_{}.tgz".format(dt) + except: + return None + + +def do_deploy(archive_path): + """Deploy function for archive to get deployed to servers""" + if not isfile(archive_path): + return False + fileNameExt = archive_path.split('/')[-1] + fileName = fileNameExt.split(".")[0] + result = put(archive_path, '/tmp/{}'.format(fileNameExt)) + if result.failed: + return False + result = sudo("rm -rf /data/tript/releases/{}/".format(fileName)) + if result.failed: + return False + result = sudo("mkdir -p /data/tript/releases/{}/".format(fileName)) + if result.failed: + return False + result = sudo("tar -xzf /tmp/{} -C /data/tript/releases/{}/" + .format(fileNameExt, fileName)) + if result.failed: + return False + result = sudo("rm /tmp/{}".format(fileNameExt)) + if result.failed: + return False + input = "mv /data/tript/releases/{}/tript/*\ + /data/tript/releases/{}/".format(fileName, fileName) + result = sudo(input) + if result.failed: + return False + result = sudo("rm -rf /data/tript/releases/{}/tript" + .format(fileName)) + if result.failed: + return False + result = sudo("rm -rf /data/tript/current") + if result.failed: + return False + result = sudo("ln -s /data/tript/releases/{}/ /data/tript/current" + .format(fileName)) + if result.failed: + return False + print("New version deployed!") + return True + + +def deploy(): + """Call pack and deploy""" + path = do_pack() + if path is None: + return False + do_deploy(path) diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..8dd0f4e --- /dev/null +++ b/backend/app.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +"""Start Flask web app for our TripleTail +website""" + +from flask import Flask, jsonify, render_template, request +from flask_cors import CORS, cross_origin +from user import User + +import requests + +# Flask setup +app = Flask(__name__) +app.url_map.strict_slashes = False +host = '0.0.0.0' +host = '127.0.0.1' +# Cross-Origin Resource sharing +cors = CORS(app, resouces={r"/*": {"origins": "*"}}) + +authorization_base_url = 'https://github.com/login/oauth/authorize' +token_url = 'https://github.com/login/oauth/access_token' +request_url = 'https://api.github.com' + + +def page_not_found(e): + """404 error page for nonexistent routes""" + return jsonify({'error': "Not found"}), 404 + + +@app.route('/', methods=['GET', 'POST']) +def homepage(): + """ + Landing page + """ + return render_template('index.html') + + +@app.route('/testing') +def test(): + userinfo = {'tier': 'tier1', 'username': 'hello', 'name': 'Random Name'} + return render_template('ranking.html', userinfo=userinfo) + + +@app.route('/tier', methods=['GET', 'POST']) +def handle_callback(): + """ + This function helps exchange temporary 'code' value with a permanent + access_token. + """ + + if 'code' in request.args: + payload = { + 'client_id': '926a308eec433f17e3ff', + 'client_secret': '8b3e897ad2ac2e85f558fd96571f6af5eca26511', + 'code': request.args['code'] + } + headers = {'Accept': 'application/json'} + req = requests.post(token_url, params=payload, headers=headers) + resp = req.json() + if 'access_token' in resp: + user_info = User(resp['access_token']) + print(user_info.__dict__) + return render_template('ranking.html', userinfo=user_info.__dict__) + else: + return render_template('index.html') + else: + return render_template('index.html') + +if __name__ == "__main__": + app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True + app.register_error_handler(404, page_not_found) + app.run(host=host) diff --git a/backend/static/images/clogo.png b/backend/static/images/clogo.png new file mode 100644 index 0000000..02afa53 Binary files /dev/null and b/backend/static/images/clogo.png differ diff --git a/backend/static/images/elephant.png b/backend/static/images/elephant.png new file mode 100644 index 0000000..199c6fb Binary files /dev/null and b/backend/static/images/elephant.png differ diff --git a/backend/static/images/image.png b/backend/static/images/image.png new file mode 100644 index 0000000..40e22cf Binary files /dev/null and b/backend/static/images/image.png differ diff --git a/backend/static/images/kangaroo.png b/backend/static/images/kangaroo.png new file mode 100644 index 0000000..36a251e Binary files /dev/null and b/backend/static/images/kangaroo.png differ diff --git a/backend/static/images/lion.png b/backend/static/images/lion.png new file mode 100644 index 0000000..6ca24f3 Binary files /dev/null and b/backend/static/images/lion.png differ diff --git a/backend/static/images/logo.png b/backend/static/images/logo.png new file mode 100644 index 0000000..82e0a26 Binary files /dev/null and b/backend/static/images/logo.png differ diff --git a/backend/static/images/pig.png b/backend/static/images/pig.png new file mode 100644 index 0000000..03d96de Binary files /dev/null and b/backend/static/images/pig.png differ diff --git a/backend/static/images/sloth.png b/backend/static/images/sloth.png new file mode 100644 index 0000000..9480179 Binary files /dev/null and b/backend/static/images/sloth.png differ diff --git a/backend/static/scripts/auth.js b/backend/static/scripts/auth.js new file mode 100644 index 0000000..eb93639 --- /dev/null +++ b/backend/static/scripts/auth.js @@ -0,0 +1,14 @@ +const ready = () => { + login() +} + +const login = () => { + $('.signin .github-signin').click(github_oauth()); + alert("hi") +} +const github_oauth = ()=> { + // Requests an api, which retrieves the auth token + window.location.href = "https://github.com/login/oauth/authorize?client_id=926a308eec433f17e3ff" +} + +document.addEventListener('DOMContentLoaded', ready); diff --git a/backend/static/scripts/design.js b/backend/static/scripts/design.js new file mode 100644 index 0000000..024e56d --- /dev/null +++ b/backend/static/scripts/design.js @@ -0,0 +1,13 @@ +$('.btn-clear').on('click', () => { + if ($('body').hasClass('black')) + { + $('body').removeClass('black'); + $('body').addClass('blue'); + } + else + { + $('body').removeClass('blue'); + $('body').addClass('black'); + } +}); + diff --git a/backend/static/scripts/index.js b/backend/static/scripts/index.js new file mode 100644 index 0000000..085f441 --- /dev/null +++ b/backend/static/scripts/index.js @@ -0,0 +1,114 @@ +import '@babel/polyfill'; + +const toParams = query => { + const q = query.replace(/^\??\//, ''); + + return q.split('&').reduce((values, param) => { + const [key, value] = param.split('='); + + values[key] = value; + + return values; + }, {}); +}; + +const toQuery = (params, delimiter = '&') => { + const keys = Object.keys(params); + + return keys.reduce((str, key, index) => { + let query = `${str}${key}=${params[key]}`; + + if (index < keys.length - 1) { + query += delimiter; + } + + return query; + }, ''); +}; + +class PopupWindow { + constructor( + params, + options = { height: 1000, width: 600 }, + url = `https://github.com/login/oauth/authorize`, + id = 'github-oauth-authorize' + ) { + this.id = id; + this.url = url + '?' + toQuery(params); + this.options = options; + } + + open() { + const { url, id, options } = this; + + this.window = window.open(url, id, toQuery(options, ',')); + } + + close() { + this.cancel(); + this.window.close(); + } + + poll() { + this.promise = new Promise((resolve, reject) => { + this._iid = window.setInterval(() => { + try { + const popup = this.window; + + if (!popup || popup.closed !== false) { + this.close(); + + reject(new Error('The popup was closed')); + + return; + } + + if ( + popup.location.href === this.url || + popup.location.pathname === 'blank' + ) { + return; + } + + const params = toParams(popup.location.search.replace(/^\?/, '')); + + resolve(params); + + this.close(); + } catch (error) {} + }, 500); + }); + } + + cancel() { + if (this._iid) { + window.clearInterval(this._iid); + this._iid = null; + } + } + + then(...args) { + return this.promise.then(...args); + } + + catch(...args) { + return this.promise.then(...args); + } + + static open(...args) { + const popup = new this(...args); + + popup.open(); + popup.poll(); + + return popup; + } +} + +export const loginWithGithub = (params, options, url, id) => { + return new Promise(async (resolve, reject) => { + const popup = PopupWindow.open(params, options, url, id); + + popup.then(resolve, reject); + }); +}; diff --git a/backend/static/scripts/ranking.js b/backend/static/scripts/ranking.js new file mode 100644 index 0000000..ce163d8 --- /dev/null +++ b/backend/static/scripts/ranking.js @@ -0,0 +1,11 @@ +$('.back').css('visibility', 'hidden'); + +$('#rotatefront').click(() => { + $('.front').css('visibility', 'hidden'); + $('.back').css('visibility', 'visible'); +}); + +$('#rotateback').click(() => { + $('.back').css('visibility', 'hidden'); + $('.front').css('visibility', 'visible'); +}); diff --git a/backend/static/styles/index.css b/backend/static/styles/index.css new file mode 100644 index 0000000..18643da --- /dev/null +++ b/backend/static/styles/index.css @@ -0,0 +1,8 @@ +h1#logo a { + max-width: 80%; + height: 80%; + display: block; + margin-left: auto; + margin-right: auto; + width: 50%; +} diff --git a/backend/static/styles/layout.css b/backend/static/styles/layout.css new file mode 100644 index 0000000..c533e5f --- /dev/null +++ b/backend/static/styles/layout.css @@ -0,0 +1,45 @@ +html { + width: 100%; + height: 100%; +} + +body { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.header { + margin: 1rem; + box-sizing: border-box; + font-family: Avenir; + color: #6ec4db; + position: relative; + display: flex; + flex-wrap: nowrap; +} + +body.black { + background-color: black; +} + +body.blue { +} + +.btn-clear { + border: none; + background: transparent; +} + +.content { + display: flex; + justify-content: center; + width: 100%; + height: 85%; +} + +.facebook-button, .twitter-button { + display: inline-block; +} diff --git a/backend/static/styles/ranking.css b/backend/static/styles/ranking.css new file mode 100644 index 0000000..00cd8f3 --- /dev/null +++ b/backend/static/styles/ranking.css @@ -0,0 +1,75 @@ +.my-card { + background-color: white; + max-width: 80%; + height: 80%; + width: auto; + padding: 1.5rem; +} + +.front-side { + display: flex; + flex-direction: row; + padding: 1rem; +} + +.front-side-body { + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.face { + position: relative; + height: 100%; +} + +.user-image { + width: 40%; + height: 90%; + background-size: contain; + padding-right: 2rem; +} + +.front { + position: relative; +} + +.back { + position: absolute; + left: 0; + top: 0%; + width: 100%; + height: 100%; +} + +.card { + width: 75%; + border: 5px solid rgba(110, 196, 219, 1); + padding: 1rem; +} + +.card-body { + padding: 1rem; + width: 100%; + height: 100%; +} + +.rotate-button { + position: absolute; + bottom: 2%; + transform: translate(-50%, -50%); + left: 50%; +} + +.avatar-git { + height: 40%; +} + +.avatar-animal { + height: 60%; +} + +.img { + max-height: 100%; + max-width: 100%; +} diff --git a/backend/templates/base.html b/backend/templates/base.html new file mode 100644 index 0000000..14015f7 --- /dev/null +++ b/backend/templates/base.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + {% block css %}{% endblock %} + {% block title %}{% endblock %} - Triple Tail + + +
+ +
+
+ {% block log_out %}{% endblock %} +
+ + +
+ +
+
+
+
+
+ {% block header %}{% endblock %} +
+ + {% block content %}{% endblock %} + + + + + + + + {% block script %}{% endblock %} + + diff --git a/backend/templates/index.html b/backend/templates/index.html new file mode 100644 index 0000000..e7ba45e --- /dev/null +++ b/backend/templates/index.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block css %} + +{% endblock %} + +{% block content %} +
+

+ cropped logo +

+ +
+{% endblock %} + + +{% block script %} + + +{% endblock %} diff --git a/backend/templates/ranking.html b/backend/templates/ranking.html new file mode 100644 index 0000000..7aa2e8c --- /dev/null +++ b/backend/templates/ranking.html @@ -0,0 +1,76 @@ +{% extends 'base.html' %} +{% block css %} + +{% endblock %} + +{% block log_out %} +
Log Out
+{% endblock %} +{% block content %} + +
+ + +
+ + +
Sample avatar image. +
+ + +
+

{{ userinfo.name }}

+

{{ userinfo.username }}

+

Public Repos: {{ userinfo.public_repos }}

+

Followers: {{ userinfo.followers }}

+

Hireable: {{ userinfo.hireable }}

+ + +
+
+ + + +
+
+ {% if userinfo.tier == 'O(n!)' %} +
+

About This Rank

+
+
Rank
+

O(n!)

+

This mighty sloth is on its way to greatness

+ + {% elif userinfo.tier == 'O(n)' %} +
+

Rank

+
+
Time Complexity
+

O(n)

+

Mediocrity is the elephant in the room

+ + {% else %} +
+

Rank

+
+
Time Complexity
+

O(1)

+

Long live the king!

+ {% endif %} + + + +
+
+ +
+ +{% endblock %} + +{% block script %} + +{% endblock %} diff --git a/backend/user.py b/backend/user.py new file mode 100755 index 0000000..3a28687 --- /dev/null +++ b/backend/user.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +""" +this module contains a User class +""" + + +from datetime import datetime, timedelta +import re +import requests + + +class User: + """a User class""" + url = 'https://api.github.com' + tiers = {'tier1': 'O(n!)', 'tier2': 'O(n)', 'tier3': 'O(1)'} + + def __init__(self, token): + """instantiation of User class object""" + self.token = token + self.user_dict = self.user_dict() + self.username = self.username() + self.public_repos = self.public_repos() + self.followers = self.followers() + self.account_age = self.account_age() + self.readme_pct = self.readme_pct() + self.tier = self.tier() + self.avatar_url = self.avatar_url() + self.following = self.following() + self.bio = self.bio() + self.hireable = self.hireable() + self.name = self.name() + self.email = self.email() + + def user_dict(self): + """creates dictionary of user's info""" + header = {'Authorization': 'Bearer {}'.format(self.token)} + r = requests.get('https://api.github.com/user', headers=header) + allowed = ['public_repos', 'followers'] + return r.json() + + def public_repos(self): + """returns number of public repos of a user""" + return self.user_dict['public_repos'] + + def followers(self): + """returns number of followers of a user""" + return self.user_dict['followers'] + + def account_age(self): + """calculates and returns age of the account""" + self.now = datetime.now() + self.created_at = datetime.strptime(self.user_dict['created_at'], + "%Y-%m-%dT%H:%M:%SZ") + age = self.now - self.created_at + age_str = str(age) + age_str_split = re.split('[, :\.]*', age_str) + if len(age_str_split) > 5: + age_dhms = age_str_split[0] \ + + ' ' + age_str_split[1] \ + + ' ' + age_str_split[2] \ + + ' hours ' + age_str_split[3] \ + + ' minutes ' + age_str_split[4] \ + + ' seconds' + else: + age_dhms = '0 days ' \ + + age_str_split[0] \ + + ' hours ' + age_str_split[1] \ + + ' minutes ' + age_str_split[2] \ + + ' seconds' + return age_dhms + + def readme_pct(self): + """Returns the percentage of repos that have a README""" + header = {'Authorization': 'Bearer {}'.format(self.token)} + repo_list = requests.get('{}/users/{}/repos' + .format(self.url, self.username), + headers=header).json() + readme_count = 0 + if self.user_dict.get('public_repos') is 0: + return 0 + for repo in repo_list: + response = requests.get('{}/repos/{}/{}/readme' + .format(self.url, + self.username, + repo.get('name')), + headers=header) + if response.status_code == 404: + pass + else: + readme_count += 1 + return '{:.1%}'.format(readme_count/self.user_dict.get('public_repos')) + + def tier(self): + """returns the tier of the user""" + acc_age_str_split = self.account_age.split() + days_old = acc_age_str_split[0] + if self.followers > 4 and \ + self.public_repos > 10: + return User.tiers['tier3'] + elif self.followers > 0 and \ + self.public_repos > 4: + return User.tiers['tier2'] + else: + return User.tiers['tier1'] + + def avatar_url(self): + """returns url of user's avatar""" + return self.user_dict['avatar_url'] + + def username(self): + """returns username""" + return self.user_dict['login'] + + def following(self): + """returns number of users the user is following""" + return self.user_dict['following'] + + def bio(self): + """get users bio""" + return self.user_dict['bio'] + + def hireable(self): + """status on whether user is hireable""" + return self.user_dict['hireable'] + + def name(self): + """users name on github""" + return self.user_dict['name'] + + def email(self): + """get users email""" + return self.user_dict['email'] diff --git a/package.json b/package.json new file mode 100644 index 0000000..4668a55 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "tripletail", + "version": "1.0.0", + "description": "Derrick's README", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/stak21/TripleTail.git" + }, + "author": "Shoji Takashima, Sammie Azad, Susan Su, Phu Truong, Stef Silverio, Derrick Gee, Wendy Leung", + "license": "MIT", + "bugs": { + "url": "https://github.com/stak21/TripleTail/issues" + }, + "homepage": "https://github.com/stak21/TripleTail#readme", + "dependencies": { + "simple-crypto-js": "^2.0.2" + } +} diff --git a/requirements.txt b/requirements.txt index bc972d3..6463ec1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Fabric3==1.14.post1 Python3==3.4.3 Flask==1.0.2 +beautifulsoup4==4.7.1 diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..324e089 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + Triple Tail + + +
+ +
+ + + + + + +