Skip to content
Browse files

Initial Touchpoint work

Add gitignore

add helloworld

Initial Touchpoint work

homepublic navbar created

homepublic navbar created

signup text fields added

created password field and fixed bg for large widths

moved homepublic files into static and template folders

Rename template

Add basic routes and RH stubs

Add static/temp settings; templated homepublic

Add db conn; refactor Application, handlers

.gitignore added .idea

email validation

refactor email validation

Fix content-type

Refactor code out of main; add db module

remove valid_suffixes injection from _validate_email_suffix

Add on more to signup API

Don't salt on client, fix SQL query

Check for duplicate email

Add logging to file

Protect from SQL injection

Add http_assert

added Part 1 of signup detail process

Part 1 signup detail process - rev1

style templates for select, input, button

initial wireframe for signup part 2

signup details part 2 wireframe complete

signup details part 3 wireframes complete

Remove main.py

Make SignUpDetails views visible

generic_query method for asserting parameterized queries

Enable gzip (smaller data footprint, more cpu cycles for compression)

Use bcrypt instead of sha512

Add dataset connection to db; make touchpoint.py executable

snake_case db fields

Add JSendMixin, override write_error

Implement write_error override, use JSend, implement APIError

Make auto_routes actually automatic

log location; explicit class reference

Update README.md

Update README.md

Create requirements.txt

Create bootstrapdb.sql

Create install.bash

Add Getting Started to README
  • Loading branch information...
1 parent 6a3f18b commit e967e7b8f90502ec9e12bdd3344b1a103778dfe1 @hfaran committed Sep 11, 2013
View
22 .gitignore
@@ -0,0 +1,22 @@
+*.mo
+*.egg-info
+*.egg
+*.EGG
+*.EGG-INFO
+bin
+build
+develop-eggs
+downloads
+eggs
+fake-eggs
+parts
+dist
+.installed.cfg
+.mr.developer.cfg
+.hg
+.bzr
+.svn
+*.pyc
+*.pyo
+*.tmp*
+.idea
View
10 README.md
@@ -1 +1,9 @@
-README
+## Getting Started
+ ./install.bash
+ sudo mkdir /var/log/touchpoint
+ sudo ./touchpoint.py
+ # Visit localhost:8888
+
+## Places to visit
+* [Touchpoint Trello Board](https://trello.com/b/ZfdpE4q5/project-touchpoint-website)
+* [An instance of `dev` branch hosted on Hamza's droplet](http://192.241.246.65:8888)
View
15 bootstrapdb.sql
@@ -0,0 +1,15 @@
+# Create a DB called **touchpoint**
+ CREATE DATABASE touchpoint;
+
+# Create a user called **touchpt-dev** with password 'pooltable' and grant full permissions
+ CREATE USER 'touchpt-dev'@'localhost' IDENTIFIED BY 'pooltable';
+ GRANT ALL PRIVILEGES ON *.* TO 'touchpt-dev'@'localhost' WITH GRANT OPTION;
+
+# Create and populate DB tables
+ USE touchpoint;
+
+ CREATE TABLE email_suffixes ( id INT NOT NULL AUTO_INCREMENT, suffix varchar(255) NOT NULL, PRIMARY KEY (id) );
+
+ INSERT INTO email_suffixes (suffix) VALUES ("ubc.ca");
+
+ CREATE TABLE persons (id INT NOT NULL AUTO_INCREMENT, first varchar(255) NOT NULL, last varchar(255) NOT NULL, password varchar(512) NOT NULL, salt varchar(255) NOT NULL, email varchar(255) NOT NULL, karma INT NOT NULL, time INT NOT NULL, PRIMARY KEY (id));
View
17 install.bash
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+
+# Install any system packages (Ubuntu/Debian only) that are pre-requisites
+echo -e "\n\nInstalling required system packages . . ."
+sudo apt-get install libffi-dev
+
+# Install all required Python packages
+echo -e "\n\nInstalled required Python packages with pip . . ."
+sudo pip install -r requirements.txt
+
+# Bootstrap the DB
+echo -e "\n\nAbout the boostrap the DB; please enter your MySQL root user password"
+mysql -uroot -p < bootstrapdb.sql
+
+echo -e "\n\nBootstrapping complete."
+
View
7 requirements.txt
@@ -0,0 +1,7 @@
+MySQL-python
+py_email_validation
+tornado
+dataset
+torndb
+# libffi-dev is a prerequisite package for bcrypt on Debian/Ubuntu
+bcrypt
View
4,601 static/bootstrap.css
4,601 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
14 static/homepublic.js
@@ -0,0 +1,14 @@
+/* ===============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: signup.css
+ * Contains jquery for signup process.
+ * =============================== */
+
+$(window).ready(function(){
+ $('.viewport').css('height',window.innerHeight());
+});
+
+$(window).resize(function(){
+ $('.viewport').css('height',window.innerHeight());
+});
View
331 static/signup.css
@@ -0,0 +1,331 @@
+/* ===============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: signup.css
+ * Contains styles for the signup process.
+ * =============================== */
+
+/* ===== HEADER BAR + LOGO ===== */
+.headerBar {
+ margin: 0 auto;
+ position: fixed;
+ top:0px;
+ width: 100%;
+ height: 50px;
+ background-color: #EFEDDF;
+ padding-left: 10px;
+ padding right: 10px;
+ padding-top: 5px;
+
+ -webkit-box-shadow: 0px 2px 4px rgba(50, 50, 50, 0.38);
+ -moz-box-shadow: 0px 2px 4px rgba(50, 50, 50, 0.38);
+ box-shadow: 0px 2px 4px rgba(50, 50, 50, 0.38);
+}
+
+.signinArea{
+ float: right;
+ padding-right: 20px;
+}
+
+.logo {
+ margin: 5px;
+ float: left;
+ text-align: center;
+}
+
+a.logo{
+ padding: 0px;
+ margin:0px;
+}
+a.logo:hover{
+ text-decoration: none;
+}
+/* ============================ */
+
+/* ====== NATIVE SIGNIN ======= */
+.emailaddress {
+ margin: 5px 10px 5px 5px !important;
+ height: 25px;
+ float: left;
+ border: 1px solid #51CD7B;
+ background-color: #ffffff;
+ width: 130px;
+ font-size: 11px;
+ padding: 3px 12px;
+ font-weight: 300;
+ color: #9B9B9B;
+ vertical-align: middle;
+ border-radius: 5px;
+}
+
+button.loginButton {
+ height: 30px;
+ width: 70px;
+ margin: 5px 10px 5px 5px;
+ float: left;
+ font-size: 11px;
+ line-height: 19px;
+ padding: 0;
+}
+
+button.signupButton {
+ position: absolute; /*within relative container*/
+ margin: auto;
+ left: 0; right: 0;
+ width: 150px;
+ margin-top: 10px;
+ margin-bottom: 0px;
+ padding: 3px 0px;
+ font-size: 13px;
+ line-height: 22px;
+ letter-spacing: 2px;
+ font-weight: bold;
+}
+
+button.signupNextButton {
+ /* position from button#id */
+ width: 80px;
+ padding: 3px 0px;
+ font-size: 13px;
+ line-height: 22px;
+}
+
+button#next, button#prev {
+ position: absolute;
+ margin: auto;
+ left: 0; right: 0;
+}
+
+button#prevNext {
+ float: left !important;
+ margin-right: 5px;
+ margin-left: 5px;
+}
+/* ============================ */
+
+/* ======== BACKGROUND ======== */
+@media (max-width: 1561px){
+ img.background{
+ left: 50%;
+ margin-left: -780px;
+ bottom:0px;
+ min-width: 1561px;
+ min-height: 966px;
+ width: 100%;
+ vertical-align: bottom;
+ }
+}
+img.background{
+ z-index: -1;
+ height:100%;
+ position: fixed;
+ opacity: .7;
+ width: 100%;
+}
+/* ============================ */
+
+/* ====== NATIVE SIGNUP ======= */
+p.signupCaption {
+ font-family: Georgia;
+ font-size: 22px;
+ font-weight: 500;
+ padding: 0px;
+ text-align: center;}
+
+p.signupSubCaption {
+ font-family: Georgia;
+ font-size: 14px;
+ font-weight: 300px;
+ padding: 0px;
+ text-align: center;}
+
+.signupArea {
+ margin: 0 auto;
+ width: 287px;
+ padding: 20px;
+ z-index: 10;
+}
+
+.firstLastName{
+ width: 250px;
+ padding: 0px;
+}
+
+input.signupForm {
+ z-index: 10;
+}
+
+input#firstName, input#lastName {
+ width: 121px;
+ float: left;
+ margin-left: 2px;
+ margin-right: 2px;
+}
+input#ubcEmail, input#password1, input#password2 {
+ width: 250px;
+}
+/* ============================ */
+
+/* ===== FACEBOOK SIGNIN ====== */
+button.facebook {
+ background: #6e86bd;
+ background: -moz-linear-gradient(top, #6e86bd 0%, #6680b9 100%);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#6e86bd), to(#6680b9));
+
+ border-radius: 6px;
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+
+ box-shadow: inset 0 1px 0 0 #abbbdf;
+ -moz-box-shadow: inset 0 1px 0 0 #abbbdf;
+ -webkit-box-shadow: inset 0 1px 0 0 #abbbdf;
+
+ border: 1px solid #3f5b98;
+ color: #fff;
+ font-size: 1.6em;
+ font-weight: bold;
+ line-height: 1;
+ padding: 8px 0 7px 0;
+ text-align: center;
+ text-shadow: 0 -1px 1px #344d80;
+ width: 250px;
+ margin: 10px 0px;
+}
+
+button.facebook:hover {
+ background: #546a99;
+ background: -moz-linear-gradient(top, #546a99 0%, #546a99 100%);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#546a99), to(#546a99));
+
+ box-shadow: inset 0 1px 0 0 #98a8d3;
+ -moz-box-shadow: inset 0 1px 0 0 #98a8d3;
+ -webkit-box-shadow: inset 0 1px 0 0 #98a8d3;
+
+ text-shadow: 0 -1px 1px #283960;
+ color: #fff;
+ cursor: pointer;
+}
+
+button.facebook:active {
+ border: 1px solid #283960;
+ box-shadow: inset 0 0 4px 2px #51658b, 0 0 1px 0 #eee;
+ -moz-box-shadow: inset 0 0 4px 2px #51658b, 0 0 1px 0 #eee;
+ -webkit-box-shadow: inset 0 0 4px 2px #51658b, 0 0 1px 0 #eee;
+}
+/* ============================ */
+
+/* ======= SCHOOL INFO ======== */
+.signupDetailBG{
+ width: 450px;
+ height: 60%;
+ margin: auto;
+ left: 0; right: 0; bottom: 0; top: 0;
+ background: #EFEDDF;
+ border-radius: 5px;
+ border: 3px solid #F15E52;
+ z-index: 10;
+ position: absolute;
+ opacity: 0.9;
+}
+.signupDetailsOutter{
+ position: absolute;
+ width: 50%;
+ height: 50%;
+ margin: auto;
+ left: 0; right: 0; bottom: 0; top: 0;
+ z-index: 100;
+ padding: 20px;
+}
+
+.signupDetailsInner{
+ width: 250px;
+ margin: 0 auto;
+ padding-top: 25px;
+ padding-bottom: 25px;
+ z-index: 110;
+}
+
+select.schoolInfo,
+input.schoolInfo {
+ width: 250px;
+}
+
+div.prevNext {
+ margin: auto;
+ left: 0; right: 0;
+ width: 180px;
+}
+/* ============================ */
+
+/* ======= EXPERIENCE ========= */
+div#signupExperience {
+ width: 280px !important;
+ height: 150px !important;
+}
+
+input.companyInput {
+ float: left;
+}
+input[type="text"].companyInput {
+ float: left !important;
+ width: 240px;
+}
+input[type="checkbox"].companyCheck {
+ float: left !important;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ height: 20px;
+ width: 20px;
+ display:none;
+}
+
+label.companyCheck{
+ margin: 9px 15px 0px 0px;
+ float: left;
+ font-size: 18px;
+}
+
+label.companyCheck:before{
+ width: 18px;
+ height: 18px;
+ background-color: #ffffff;
+ border-radius: 3px;
+ border: 2px solid #51CD7B;
+}
+
+/* ============================ */
+
+/* ========== SKILLS ========== */
+div#signupSkills {
+ width: 420px !important;
+ height: 150px !important;
+}
+
+div.checkboxHeight {
+ height: 32px !important;
+}
+
+input[type="checkbox"].skillCheck {
+ float: left !important;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ height: 18px;
+ width: 18px;
+}
+
+label.skillName {
+ float: left;
+ font: Open Sans, Arial;
+ font-size: 14px;
+ font-weight: 400;
+ vertical-align: top;
+}
+label.skillName:before{
+ width: 18px;
+ height: 18px;
+ background-color: #ffffff;
+ border-radius: 3px;
+ border: 2px solid #51CD7B;
+ line-height: 22px;
+}
+/* ============================ */
View
18 static/signup.js
@@ -0,0 +1,18 @@
+/* ===============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: signup.css
+ * Contains jquery for home-public page.
+ * =============================== */
+
+// height of viewport (minus the headerBar) is used
+// for absolute height/width centering
+$(window).ready(function(){
+ viewportHeight = $(window).innerHeight() - $('.headerBar').height();
+ $('.viewport').css('height',viewportHeight);
+});
+
+$(window).resize(function(){
+ viewportHeight = $(window).innerHeight() - $('.headerBar').height();
+ $('.viewport').css('height',viewportHeight);
+});
View
186 static/style.css
@@ -0,0 +1,186 @@
+/* ===============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: style.css
+ * Contains general styles ie. body, h1, p, etc.
+ * =============================== */
+html, body {
+ font-family: Open Sans, Arial, sans-serif;
+ margin: 0px;
+ padding: 0px;
+ margin-top: 0px;
+ overflow-x: hidden !important;
+ position: relative !important;
+}
+h1 {
+ font-family: Georgia;
+ font-weight: 500;
+ font-size: 22px;
+ line-height: 22px;
+ color: #4A4A4A;
+ padding: 0px;
+ text-align: center;}
+
+h1.orange{
+ color: #F15E52;
+ margin: 0 auto;
+ padding: 3px;}
+
+h2 {
+ font-family: Georgia;
+ font-weight: 600;
+ font-size: 17px;
+ color: #4A4A4A;
+ line-height: 20px;}
+
+h3 {
+ font-weight: 400;
+ color: #9B9B9B;
+ font-family: Open Sans;
+ font-size: 16px;
+ line-height: 25px;}
+
+h4 {
+ font-weight: 400;
+ font-size: 15px;
+ color: #4A4A4A;
+ line-height: 23px;}
+
+h5 {
+ font-family: Georgia;
+ font-weight:400;
+ font-size: 15px;
+ color:#4A4A4A;
+ line-height: 15px;
+ margin-top: 10px;
+ margin-bottom: 10px;}
+
+p { font-size: 13px; }
+
+@media (min-width: 900px){
+ .contain900px {
+ margin: 0 auto;
+ width: 900px;
+ }
+}
+
+.spacer50 {
+ height: 50px;
+}
+.spacer100 {
+ height: 100px;
+}
+
+.viewport {
+ width: 100%;
+/* height: 100%; from jquery */
+ position: relative;
+}
+
+/* ============================ */
+
+/* ========== Dropdown, Text Fields - Green border theme ========== */
+select, input[type="text"], input[type="password"] {
+ border: 2px solid #51CD7B;
+ background-color: #ffffff;
+ font-size: 13px;
+ padding: 3px 12px;
+ font-weight: 300;
+ color: #000000;
+ vertical-align: middle;
+ border-radius: 5px;
+ opacity: 1.0;
+ margin: 5px 0px;
+ height: 30px;
+ font-family: Open Sans;
+ display: block;
+
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ -ms-appearance: none;
+ -o-appearance: none;
+ appearance: none;
+}
+
+::-webkit-input-placeholder { /* WebKit browsers */
+ color: #9B9B9B;
+}
+:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
+ color: #9B9B9B;
+}
+::-moz-placeholder { /* Mozilla Firefox 19+ */
+ color: #9B9B9B;
+}
+:-ms-input-placeholder { /* Internet Explorer 10+ */
+ color: #9B9B9B;
+}
+
+select {
+ text-indent: 0.01px; /* hack to remove firefox dropdown arrow */
+ text-overflow: ''; /* hack to remove firefox dropdown arrow */
+}
+
+select {
+ background: url("http://i.imgur.com/b060GpA.png?2?3374") right; /* dropdown arrow */
+ background-size: auto 25%;
+ background-repeat: no-repeat;
+ background-color: #ffffff;
+}
+/* ============================ */
+
+/* ========== Checkbox - Green border theme ========== */
+label {
+ display: inline-block;
+ cursor: pointer;
+ /* specify margins and position in specific id/class */
+}
+
+input[type=checkbox] {
+ display: none;
+}
+label:before {
+ content: "";
+ display: inline-block;
+}
+input[type=checkbox]:checked + label:before {
+ content: "\2713";
+ font-size: 18px;
+ color: #000;
+ text-align: center;
+ vertical-align: top;
+ line-height: 1;
+}
+/* ============================ */
+
+/* ========== Green Button ========== */
+button.greenButton {
+ font-family: Open Sans;
+ font-weight: 400;
+ color: #ffffff;
+ background-color: #51CD7B;
+ border: 3px solid #51CD7B;
+ border-radius: 5px;
+ text-transform: uppercase;
+ text-align: center;
+ vertical-align: middle;
+
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ transition: all .35s ease-in-out;
+ -moz-transition: all .35s ease-in-out;
+ -webkit-transition: all .35s ease-in-out;
+}
+
+button.greenButton:hover {
+ background-color: #4A4A4A;
+ border: 3px solid #4A4A4A;
+}
+
+button.greenButton:active {
+ background-color: #4A4A4A;
+}
View
55 templates/homepublic.html
@@ -0,0 +1,55 @@
+<!-- =============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: homepublic.css
+ * Home-public page with signup/signin.
+ * =============================== -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Touchpoint</title>
+ <link rel="stylesheet" type="text/css" href="{{ static_url("signup.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("bootstrap.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("style.css") }}">
+ <script type="text/javascript" src="{{ static_url("signup.js") }}"></script>
+ <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+
+ <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,300' rel='stylesheet' type='text/css'>
+</head>
+
+<body>
+ <div class="headerBar">
+ <div class="contain900px">
+ <div class="logo">
+ <a class="logo" href="/"><h1 class="orange">touch&middot;point</h1></a>
+ </div>
+ <div class="signinArea">
+ <input type="text" class="emailaddress" placeholder="email">
+ <input type="password" class="emailaddress" placeholder="password">
+ <button class="greenButton loginButton">LOG IN</button>
+ </div>
+ </div>
+ </div>
+
+ <div class="background">
+ <img class="background" src="http://i.imgur.com/5Uz0vmKh.jpg">
+ </div>
+ <div class="spacer100"></div>
+ <p class="signupCaption">New to Touchpoint? Join us.</p>
+ <div class="signupArea">
+ <div class="firstLastName">
+ <input type="text" id="firstName" class="signupForm" placeholder="first name">
+ <input type="text" id="lastName" class="signupForm" placeholder="last name">
+ </div>
+ <input type="text" id="ubcEmail" class="signupForm" placeholder="ubc email">
+ <input type="password" id="password1" class="signupForm" placeholder="password">
+ <input type="password" id="password2" class="signupForm" placeholder="re-enter password">
+ <button class="greenButton signupButton">SIGN UP!</button>
+ <div class="spacer50"></div>
+ <p class="signupCaption">or</p>
+ <button class="facebook">Login through Facebook</button>
+ </div>
+</body>
+</html>
View
74 templates/signup_part1.html
@@ -0,0 +1,74 @@
+<!-- =============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: signup_part1.html
+ * Part 1 of sign up process (school info)
+ * =============================== -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Touchpoint</title>
+ <link rel="stylesheet" type="text/css" href="{{ static_url("signup.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("bootstrap.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("style.css") }}">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script type="text/javascript" src="{{ static_url("signup.js") }}"></script>
+
+
+ <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,300' rel='stylesheet' type='text/css'>
+</head>
+
+<body>
+ <div class="background">
+ <img class="background" src="http://i.imgur.com/5Uz0vmKh.jpg">
+ </div>
+
+ <div class="headerBar">
+ <div class="contain900px">
+ <div class="logo">
+ <a class="logo" href="/"><h1 class="orange">touch&middot;point</h1></a>
+ </div>
+ </div>
+ </div>
+
+ <div class="viewport">
+ <div class="signupDetailBG"></div>
+ <div class="signupDetailsOutter">
+ <p class="signupCaption">What are you studying in school?</p>
+ <div class="signupDetailsInner">
+ <select class="schoolInfo">
+ <option>Program</option>
+ <option>Undergraduate (Bachelor's)</option>
+ <option>Graduate (Master's)</option>
+ </select>
+
+ <input type="text" class="schoolInfo" placeholder="Year">
+
+ <select class="schoolInfo">
+ <option>Department</option>
+ <option>Computer Science</option>
+ <option>Electrical &amp; Computer Engineering</option>
+ </select>
+
+ <select class="schoolInfo">
+ <option>Specialization</option>
+ <optgroup label="Electrical Engineering">
+ <option>General Electrical</option>
+ <option>Energy Systems Option</option>
+ <option>Biomedical Option</option>
+ <option>Nanosystems</option>
+ </optgroup>
+ <optgroup label="ComputerEngineering">
+ <option>General Computer</option>
+ <option>Software Option</option>
+ </optgroup>
+ </select>
+ </div>
+ <button id="next" class="greenButton signupNextButton">Next</button>
+ </div>
+ </div>
+ </div>
+
+</body>
+</html>
View
61 templates/signup_part2.html
@@ -0,0 +1,61 @@
+<!-- =============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: signup_part2.html
+ * Part 2 of sign up process (job experience)
+ * =============================== -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Touchpoint</title>
+ <link rel="stylesheet" type="text/css" href="{{ static_url("signup.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("bootstrap.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("style.css") }}">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script type="text/javascript" src="{{ static_url("signup.js") }}"></script>
+
+
+ <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,300' rel='stylesheet' type='text/css'>
+</head>
+
+<body>
+ <div class="background">
+ <img class="background" src="http://i.imgur.com/5Uz0vmKh.jpg">
+ </div>
+
+ <div class="headerBar">
+ <div class="contain900px">
+ <div class="logo">
+ <a class="logo" href="/"><h1 class="orange">touch&middot;point</h1></a>
+ </div>
+ </div>
+ </div>
+
+ <div class="viewport">
+ <div class="signupDetailBG"></div>
+ <div class="signupDetailsOutter">
+ <p class="signupCaption">Tell us your experience.</p>
+ <p class="signupSubCaption">(check the box if you got an offer from that company)</p>
+ <!-- FIXME rwong: add a "why?" hover bubble to explain the purpose of this -->
+ <div id="signupExperience" class="signupDetailsInner">
+ <input id="checker1" class="companyCheck" type="checkbox">
+ <label class="companyCheck" for="checker1"></label>
+
+ <input type="text" class="companyInput" placeholder="Company">
+
+ <input id="checker2" class="companyCheck" type="checkbox">
+ <label class="companyCheck" for="checker2"></label>
+
+ <input type="text" class="companyInput" placeholder="Company">
+ </div>
+ <div class="prevNext">
+ <button id="prevNext" class="greenButton signupNextButton">Prev</button>
+ <button id="prevNext" class="greenButton signupNextButton">Next</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+</body>
+</html>
View
72 templates/signup_part3.html
@@ -0,0 +1,72 @@
+<!-- =============================
+ * Touchpoint Copyright 2013.
+ * @author: Ryan Wong
+ * @file: signup_part3.html
+ * Part 3 of sign up process (suggested skills)
+ * =============================== -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Touchpoint</title>
+ <link rel="stylesheet" type="text/css" href="{{ static_url("signup.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("bootstrap.css") }}">
+ <link rel="stylesheet" type="text/css" href="{{ static_url("style.css") }}">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script type="text/javascript" src="{{ static_url("signup.js") }}"></script>
+
+
+ <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,300' rel='stylesheet' type='text/css'>
+</head>
+
+<body>
+ <div class="background">
+ <img class="background" src="http://i.imgur.com/5Uz0vmKh.jpg">
+ </div>
+
+ <div class="headerBar">
+ <div class="contain900px">
+ <div class="logo">
+ <a class="logo" href="/"><h1 class="orange">touch&middot;point</h1></a>
+ </div>
+ </div>
+ </div>
+
+ <div class="viewport">
+ <div class="signupDetailBG"></div>
+ <div class="signupDetailsOutter">
+ <p class="signupCaption">Here are some suggested skills/topics.</p>
+ <p class="signupSubCaption">This will help us deliver content relevant to you.</p>
+ <div id="signupSkills" class="signupDetailsInner">
+ <div class="col-4 checkboxHeight">
+ <input type="checkbox" id="checker1" class="skillCheck">
+ <label for="checker1" class="skillName"> Internships</label>
+ </div>
+ <div class="col-4 checkboxHeight">
+ <input type="checkbox" id="checker2" class="skillCheck">
+ <label for="checker2" class="skillName"> Hardware</label>
+ </div>
+ <div class="col-4 checkboxHeight">
+ <input type="checkbox" id="checker3" class="skillCheck">
+ <label for="checker3" class="skillName"> Tech Startups</label>
+ </div>
+ <div class="col-4 checkboxHeight">
+ <input type="checkbox" id="checker4" class="skillCheck">
+ <label for="checker4" class="skillName"> Software</label>
+ </div>
+ <div class="col-4 checkboxHeight">
+ <input type="checkbox" id="checker5" class="skillCheck">
+ <label for="checker5" class="skillName"> Robotics</label>
+ </div>
+ <div class="col-4 checkboxHeight">
+ <input type="checkbox" id="checker6" class="skillCheck">
+ <label for="checker6" class="skillName"> Unix/Linux</label>
+ </div>
+ </div>
+ <button id="prev" class="greenButton signupNextButton">Prev</button>
+ </div>
+ </div>
+ </div>
+
+</body>
+</html>
View
7 touchpoint.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python2.7
+
+import sys
+from touchpoint.main import main
+
+if __name__ == '__main__':
+ main(sys.argv)
View
1 touchpoint/__init__.py
@@ -0,0 +1 @@
+
View
86 touchpoint/db.py
@@ -0,0 +1,86 @@
+import torndb
+import dataset
+import time
+
+from tornado.options import define, options
+
+
+define("mysql_host", default="127.0.0.1:3306")
+define("mysql_database", default="touchpoint")
+define("mysql_user", default="touchpt-dev")
+define("mysql_password", default="pooltable")
+
+
+class Connection(object):
+
+ def __init__(self):
+ """Create torndb and dataset connections to database
+
+ * torndb should be used for custom SQL queries
+ * dataset is encouraged for simple find and insert queries
+ """
+ self.__db_torndb = torndb.Connection(
+ host=options.mysql_host,
+ database=options.mysql_database,
+ user=options.mysql_user,
+ password=options.mysql_password,
+ )
+ self.__db_dataset = dataset.connect(
+ "mysql://{}:{}@{}/{}".format(
+ options.mysql_user,
+ options.mysql_password,
+ options.mysql_host,
+ options.mysql_database,
+ )
+ )
+
+ def generic_query(self, method, query, parameters=None, force=False):
+ """torndb.Connection.method(query, *parameters)
+
+ This method attempts to force proper parameterization of SQL
+ queries to prevent SQL injection
+ * Any queries to the db MUST be made with this method
+ * This method does not yet support kwparameters (if needed, will add)
+ """
+ _method = getattr(self.__db_torndb, method)
+ parameterized = any(c in query for c in ["%", "{"]) and not force
+
+ if parameterized:
+ assert parameters, "Expected parameters for parameterized query"
+ return _method(query, *parameters)
+ else:
+ return _method(query)
+
+ def get_email_suffixes(self):
+ """Return list of email suffixes"""
+ return self.generic_query("query", "SELECT * FROM email_suffixes")
+
+ def get_all_emails(self):
+ """Returns all email addresses in DB"""
+ return self.generic_query("query", "SELECT email FROM persons")
+
+ def create_person(self, kwargs):
+ """Writes a new person to the DB"""
+ # Parameterized query left here as an example
+ # return self.generic_query(
+ # "execute",
+ # ("INSERT INTO persons (first, last, email, "
+ # "salt, password, karma, time)"
+ # " VALUES (%s,%s,%s,%s,%s,%s,%s)"),
+ # [kwargs["first"], kwargs["last"],
+ # kwargs["email"], kwargs["salt"],
+ # kwargs["password"], 0, int(time.time())]
+ # )
+
+ # Here we use dataset to execute the same query as above
+ table = self.__db_dataset["persons"]
+ table.insert(dict(
+ first=kwargs["first"],
+ last=kwargs["last"],
+ email=kwargs["email"],
+ salt=kwargs["salt"],
+ password=kwargs["password"],
+ karma=0,
+ time=int(time.time()),
+ )
+ )
View
37 touchpoint/jsend.py
@@ -0,0 +1,37 @@
+# Source: http://tornadogists.org/6612013/
+
+
+class JSendMixin(object):
+
+ """http://labs.omniti.com/labs/jsend
+
+ JSend is a specification that lays down some rules for how JSON
+ responses from web servers should be formatted.
+
+ JSend focuses on application-level (as opposed to protocol- or
+ transport-level) messaging which makes it ideal for use in
+ REST-style applications and APIs.
+ """
+
+ def success(self, data):
+ """When an API call is successful, the JSend object is used as a simple
+ envelope for the results, using the data key.
+ """
+ self.write({'status': 'success', 'data': data})
+
+ def fail(self, data):
+ """There was a problem with the data submitted, or some pre-condition
+ of the API call wasn't satisfied.
+ """
+ self.write({'status': 'fail', 'data': data})
+
+ def error(self, message, data=None, code=None):
+ """An error occurred in processing the request, i.e. an exception was
+ thrown.
+ """
+ result = {'status': 'error', 'message': message}
+ if data:
+ result['data'] = data
+ if code:
+ result['code'] = code
+ self.write(result)
View
52 touchpoint/main.py
@@ -0,0 +1,52 @@
+import re
+import logging
+import os
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+from tornado import web
+from tornado.options import define, options
+
+from touchpoint import db
+from touchpoint.signup import routes as signup_routes
+
+
+define("port", default=8888, help="run on the given port", type=int)
+define("log_file", default="/var/log/touchpoint/touchpoint.log",
+ help="path for the log file", type=str)
+
+
+class Application(tornado.web.Application):
+
+ def __init__(self):
+ routes = []
+ routes += signup_routes.get_routes()
+
+ settings = dict(
+ template_path=os.path.join(
+ os.path.dirname(__file__), "../templates"),
+ static_path=os.path.join(os.path.dirname(__file__), "../static"),
+ gzip=True
+ )
+
+ tornado.web.Application.__init__(
+ self,
+ routes,
+ **settings
+ )
+
+ self.db_conn = db.Connection()
+
+
+def main(args):
+ print "Getting ready . . ."
+ args.append('--log_file_prefix={}'.format(options.log_file))
+ tornado.options.parse_command_line(args)
+ http_server = tornado.httpserver.HTTPServer(Application())
+ http_server.listen(options.port)
+ print "Welcome to Touchpoint"
+ tornado.ioloop.IOLoop.instance().start()
+
+
+if __name__ == '__main__':
+ main()
View
62 touchpoint/requesthandlers.py
@@ -0,0 +1,62 @@
+from tornado.web import RequestHandler
+
+from touchpoint.jsend import JSendMixin
+from touchpoint.utils import APIError
+
+
+class BaseHandler(RequestHandler):
+
+ @property
+ def db_conn(self):
+ return self.application.db_conn
+
+
+class ViewHandler(BaseHandler):
+
+ def initialize(self):
+ self.set_header("Content-Type", "text/html")
+
+
+class APIHandler(BaseHandler, JSendMixin):
+
+ """RequestHandler for API calls
+
+ * Sets header as application/json
+ * Provides custom write_error that writes error back as JSON rather than
+ as the standard HTML template
+ """
+
+ def initialize(self):
+ self.set_header("Content-Type", "application/json")
+
+ def write_error(self, status_code, **kwargs):
+ """Override of RequestHandler.write_error
+
+ * Call `error()` or `fail()` from JSendMixin depending on which
+ exception was raised with provided reason and status code.
+ """
+ self.clear()
+
+ # If exc_info is not in kwargs, something is very fubar
+ if not "exc_info" in kwargs.keys():
+ logging.error("exc_info not provided")
+ self.set_status(500)
+ self.error(message="Internal Server Error", code=500)
+ self.finish()
+
+ self.set_status(status_code)
+
+ # Any APIError exceptions raised will result in a JSend fail written
+ # back with the log_message as data. Hence, log_message should NEVER
+ # expose internals.
+ # All other exceptions result in a JSend error being written back,
+ # with log_message only written if debug mode is enabled
+ exception = kwargs["exc_info"][1]
+ if isinstance(exception, APIError):
+ self.fail(exception.log_message)
+ else:
+ self.error(message=self._reason,
+ data=exception.log_message if self.settings.get(
+ "debug") else None,
+ code=status_code)
+ self.finish()
View
0 touchpoint/signup/__init__.py
No changes.
View
107 touchpoint/signup/requesthandlers.py
@@ -0,0 +1,107 @@
+import json
+import bcrypt
+import os
+from email_validation import valid_email_address
+from tornado.web import HTTPError
+
+from touchpoint.requesthandlers import ViewHandler, BaseHandler, APIHandler
+from touchpoint.utils import api_assert
+
+
+class PublicHome(ViewHandler):
+
+ def get(self):
+ self.render("homepublic.html")
+
+
+class SignUp(APIHandler):
+
+ def _validate_email_suffix(self, email):
+ """Validate email suffix
+
+ Validates the suffix of email against all valid_suffixes.
+ Returns True for valid suffix, and False for invalid.
+ """
+ for suffix in [s["suffix"] for s in self.db_conn.get_email_suffixes()]:
+ if any(map(email.endswith, [p + suffix for p in ["@", "."]])):
+ return True
+ return False
+
+ def _validate_email(self, email):
+ """Validates email
+
+ Validates email as a proper-form email address with allowed suffix.
+ If email address is not valid, or if suffix is invalid,
+ raise a 400. If email address has already been used, raises 409.
+ """
+ api_assert(
+ valid_email_address(email),
+ 400, log_message="Not a valid email address"
+ )
+
+ api_assert(
+ self._validate_email_suffix(email),
+ 400, log_message="Not a valid email suffix"
+ )
+
+ api_assert(
+ not email in [d["email"] for d in self.db_conn.get_all_emails()],
+ 409, log_message="Someone else is already using this email"
+ )
+
+ def post(self):
+ """POST RequestHandler
+
+ * Asserts request data contains required fields
+ * Hashes password with a random salt
+ * Stores a new person in the DB if all is well
+ """
+ json_body = json.loads(self.request.body)
+
+ # Assert that all required fields are present in the request
+ required_fields = ["first", "last", "email", "password"]
+ api_assert(
+ all(field in json_body.keys() for field in required_fields),
+ 400, log_message="API request missing required fields"
+ )
+ # Validate email
+ self._validate_email(json_body["email"])
+ # Generate salt, hash password with salt
+ salt = bcrypt.gensalt(rounds=12) # default rounds are 12
+ json_body.update(
+ {"salt": salt,
+ "password": bcrypt.hashpw(str(json_body["password"]), salt)}
+ )
+ # Add person to DB and write back on successful entry
+ self.db_conn.create_person(json_body)
+ self.success("Signup successful!")
+
+
+class SignUpFb(BaseHandler):
+
+ def get(self):
+ self.write("I am a stub.")
+
+
+class SignUpFbEmail(BaseHandler):
+
+ def get(self):
+ self.write("I am a stub.")
+
+
+class SignUpDetails1(ViewHandler):
+
+ def get(self):
+ self.render("signup_part1.html")
+
+
+class SignUpDetails2(ViewHandler):
+
+ def get(self):
+ self.render("signup_part2.html")
+
+
+class SignUpDetails3(ViewHandler):
+
+ def get(self):
+ self.render("signup_part3.html")
View
42 touchpoint/signup/routes.py
@@ -0,0 +1,42 @@
+import pyclbr
+
+from touchpoint.signup import requesthandlers
+from touchpoint.requesthandlers import BaseHandler
+
+
+def get_routes():
+ """Create and return all routes for signup
+
+ Routes are (url, RequestHandler) tuples
+ """
+ # Any routes which are not to be named as the lowercase
+ # name of their respective requesthandler should be entered here
+ custom_routes = [(r"/", requesthandlers.PublicHome)]
+ custom_routes_s = [c.__name__ for r, c in custom_routes]
+ # Exclude any requesthandlers by name (string) here
+ exclusions = [] # ex: ["PublicHome"]
+
+ # rhs is a dict of {classname: pyclbr.Class} key, value pairs
+ rhs = pyclbr.readmodule("touchpoint.signup.requesthandlers")
+
+ # You better believe this is a list comprehension
+ auto_routes = [
+ # URL, requesthandler tuple
+ (
+ "/{}".format(k.lower()),
+ getattr(requesthandlers, k)
+ )
+ # foreach classname, pyclbr.Class in rhs
+ for k, v in rhs.iteritems()
+ # Only add the pair to auto_routes if:
+ # * the superclass is in the list of supers we want
+ # * the requesthandler isn't already paired in custom_routes
+ # * the requesthandler isn't manually excluded
+ if any(
+ True for s in v.super if s in ["ViewHandler", "APIHandler"]
+ )
+ and k not in (custom_routes_s + exclusions)
+ ]
+
+ routes = auto_routes + custom_routes
+ return routes
View
14 touchpoint/utils.py
@@ -0,0 +1,14 @@
+from tornado.web import HTTPError
+
+
+class APIError(HTTPError):
+
+ """Equivalent to RequestHandler.HTTPError except for in name"""
+
+
+def api_assert(condition, *args, **kwargs):
+ """Asserts that condition is True, else raises an APIError with the
+ provided args and kwargs
+ """
+ if not condition:
+ raise APIError(*args, **kwargs)

0 comments on commit e967e7b

Please sign in to comment.
Something went wrong with that request. Please try again.