git.fiddlerwoaroof.com
Browse code

Many core features working

- TODO: implement subscriptions

fiddlerwoaroof authored on 15/03/2015 01:07:23
Showing 18 changed files
... ...
@@ -3,3 +3,8 @@ bower_components
3 3
 include
4 4
 lib
5 5
 local
6
+*.pyc
7
+.*.swp
8
+share
9
+pip-selfcheck.json
10
+src.twisted
6 11
new file mode 100644
7 12
Binary files /dev/null and b/db/.data.sql.swp differ
8 13
new file mode 100644
9 14
Binary files /dev/null and b/db/.schema.sql.swp differ
10 15
new file mode 100644
... ...
@@ -0,0 +1,31 @@
1
+INSERT INTO users (name, password, email) VALUES
2
+  ('user1', 'password', 'user1@example.com'),
3
+  ('user2', 'password', 'user2@example.com'),
4
+  ('user3', 'password', 'user2@example.com'),
5
+  ('user4', 'password', 'user2@example.com'),
6
+  ('user5', 'password', 'user2@example.com'),
7
+  ('user6', 'password', 'user2@example.com'),
8
+  ('user7', 'password', 'user2@example.com'),
9
+  ('user8', 'password', 'user2@example.com');
10
+
11
+SELECT * FROM put_link('user1', 'http://arguendo.com/this/is-link');
12
+SELECT * FROM put_link('user1', 'http://example.com/this/is-a-link');
13
+SELECT * FROM put_link('user1', 'http://google.com/is/is-a-link');
14
+SELECT * FROM put_link('user2', 'http://arguendo.com/this/is-link');
15
+SELECT * FROM put_link('user2', 'http://arguendo.com/this/is-link?a=we&b=34534');
16
+SELECT * FROM put_link('user2', 'http://example.com/this/is-a-link');
17
+SELECT * FROM put_link('user2', 'http://google.com/is/is-a-link');
18
+SELECT * FROM put_link('user3', 'http://arguendo.com/this/is-link');
19
+SELECT * FROM put_link('user3', 'http://arguendo.com/this/is-link?a=we&b=34534');
20
+SELECT * FROM put_link('user3', 'http://example.com/this/is-a-link');
21
+SELECT * FROM put_link('user3', 'http://google.com/is/is-a-link');
22
+SELECT * FROM put_link('user4', 'http://arguendo.com/this/is-link?a=we&b=34534');
23
+SELECT * FROM put_link('user5', 'http://arguendo.com/this/is-link?a=we&b=34534');
24
+SELECT * FROM put_link('user5', 'http://facebook.com');
25
+SELECT * FROM put_link('user5', 'http://google.com/is/is-a-link');
26
+SELECT * FROM put_link('user5', 'http://learn.knockoutjs.com/#/?tutorial=webmail');
27
+SELECT * FROM put_link('user5', 'http://python.org');
28
+SELECT * FROM put_link('user6', 'http://learn.knockoutjs.com/#/?tutorial=webmail');
29
+SELECT * FROM put_link('user6', 'http://stackoverflow.com/questions/13715743/psycopg2-not-actually-inserting-data');
30
+SELECT * FROM put_link('user7', 'http://learn.knockoutjs.com/#/?tutorial=webmail');
31
+SELECT * FROM put_link('user7', 'https://mail.google.com/mail/u/3/#inbox');
0 32
new file mode 100644
... ...
@@ -0,0 +1,64 @@
1
+DROP TABLE IF EXISTS user_links;
2
+DROP TABLE IF EXISTS users;
3
+CREATE TABLE users (
4
+  id SERIAL UNIQUE NOT NULL,
5
+  name TEXT UNIQUE NOT NULL,
6
+  password TEXT NOT NULL,
7
+  email TEXT NOT NULL,
8
+  PRIMARY KEY (id)
9
+);
10
+
11
+DROP TABLE IF EXISTS links;
12
+CREATE TABLE links (
13
+  id SERIAL UNIQUE NOT NULL,
14
+  url TEXT NOT NULL,
15
+  title TEXT DEFAULT '',
16
+  posted TIMESTAMP DEFAULT current_timestamp,
17
+  PRIMARY KEY (id)
18
+);
19
+CREATE INDEX url_index ON links (url);
20
+
21
+CREATE TABLE user_links (
22
+  id SERIAL UNIQUE NOT NULL,
23
+  user_id INTEGER REFERENCES users (id) NOT NULL,
24
+  link_id INTEGER REFERENCES links (id) NOT NULL,
25
+  UNIQUE (user_id, link_id),
26
+  PRIMARY KEY (id)
27
+);
28
+
29
+DROP FUNCTION IF EXISTS get_bone(text);
30
+CREATE OR REPLACE FUNCTION get_bone(username text)
31
+  RETURNS TABLE(name text, url text, title text, posted timestamp) AS $$
32
+BEGIN
33
+  RETURN QUERY SELECT users.name, links.url, links.title, links.posted
34
+      FROM users
35
+        INNER JOIN user_links ON user_links.user_id = users.id
36
+        INNER JOIN links ON user_links.link_id = links.id
37
+        WHERE users.name=username
38
+        ORDER BY links.posted DESC;
39
+END
40
+$$ LANGUAGE plpgsql;
41
+
42
+DROP FUNCTION IF EXISTS put_link(text,text,text);
43
+DROP TYPE IF EXISTS link_url_type;
44
+
45
+CREATE TYPE link_url_type AS (link_id int, user_id int);
46
+CREATE OR REPLACE FUNCTION put_link(username text, link_url text, link_title text default '')
47
+  RETURNS link_url_type
48
+AS $$
49
+DECLARE
50
+  n_link_id int;
51
+  n_user_id int;
52
+  result link_url_type;
53
+BEGIN
54
+  INSERT INTO links (url, title) VALUES
55
+    (link_url, link_title) RETURNING id INTO n_link_id;
56
+  SELECT users.id FROM users WHERE users.name = username INTO n_user_id;
57
+  INSERT INTO user_links (user_id, link_id) VALUES (n_user_id, n_link_id);
58
+
59
+  SELECT n_link_id INTO result.link_id;
60
+  SELECT n_user_id INTO result.user_id;
61
+  RETURN result;
62
+END
63
+$$ LANGUAGE plpgsql;
64
+
... ...
@@ -1,14 +1,10 @@
1
-[
2
-  {'url': 'http://www.wsj.com/articles/hillary-seems-tired-not-hungry-1426205650',
3
-   'title': 'Hillary Seems Tired, Not Hungry',
4
-   'short-url': 'http://themarrow.is/hillary-seems-tired'},
5
-  {'url': 'http://www.wsj.com/articles/hillary-seems-tired-not-hungry-1426205650',
6
-   'title': 'Hillary Seems Tired, But Capable',
7
-   'short-url': 'http://themarrow.is/hillary-seems-tired-but'},
8
-  {'url': 'http://example.com/some-page',
9
-   'title': 'The Page Title',
10
-   'short-url': 'http://themarrow.is/the-page-title'},
11
-  {'url': 'http://example.com/another-page',
12
-   'title': 'The Second Page Title',
13
-   'short-url': 'http://themarrow.is/the-second-page'}
14
-]
1
+{"user1": [
2
+  {"url": "http://www.wsj.com/articles/hillary-seems-tired-not-hungry-1426205650",
3
+    "title": "Hillary Seems Tired, Not Hungry"}
4
+  {"url": "http://www.wsj.com/articles/hillary-seems-tired-not-hungry-1426205650",
5
+    "title": "Hillary Seems Tired, But Capable"}
6
+  {"url": "http://example.com/some-page",
7
+    "title": "The Page Title"},
8
+  {"url": "http://example.com/another-page",
9
+    "title": "The Second Page Title"}
10
+]}
... ...
@@ -1,14 +1,31 @@
1
-from flask import Flask, session, redirect, url_for, escape, request, abort
1
+from flask import Flask, session, redirect, url_for, escape, request, abort, g
2 2
 import json
3
+import os
4
+import base64
5
+import psycopg2
6
+
7
+from marrow import database
8
+from marrow import user
9
+from marrow import bone
3 10
 
4 11
 app = Flask(__name__)
12
+app.teardown_appcontext(database.close_connection)
5 13
 app.config["APPLICATION_ROOT"] = "/api"
14
+app.secret_key = base64.b64encode(os.urandom(24))
15
+app.debug = True
16
+
17
+
18
+# Blueprints #
19
+user.get_users(app)
20
+app.register_blueprint(user.user_blueprint, url_prefix='/user')
21
+app.register_blueprint(bone.bone_blueprint, url_prefix='/bones')
22
+
6 23
 
7 24
 links = {}
8 25
 def get_feeds(filename):
9 26
     global links
10 27
     with open(filename) as f:
11
-        links = {'everyone': json.load(f)}
28
+        links = json.load(f)
12 29
 
13 30
 @app.route('/')
14 31
 def index():
... ...
@@ -18,84 +35,5 @@ def index():
18 35
 def humans():
19 36
     return 'blah'
20 37
 
21
-@app.route('/data/submit', methods=['POST'])
22
-def submit_link():
23
-    result = False
24
-    if 'username' in session:
25
-        obj = request.get_json()
26
-        links.setdefault(session['username'], []).append(dict(
27
-            title = obj.get('title',''), #TODO: get title somehow . . .
28
-            url = obj['url']
29
-        ))
30
-        result = True
31
-    return json.dumps(result)
32
-
33
-@app.route('/data/<username>')
34
-def user_data(username):
35
-    return json.dumps(links.get(username, []))
36
-
37
-@app.route('/data',methods=['GET'])
38
-def data():
39
-    result = {'marrow':[]}
40
-    if 'username' in session:
41
-        result['marrow'] = links.get(session['username'])
42
-    else:
43
-        result['marrow'] = links['everyone']
44
-    return json.dumps(result)
45
-
46
-users = {}
47
-def get_users(filename):
48
-    global users
49
-    with open(filename) as f:
50
-        users = {x['username']:x for x in json.load(f)}
51
-
52
-
53
-@app.route('/user/add', methods=['GET', 'POST'])
54
-def adduser():
55
-    if request.method == 'POST':
56
-        users[request.form['username']] = dict(username=request.form['username'],
57
-                                               password=request.form['password'])
58
-        return redirect(url_for('index'))
59
-    else:
60
-        return '''<form action="" method="post"><p>
61
-                    <input type=text name=username>
62
-                    <input type=text name=password>
63
-                    <p><input type=submit value='Add User'></form>'''
64
-
65
-@app.route('/user/check')
66
-def checkuser():
67
-    return json.dumps('username' in session)
68
-
69
-@app.route('/login', methods=['GET', 'POST'])
70
-def login():
71
-    if request.method == 'POST':
72
-        username, password = request.form['username'], request.form['password']
73
-        user = users.get(username, {})
74
-        rightPassword = user.get(password,None)
75
-        if password == rightPassword:
76
-            session['username'] = request.form['username']
77
-        else:
78
-            abort(403)
79
-        to = request.args.get('to', '/')
80
-        return redirect(to)
81
-    else:
82
-        return '''<form action="" method="post"><p>
83
-                    <label for=username>User:</label>
84
-                    <input type=text name=username>
85
-                    <label for=password>Passwod:</label>
86
-                    <input type=text name=password>
87
-                    <p><input type=submit value=Login></form>'''
88
-
89
-@app.route('/logout')
90
-def logout():
91
-    session.pop('username', None)
92
-    return redirect(url_for('index'))
93
-
94
-
95
-app.secret_key = 'Rp7ZKSO21AUDz6DUdROxVoWlZdUoGciX'
96
-
97
-get_users('sample_data/users.json')
98
-get_feeds('sample_data/sites.json')
99
-app.debug = True
100 38
 if __name__ == '__main__':
101 39
     app.run(host='0.0.0.0')
102 40
new file mode 100644
103 41
Binary files /dev/null and b/src/marrow/.bone.py.swp differ
104 42
new file mode 100644
105 43
Binary files /dev/null and b/src/marrow/.database.py.swp differ
106 44
new file mode 100644
107 45
Binary files /dev/null and b/src/marrow/.user.py.swp differ
109 47
new file mode 100644
... ...
@@ -0,0 +1,51 @@
1
+import flask
2
+from flask import Blueprint, session, redirect, url_for, escape, request, abort, g
3
+from . import database
4
+import json
5
+
6
+bone_blueprint = Blueprint('bone', __name__)
7
+
8
+@bone_blueprint.route('/submit', methods=['POST'])
9
+def submit_link():
10
+    result = False
11
+    if 'username' in session:
12
+        obj = request.get_json()
13
+        url, title = obj['url'],obj['title']
14
+        username = session['username']
15
+        db = database.get_db()
16
+        with db.cursor() as cur:
17
+            cur.callproc('put_link', (username, url, title))
18
+            cur.fetchall()
19
+            if cur.rowcount != -1:
20
+                db.commit()
21
+                result = True
22
+    return json.dumps(result)
23
+
24
+@bone_blueprint.route('',defaults={'username':None}, methods=['GET'])
25
+@bone_blueprint.route('/<username>', methods=['GET'])
26
+def data(username):
27
+    if username is None and 'username' in session:
28
+        username = session['username']
29
+
30
+    result = {'marrow':[]}
31
+    with database.get_db().cursor() as cur:
32
+        cur.execute("SELECT url, title, posted from get_bone(%s);", (username,))
33
+        result['marrow'] = [
34
+                dict(url=url,title=title,posted=posted.isoformat())
35
+                    for url,title,posted
36
+                    in cur.fetchall()
37
+        ]
38
+    return json.dumps(result)
39
+
40
+import random
41
+@bone_blueprint.route('/random')
42
+def random():
43
+    db = database.get_db()
44
+    with db.cursor() as cur:
45
+        if 'username' in session:
46
+            cur.execute('SELECT name FROM users WHERE name != %s ORDER BY random() LIMIT 1',
47
+                    (session['username'],))
48
+        else:
49
+            cur.execute('SELECT name FROM users ORDER BY random() LIMIT 1')
50
+        username = cur.fetchone()[0]
51
+        return redirect(url_for('bone.data', username=username))
0 52
new file mode 100644
... ...
@@ -0,0 +1,20 @@
1
+from flask import g
2
+import psycopg2
3
+
4
+def get_db():
5
+    db = getattr(g, '_database', None)
6
+    if db is None:
7
+        db = g._database = psycopg2.connect(
8
+          database="marrow",
9
+          user="marrow",
10
+          password="marrowpass",
11
+          host="pgsqlserver.elangley.org"
12
+        );
13
+    return db
14
+
15
+def close_connection(exception):
16
+    db = getattr(g, '_database', None)
17
+    if db is not None:
18
+        db.close()
19
+
20
+
0 21
new file mode 100644
... ...
@@ -0,0 +1,68 @@
1
+import flask
2
+from flask import Blueprint, session, redirect, url_for, escape, request, abort, g
3
+from . import database
4
+import json
5
+
6
+user_blueprint = Blueprint('user', __name__)
7
+
8
+users = {}
9
+def get_users(app):
10
+    with app.app_context(): _get_users()
11
+
12
+def _get_users():
13
+    global users
14
+    cur = database.get_db().cursor()
15
+    with cur:
16
+        cur.execute('SELECT name,password,email FROM users')
17
+        for username,password,email in cur.fetchall():
18
+            users[username] = dict(username=username, password=password, email=email)
19
+    
20
+#TODO: load this from somewhere
21
+user_env = {}
22
+@user_blueprint.route('/environment')
23
+def get_user_env():
24
+    if 'username' in session:
25
+        username = session['username']
26
+        env = user_env.setdefault(username, {})
27
+        env['username'] = username
28
+        return env
29
+        
30
+@user_blueprint.route('/list')
31
+def list_users():
32
+    return json.dumps([_ for _ in users.keys()])
33
+
34
+@user_blueprint.route('/add', methods=['POST'])
35
+def adduser():
36
+    db = database.get_db()
37
+    with db.cursor() as cur:
38
+        obj = request.get_json();
39
+        username, password = obj['username'], obj['password']
40
+        cur.execute('INSERT INTO users (name,password,email) VALUES (%s,%s,%s)',
41
+                    (username, password, 'abc@def.com'))
42
+        _get_users()
43
+        db.commit()
44
+        session['username'] = username
45
+        return json.dumps(True)
46
+
47
+@user_blueprint.route('/check')
48
+def checkuser():
49
+    return json.dumps('username' in session)
50
+
51
+@user_blueprint.route('/login', methods=['POST'])
52
+def login():
53
+    obj = request.get_json();
54
+    result = False
55
+    username, password = obj['username'], obj['password']
56
+    user = users.get(username, {})
57
+    rightPassword = user.get('password',None)
58
+    if password == rightPassword:
59
+        session['username'] = username
60
+        result = True
61
+    return json.dumps(result)
62
+
63
+@user_blueprint.route('/logout')
64
+def logout():
65
+    to = request.args.get('to', '/')
66
+    session.pop('username', None)
67
+    return redirect(to)
68
+
... ...
@@ -48,13 +48,31 @@ a {
48 48
   text-decoration: none;
49 49
 }
50 50
 
51
+.hidden {
52
+  display: none;
53
+}
54
+
55
+#login_form {
56
+  z-index: 100;
57
+  background: white;
58
+  position: absolute;
59
+  height:100%;
60
+  width:100%;
61
+  top: 0px;
62
+  left: 0px;
63
+}
64
+#login_form form {
65
+  position: absolute;
66
+  top:25%;
67
+  left:25%;
68
+}
69
+
51 70
 #sidebar{
52 71
   position: absolute;
53 72
   height: 25%;
54 73
   width: 10%;
55 74
   top: 37.5%;
56 75
   left: 0px;
57
-  background: #aaa;
58 76
 }
59 77
 
60 78
 #sidebar ul {
... ...
@@ -68,14 +86,18 @@ a {
68 86
   padding-bottom: 5px;
69 87
 }
70 88
 
71
-
72
-
73 89
 #main{
90
+  padding-left: 10px;
91
+  padding-top: 50px;
74 92
   position: absolute;
75 93
   height:100%;
76 94
   top:0px;
77 95
   right:0px;
78 96
   width: 90%;
79
-  background: #bbb;
80
-  
81 97
 }
98
+
99
+#main ul {
100
+  position:relative;
101
+}
102
+
103
+#main ul 
... ...
@@ -7,6 +7,15 @@
7 7
   <link rel="stylesheet" href="/css/main.css" media="screen" />
8 8
 </head>
9 9
 <body>
10
+  <div id="login_form">
11
+    <div class="message"></div>
12
+    <form data-bind="submit: login.login">
13
+      <input type="text" name="username" placeholder="Username" data-bind="value: login.username" />
14
+      <input type="text" name="password" placeholder="Password" data-bind="value: login.password" />
15
+      <button type="submit" >Log In</button>
16
+      <button data-bind="click: login.newuser">Add User</button>
17
+    </form>
18
+  </div>
10 19
   <div class="container">
11 20
     <section id="main">
12 21
       <ul data-bind='foreach: marrow'>
... ...
@@ -21,7 +30,8 @@
21 30
       <ul>
22 31
         <li><a href="/">Home</a></li>
23 32
         <li><a href="/mylists">My Lists</a></li>
24
-        <li><a href="/random">Random</a></li>
33
+        <li><a href="#" data-bind="click: random">Random</a></li>
34
+        <li><a href="/api/user/logout?to=/">Log Out</a></li>
25 35
       </ul>
26 36
     </section>
27 37
   </div>
... ...
@@ -5,21 +5,91 @@ function Marrow(data) {
5 5
   this.url = ko.observable(data.url);
6 6
 };
7 7
 
8
+function Login(master) {
9
+  var self = this;
10
+  self.username = ko.observable();
11
+  self.password = ko.observable();
12
+
13
+  self.check = function() {
14
+    $.getJSON("/api/user/check", function (logged_in) {
15
+      if (logged_in) {
16
+        master.update(function() {
17
+          $('#login_form').addClass('hidden');
18
+        });
19
+      }
20
+    });
21
+  };
22
+
23
+  self.login = function() {
24
+    var username = self.username();
25
+    var password = self.password();
26
+    $.ajax("/api/user/login", {
27
+      data: ko.toJSON({username:username,password:password}),
28
+      type: "post", contentType: "application/json",
29
+      success: function(login_succeeded) {
30
+        login_succeeded = JSON.parse(login_succeeded);
31
+        console.log(login_succeeded);
32
+        var login_form = $('#login_form');
33
+        if (login_succeeded === true) {
34
+          master.update(function() {
35
+            login_form.addClass('hidden');
36
+          });
37
+        } else {
38
+          login_form.children('.message').text('Login Failed!').addClass('err');
39
+        }
40
+          
41
+      }
42
+    });
43
+  };
44
+
45
+  self.newuser = function() {
46
+    var username = self.username();
47
+    var password = self.password();
48
+    $.ajax("/api/user/add", {
49
+      data: ko.toJSON({username:username,password:password}),
50
+      type: "post", contentType: "application/json",
51
+      success: function(creation_succeeded) {
52
+        creation_succeeded = JSON.parse(creation_succeeded);
53
+        console.log(creation_succeeded);
54
+        if (creation_succeeded === true) {
55
+          $('#login_form').addClass('hidden');
56
+        }
57
+      }
58
+    });
59
+  };
60
+};
61
+
8 62
 function Bone() {
9 63
   var self = this;
10 64
   self.marrow = ko.observableArray([]);
11 65
   self.newLink = ko.observable();
66
+  self.login = new Login(self);
67
+  self.login.check();
12 68
 
13 69
   self.add_marrow = function(link) {};
14 70
 
15
-  $.getJSON("/api/data", function(data) {
16
-    var otherMarrow = $.map(data.marrow, function(item) { return new Marrow(item) });
17
-    self.marrow(otherMarrow);
18
-  });
71
+  self.random = function() {
72
+    $.getJSON("/api/bones/random", function(data) {
73
+      if (data !== []) {
74
+        var theMarrow = $.map(data.marrow, function(item) { return new Marrow(item) });
75
+        self.marrow(theMarrow);
76
+      }
77
+    });
78
+  };
79
+
80
+  self.update = function(cb) {
81
+    $.getJSON("/api/bones", function(data) {
82
+      if (data !== []) {
83
+        var theMarrow = $.map(data.marrow, function(item) { return new Marrow(item) });
84
+        self.marrow(theMarrow);
85
+      }
86
+      cb();
87
+    });
88
+  };
19 89
 
20 90
   self.addLink = function() {
21 91
     var newLink = {url:self.newLink(),title:''};
22
-    $.ajax("/api/data/submit", {
92
+    $.ajax("/api/bones/submit", {
23 93
       data: ko.toJSON(newLink),
24 94
       type: "post", contentType: "application/json",
25 95
       success: function(result) {
26 96
new file mode 100644
... ...
@@ -0,0 +1,2 @@
1
+User-agent: *
2
+Disallow: /api/