You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@steve.apache.org by hu...@apache.org on 2015/03/18 13:17:03 UTC

svn commit: r1667520 - in /steve/trunk/pytest: ./ REST_API_README.txt httpd.conf steve.cfg www/ www/cgi-bin/ www/cgi-bin/lib/ www/cgi-bin/lib/__init__.py www/cgi-bin/lib/response.py www/cgi-bin/rest_admin.py www/cgi-bin/rest_voter.py www/htdocs/

Author: humbedooh
Date: Wed Mar 18 12:17:02 2015
New Revision: 1667520

URL: http://svn.apache.org/r1667520
Log:
First stab at a REST API for STeVe in Python (via CGI).
This just contains the backend API for setting up and viewing elections and issues, nothing else.
Voting via API is not implemented yet.
No authentication feature yet either.

Added:
    steve/trunk/pytest/
    steve/trunk/pytest/REST_API_README.txt
    steve/trunk/pytest/httpd.conf
    steve/trunk/pytest/steve.cfg
    steve/trunk/pytest/www/
    steve/trunk/pytest/www/cgi-bin/
    steve/trunk/pytest/www/cgi-bin/lib/
    steve/trunk/pytest/www/cgi-bin/lib/__init__.py
    steve/trunk/pytest/www/cgi-bin/lib/response.py
    steve/trunk/pytest/www/cgi-bin/rest_admin.py
    steve/trunk/pytest/www/cgi-bin/rest_voter.py
    steve/trunk/pytest/www/htdocs/

Added: steve/trunk/pytest/REST_API_README.txt
URL: http://svn.apache.org/viewvc/steve/trunk/pytest/REST_API_README.txt?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/REST_API_README.txt (added)
+++ steve/trunk/pytest/REST_API_README.txt Wed Mar 18 12:17:02 2015
@@ -0,0 +1,274 @@
+##################
+# ADMIN REST API #
+##################
+
+==========================
+Setting up a new election:
+==========================
+
+/steve/admin/setup/$electionid
+  POST
+    input: 
+          title: Title of the election
+          owner: uid of election owner
+          monitors: email addresses of monitors
+          starts: UNIX timestamp of start (optional)
+          ends: UNIX timestamp when it closes (optional)
+    output:
+      HTTP 201 Created on success
+      HTTP 400 Bad request if params missing or invalid or already exists
+      HTTP 500 on error
+    response payload:
+       JSON formatted
+        {
+        "message": "created"
+        }
+        
+==============================
+Editing an existing election:
+==============================
+
+/steve/admin/edit/$electionid
+  POST
+    input: 
+          title: Title of the election (optional)
+          owner: uid of election owner (optional)
+          monitors: email addresses of monitors (optional)
+          starts: UNIX timestamp of start (optional)
+          ends: UNIX timestamp when it closes (optional)
+    output:
+      HTTP 200 Saved on success
+      HTTP 404 Not Found if no such election
+      HTTP 500 on error
+    response payload:
+       JSON formatted
+        {
+        "message": "Changes saved"
+        }
+        
+        
+        
+=================================
+Creating an issue in an election:
+=================================
+
+/steve/admin/create/$electionid/$issueid
+  POST
+    input: 
+          title: Title of the issue
+          description: (optional) Description of the issue
+          type: type of issue (yna, stv[1-9])
+          candidates: (if stv[1-9]) \n separated list of candidate names
+          nominatedby: (if YNA) Person to nominate this issue
+          seconds: (if YNA) \n separated list of seconds for the issue
+          
+    output:
+      HTTP 201 Created on success
+      HTTP 400 Bad request if params missing or invalid or already exists
+      HTTP 500 on error
+    response payload:
+       JSON formatted
+        {
+        "message": "created"
+        }
+       
+        
+================================
+Editing an issue in an election:
+================================
+
+/steve/admin/edit/$electionid/$issueid
+  POST
+    input: 
+          title: Title of the issue (optional)
+          description: (optional) Description of the issue (optional)
+          type: type of issue (yna, stv[1-9]) (optional)
+          candidates: (if stv[1-9]) \n separated list of candidate names (optional)
+          nominatedby: (if YNA) Person to nominate this issue (optional)
+          seconds: (if YNA) \n separated list of seconds for the issue (optional)
+          
+    output:
+      HTTP 200 Saved on success
+      HTTP 404 Not Found if no such issue or election
+      HTTP 500 on error
+    response payload:
+       JSON formatted
+        {
+        "message": "Changes saved"
+        }
+        
+        
+==================================
+Adding a statement to a candidate:
+==================================
+
+/steve/admin/statement/$electionid/$issueid
+  POST
+    input: 
+          candidate: Candidate to set statement for
+          statement: Statement to set/add
+          
+    output:
+      HTTP 200 Saved on success
+      HTTP 400 Bad request if params missing
+      HTTP 404 Not Found if candidate is not on the ballot
+      HTTP 500 on error
+    response payload:
+       JSON formatted
+        {
+        "message": "Changes saved"
+        }
+
+        
+================================
+Adding a candidate to the issue:
+================================
+
+/steve/admin/addcandidate/$electionid/$issueid
+  POST
+    input: 
+          candidate: Candidate to set add
+          statement: Statement to set/add (optional)
+          
+    output:
+      HTTP 200 Saved on success
+      HTTP 400 Bad request if params missing
+      HTTP 404 Not Found if no such issue/election
+      HTTP 500 on error
+    response payload:
+       JSON formatted
+        {
+        "message": "Changes saved"
+        }
+
+
+===================================
+Deleting a candidate to the issue:
+===================================
+
+/steve/admin/delcandidate/$electionid/$issueid
+  POST
+    input: 
+          candidate: Candidate to delete
+          
+    output:
+      HTTP 200 Saved on success
+      HTTP 400 Bad request if params missing
+      HTTP 404 Not Found if candidate is not on the ballot
+      HTTP 500 on error
+    response payload:
+       JSON formatted
+        {
+        "message": "Changes saved"
+        }
+
+
+
+
+
+
+##################
+# VOTER REST API #
+##################
+
+============================
+Viewing election and issues:
+============================
+
+/steve/voter/view/$electionid
+  GET
+    input:
+      none
+    output:
+      HTTP 200 Okay if election exists
+      HTTP 404 Not Found if no such election
+    response payload:
+      JSON formatted
+        {
+        "base_data": {
+            "owner": "humbedooh", 
+            "starts": null, 
+            "ends": null, 
+            "monitors": [
+                "humbedooh@humbedooh.com"
+            ], 
+            "title": "Foo Elections, 2015"
+        }, 
+        "baseurl": "https://stv.website/steve/election?foo", 
+        "issues": [
+            {
+                "description": null, 
+                "title": "Test Board Election", 
+                "APIURL": "https://stv.website/steve/voter/view/foo/baz", 
+                "prettyURL": "https://stv.website/steve/ballot?foo/baz", 
+                "candidates": [
+                    {
+                        "name": "Just One Guy"
+                    }, 
+                    {
+                        "name": "John Doe", 
+                        "statement": "Vote for me!"
+                    }, 
+                    {
+                        "name": "Kate Smurf", 
+                        "statement": "Vote for me too!"
+                    }
+                ], 
+                "type": "stv6", 
+                "id": "baz"
+            }, 
+            {
+                "description": "This is to nominate WALL-E for ASF membership", 
+                "title": "Membership for WALL-E", 
+                "seconds": "humbedooh", 
+                "APIURL": "https://stv.website/steve/voter/view/foo/member-wall-e", 
+                "prettyURL": "https://stv.website/steve/ballot?foo/member-wall-e", 
+                "candidates": [], 
+                "nominatedby": "mattman", 
+                "type": "yna", 
+                "id": "member-wall-e"
+            }
+        ]
+    }
+    
+    
+=======================
+Viewing a single issue:
+=======================
+/steve/voter/view/$electionid/$issueid
+  GET
+    input:
+      none
+    output:
+      HTTP 200 Okay if issue exists
+      HTTP 404 Not Found if no such election/issue
+    response payload:
+      JSON formatted
+        {
+          "issue": {
+              "description": null, 
+              "title": "Test Board Election", 
+              "APIURL": "https://stv.website/steve/voter/view/foo/baz", 
+              "prettyURL": "https://stv.website/steve/ballot?foo/baz", 
+              "candidates": [
+                  {
+                      "name": "Just One Guy"
+                  }, 
+                  {
+                      "name": "John Doe", 
+                      "statement": "Vote for me!"
+                  }, 
+                  {
+                      "name": "Kate Smurf", 
+                      "statement": "Vote for me too!"
+                  }
+              ], 
+              "type": "stv6", 
+              "id": "baz"
+          }
+      }
+      
+      
+      
+      
+MORE TO COME!
\ No newline at end of file

Added: steve/trunk/pytest/httpd.conf
URL: http://svn.apache.org/viewvc/steve/trunk/pytest/httpd.conf?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/httpd.conf (added)
+++ steve/trunk/pytest/httpd.conf Wed Mar 18 12:17:02 2015
@@ -0,0 +1,22 @@
+# Sample httpd configuration for Steve's Python REST API test
+# see http://stv.website/steve/voter/view/foo for an example.
+
+<VirtualHost *80>
+DocumentRoot /home/voter/pytest/www/htdocs
+ServerName stv.website
+DirectoryIndex index.html
+<Directory /home/voter/pytest/www>
+  Require all granted
+  Options +ExecCGI
+  AddHandler cgi-script .py
+</Directory>
+
+# REST API
+ScriptAlias /steve/admin /home/voter/pytest//www/cgi-bin/rest_admin.py
+ScriptAlias /steve/voter /home/voter/pytest/www/cgi-bin/rest_voter.py
+
+# HTML generators
+ScriptAlias /steve/ballot /home/voter/pytest/www/cgi-bin/html_ballot.py
+ScriptAlias /steve/election /home/voter/pytest/www/cgi-bin/html_election.py
+
+</VirtualHost>

Added: steve/trunk/pytest/steve.cfg
URL: http://svn.apache.org/viewvc/steve/trunk/pytest/steve.cfg?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/steve.cfg (added)
+++ steve/trunk/pytest/steve.cfg Wed Mar 18 12:17:02 2015
@@ -0,0 +1,3 @@
+[general]
+
+homedir:            /home/voter

Added: steve/trunk/pytest/www/cgi-bin/lib/__init__.py
URL: http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/lib/__init__.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/lib/__init__.py (added)
+++ steve/trunk/pytest/www/cgi-bin/lib/__init__.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,3 @@
+"""
+Stuff
+"""
\ No newline at end of file

Added: steve/trunk/pytest/www/cgi-bin/lib/response.py
URL: http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/lib/response.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/lib/response.py (added)
+++ steve/trunk/pytest/www/cgi-bin/lib/response.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+import json
+
+responseCodes = {
+    200: 'Okay',
+    201: 'Created',
+    304: 'Not Modified',
+    400: 'Bad Request',
+    403: 'Access denied',
+    404: 'Not Found',
+    500: 'Server Error'
+}
+
+def respond(code, js):
+    c = responseCodes[code] if code in responseCodes else "Unknown Response Code(?)"
+    out = json.dumps(js, indent=4)
+    print("Status: %u %s\r\nContent-Type: application/json\r\nContent-Length: %u\r\n" % (code, c, len(out)))
+    print(out)
+    
+    
+    
\ No newline at end of file

Added: steve/trunk/pytest/www/cgi-bin/rest_admin.py
URL: http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/rest_admin.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/rest_admin.py (added)
+++ steve/trunk/pytest/www/cgi-bin/rest_admin.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+#####
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#####
+import os, sys, json, re, time, base64, cgi, subprocess, hashlib
+version = 2
+if sys.hexversion < 0x03000000:
+    import ConfigParser as configparser
+else:
+    import configparser
+    version = 3
+
+path = os.path.abspath(os.getcwd())
+
+sys.path.append(path)
+sys.path.append(os.path.basename(sys.argv[0]))
+if 'SCRIPT_FILENAME' in os.environ:
+    sys.path.insert(0, os.path.basename(os.environ['SCRIPT_FILENAME']))
+    
+from lib import response
+
+
+# Fetch config (hack, hack, hack)
+config = configparser.RawConfigParser()
+config.read(path + '/../../steve.cfg')
+
+# Some quick paths
+homedir = config.get("general", "homedir")
+pathinfo = os.environ['PATH_INFO'] if 'PATH_INFO' in os.environ else None
+form = cgi.FieldStorage();
+
+
+
+# TODO: Authentication goes here
+karma = 5 # assume admin karma for now
+
+# Figure out what to do and where
+if pathinfo:
+    l = pathinfo.split("/")
+    if l[0] == "":
+        l.pop(0)
+    action = l[0]
+    election = l[1] if len(l) > 1 else None
+
+
+    # Set up new election?
+    if action == "setup":
+        if karma >= 5: # karma of 5 required to set up an election base
+            if election:
+                if os.path.isdir(os.path.join(homedir, "issues", election)):
+                    response.respond(403, {'message': "Election already exists!"})
+                else:
+                    try:
+                        required = ['title','owner','monitors']
+                        xr = required
+                        for i in required:
+                            if not form.getvalue(i):
+                                raise Exception("Required fields missing: %s" % ", ".join(xr))
+                            else:
+                                xr.pop(0)
+                        elpath = os.path.join(homedir, "issues", election)
+                        os.mkdir(elpath)
+                        with open(elpath  + "/basedata.json", "w") as f:
+                            f.write(json.dumps({
+                                'title': form.getvalue('title'),
+                                'owner': form.getvalue('owner'),
+                                'monitors': form.getvalue('monitors').split(","),
+                                'starts': form.getvalue('starts'),
+                                'ends': form.getvalue('ends')
+                            }))
+                            f.close()
+                        response.respond(201, {'message': 'Created!'})
+                    except Exception as err:
+                        response.respond(500, {'message': "Could not create election: %s" % err})
+            else:
+                response.respond(400, {'message': "No election name specified!"})
+        else:
+            response.respond(403, {'message': 'You do not have enough karma for this'})
+            
+    # Create an issue in an election
+    elif action == "create":
+        if karma >= 4: # karma of 4 required to set up an issue for the election
+            if election:
+                issue = l[2] if len(l) > 2 else None
+                if not issue:
+                    response.respond(400, {'message': 'No issue ID specified'})
+                else:
+                    issuepath = os.path.join(homedir, "issues", election, issue)
+                    if os.path.isfile(issuepath + ".json"):
+                        response.respond(400, {'message': 'An issue with this ID already exists'})
+                    else:
+                        try:
+                            required = ['title','type']
+                            xr = required
+                            for i in required:
+                                if not form.getvalue(i):
+                                    raise Exception("Required fields missing: %s" % ", ".join(xr))
+                                else:
+                                    xr.pop(0)
+                            valid_types = ['yna','stv1','stv2','stv3','stv4','stv5','stv6','stv7','stv8','stv9']
+                            if not form.getvalue('type') in valid_types:
+                                raise Exception('Invalid vote type: %s' % form.getvalue('type'))
+                            with open(issuepath + ".json", "w") as f:
+                                candidates = []
+                                if form.getvalue('candidates'):
+                                    for name in form.getvalue('candidates').split("\n"):
+                                        candidates.append({'name': name})
+                                f.write(json.dumps({
+                                    'title': form.getvalue('title'),
+                                    'description': form.getvalue('description'),
+                                    'type': form.getvalue('type'),
+                                    'candidates': candidates,
+                                    'seconds': form.getvalue('seconds'),
+                                    'nominatedby': form.getvalue('nominatedby')
+                                }))
+                                f.close()
+                            response.respond(201, {'message': 'Created!'})
+                        except Exception as err:
+                            response.respond(500, {'message': "Could not create issue: %s" % err})
+            else:
+                response.respond(400, {'message': "No election specified!"})
+        else:
+            response.respond(403, {'message': 'You do not have enough karma for this'})
+    
+    # Edit an issue or election
+    elif action == "edit":
+        issue = l[2] if len(l) > 2 else None
+        if (issue and karma >= 4) or (karma >= 5 and election):
+            if election:
+                if not issue:
+                    elpath = os.path.join(homedir, "issues", election)
+                    if not os.path.isdir(elpath) or not os.path.isfile(elpath+"/basedata.json"):
+                        response.respond(404, {'message': 'No such issue'})
+                    else:
+                        try:
+                            js = {}
+                            with open(elpath + "/basedata.json", "r") as f:
+                                js = json.loads(f.read())
+                                f.close()
+                            fields = ['title','owner','monitors','starts','ends']
+                            for field in fields:
+                                val = form.getvalue(field)
+                                if val:
+                                    if field == "monitors":
+                                        val = val.split(",")
+                                    js[field] = val
+                            with open(elpath + "/basedata.json", "w") as f:
+                                f.write(json.dumps(js))
+                                f.close()
+                            response.respond(200, {'message': "Changed saved"})
+                        except Exception as err:
+                            response.respond(500, {'message': "Could not edit election: %s" % err})
+                else:
+                    issuepath = os.path.join(homedir, "issues", election, issue)
+                    if not os.path.isfile(issuepath + ".json"):
+                        response.respond(404, {'message': 'No such issue'})
+                    else:
+                        try:
+                            js = {}
+                            with open(issuepath + ".json", "r") as f:
+                                js = json.loads(f.read())
+                                f.close()
+                            fields = ['title','description','type','candidates','seconds','nominatedby']
+                            for field in fields:
+                                val = form.getvalue(field)
+                                if val:
+                                    if field == "candidates" or field == "seconds":
+                                        xval = val.split("\n")
+                                        val = []
+                                        for entry in xval:
+                                            val.append({'name': entry})
+                                    js[field] = val
+                            with open(issuepath + ".json", "w") as f:
+                                f.write(json.dumps(js))
+                                f.close()
+                            response.respond(200, {'message': "Changed saved"})
+                        except Exception as err:
+                            response.respond(500, {'message': "Could not edit issue: %s" % err})
+            else:
+                response.respond(400, {'message': "No election specified!"})
+        else:
+            response.respond(403, {'message': 'You do not have enough karma for this'})
+    
+    # Edit/add a statement
+    elif action == "statement":
+        issue = l[2] if len(l) > 2 else None
+        if (issue and karma >= 4):
+            issuepath = os.path.join(homedir, "issues", election, issue)
+            if not os.path.isfile(issuepath + ".json"):
+                response.respond(404, {'message': 'No such issue'})
+            else:
+                try:
+                    js = {}
+                    with open(issuepath + ".json", "r") as f:
+                        js = json.loads(f.read())
+                        f.close()
+                    
+                    cand = form.getvalue('candidate')
+                    stat = form.getvalue('statement')
+                    found = False
+                    for entry in js['candidates']:
+                        if entry['name'] == cand:
+                            found = True
+                            entry['statement'] = stat
+                            break
+                    if not found:
+                        raise Exception("No such candidate: " + cand)                    
+                    with open(issuepath + ".json", "w") as f:
+                        f.write(json.dumps(js))
+                        f.close()
+                    response.respond(200, {'message': "Changed saved"})
+                except Exception as err:
+                    response.respond(500, {'message': "Could not edit issue: %s" % err})
+        else:
+            response.respond(403, {'message': 'You do not have enough karma for this'})
+            
+    # Edit/add a statement
+    elif action == "addcandidate":
+        issue = l[2] if len(l) > 2 else None
+        if (issue and karma >= 4):
+            issuepath = os.path.join(homedir, "issues", election, issue)
+            if not os.path.isfile(issuepath + ".json"):
+                response.respond(404, {'message': 'No such issue'})
+            else:
+                try:
+                    js = {}
+                    with open(issuepath + ".json", "r") as f:
+                        js = json.loads(f.read())
+                        f.close()
+                    
+                    cand = form.getvalue('candidate')
+                    stat = form.getvalue('statement')
+                    found = False
+                    for entry in js['candidates']:
+                        if entry['name'] == cand:
+                            found = True
+                            break
+                    if found:
+                        raise Exception("Candidate already exists: " + cand)
+                    else:
+                        js['candidates'].append( {
+                            'name': cand,
+                            'statement': stat
+                        })
+                    with open(issuepath + ".json", "w") as f:
+                        f.write(json.dumps(js))
+                        f.close()
+                    response.respond(200, {'message': "Changed saved"})
+                except Exception as err:
+                    response.respond(500, {'message': "Could not edit issue: %s" % err})
+        else:
+            response.respond(403, {'message': 'You do not have enough karma for this'})
+    elif action == "delcandidate":
+        issue = l[2] if len(l) > 2 else None
+        if (issue and karma >= 4):
+            issuepath = os.path.join(homedir, "issues", election, issue)
+            if not os.path.isfile(issuepath + ".json"):
+                response.respond(404, {'message': 'No such issue'})
+            else:
+                try:
+                    js = {}
+                    with open(issuepath + ".json", "r") as f:
+                        js = json.loads(f.read())
+                        f.close()
+                    
+                    cand = form.getvalue('candidate')
+                    found = False
+                    i = 0
+                    for entry in js['candidates']:
+                        if entry['name'] == cand:
+                            js['candidates'].pop(i)
+                            found = True
+                            break
+                        i += 1
+                    if not found:
+                        raise Exception("Candidate does nost exist: " + cand)
+                    with open(issuepath + ".json", "w") as f:
+                        f.write(json.dumps(js))
+                        f.close()
+                    response.respond(200, {'message': "Changed saved"})
+                except Exception as err:
+                    response.respond(500, {'message': "Could not edit issue: %s" % err})
+        else:
+            response.respond(403, {'message': 'You do not have enough karma for this'})
+    else:
+        response.respond(400, {'message': "No (or invalid) action supplied"})
+else:
+    response.respond(500, {'message': "No path_info supplied"})

Added: steve/trunk/pytest/www/cgi-bin/rest_voter.py
URL: http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/rest_voter.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/rest_voter.py (added)
+++ steve/trunk/pytest/www/cgi-bin/rest_voter.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+#####
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#####
+import os, sys, json, re, time, base64, cgi, subprocess, hashlib
+from os import listdir
+version = 2
+if sys.hexversion < 0x03000000:
+    import ConfigParser as configparser
+else:
+    import configparser
+    version = 3
+
+path = os.path.abspath(os.getcwd())
+
+sys.path.append(path)
+sys.path.append(os.path.basename(sys.argv[0]))
+if 'SCRIPT_FILENAME' in os.environ:
+    sys.path.insert(0, os.path.basename(os.environ['SCRIPT_FILENAME']))
+    
+from lib import response
+
+
+# Fetch config (hack, hack, hack)
+config = configparser.RawConfigParser()
+config.read(path + '/../../steve.cfg')
+
+# Some quick paths
+homedir = config.get("general", "homedir")
+pathinfo = os.environ['PATH_INFO'] if 'PATH_INFO' in os.environ else None
+form = cgi.FieldStorage();
+
+
+
+# TODO: Authentication goes here
+karma = 5 # assume admin karma for now
+
+# Figure out what to do and where
+if pathinfo:
+    l = pathinfo.split("/")
+    if l[0] == "":
+        l.pop(0)
+    action = l[0]
+    election = l[1] if len(l) > 1 else None
+    issue = l[2]  if len(l) > 2 else None
+    voterid = l[3] if len(l) > 3 else None
+    
+    if action == "view":
+        # View a list of issues for an election
+        if election and not issue:
+            js = []
+            elpath = os.path.join(homedir, "issues", election)
+            if os.path.isdir(elpath):
+                basedata = {}
+                try:
+                    with open(elpath + "/basedata.json", "r") as f:
+                        basedata = json.loads(f.read())
+                        f.close()
+                    issues = [ f for f in listdir(elpath) if os.path.isfile(os.path.join(elpath,f)) and f != "basedata.json" ]
+                    for issue in issues:
+                        try:
+                            with open(elpath + "/" + issue, "r") as f:
+                                entry = json.loads(f.read())
+                                f.close()
+                                entry['id'] = issue.strip(".json")
+                                entry['APIURL'] = "https://%s/steve/voter/view/%s/%s" % (os.environ['SERVER_NAME'], election, issue.strip(".json"))
+                                entry['prettyURL'] = "https://%s/steve/ballot?%s/%s" % (os.environ['SERVER_NAME'], election, issue.strip(".json"))
+                                js.append(entry)
+                        except Exception as err:
+                            response.respond(500, {'message': 'Could not load issues: %s' % err})
+                except Exception as err:
+                    response.respond(500, {'message': 'Could not load base data: %s' % err})
+                response.respond(200, {'base_data': basedata, 'issues': js, 'baseurl': "https://%s/steve/election?%s" % (os.environ['SERVER_NAME'], election)})
+            else:
+                response.respond(404, {'message': 'No such election'})
+                
+        # View a speficic issue
+        elif election and issue:
+            js = []
+            issuepath = os.path.join(homedir, "issues", election, issue)
+            if os.path.isfile(issuepath + ".json"):
+                try:
+                    with open(issuepath + ".json", "r") as f:
+                        entry = json.loads(f.read())
+                        f.close()
+                        entry['id'] = issue.strip(".json")
+                        entry['APIURL'] = "https://%s/steve/voter/view/%s/%s" % (os.environ['SERVER_NAME'], election, issue)
+                        entry['prettyURL'] = "https://%s/steve/ballot?%s/%s" % (os.environ['SERVER_NAME'], election, issue)
+                        response.respond(200, {'issue': entry})
+                except Exception as err:
+                    response.respond(500, {'message': "Could not load issue: %s" % err})
+            else:
+                response.respond(404, {'message': 'No such issue'})
+        else:
+            response.respond(404, {'message': 'No election ID supplied'})
+    elif action == "vote":
+        response.respond(500, {'message': 'Not implemented yet'})
+    else:
+        response.respond(400, {'message': 'Invalid action supplied'})
+else:
+    response.respond(500, {'message': 'No path info supplied, aborting'})
\ No newline at end of file



Re: svn commit: r1667520 - in /steve/trunk/pytest: ./ REST_API_README.txt httpd.conf steve.cfg www/ www/cgi-bin/ www/cgi-bin/lib/ www/cgi-bin/lib/__init__.py www/cgi-bin/lib/response.py www/cgi-bin/rest_admin.py www/cgi-bin/rest_voter.py www/htdocs/

Posted by "Alan D. Cabrera" <li...@toolazydogs.com>.
> On Mar 18, 2015, at 5:17 AM, humbedooh@apache.org wrote:
> 
> +                            valid_types = ['yna','stv1','stv2','stv3','stv4','stv5','stv6','stv7','stv8','stv9']
> +                            if not form.getvalue('type') in valid_types:


The variable valid_types is a constant and so it should be a tuple.  Since users of this code would be interested in what the library considers the list of valid types, it would be nice if VALID_TYPES, all caps, would be a module level variable defined at the top that clients could inspect.


Regards,
Alan


Re: svn commit: r1667520 - in /steve/trunk/pytest: ./ REST_API_README.txt httpd.conf steve.cfg www/ www/cgi-bin/ www/cgi-bin/lib/ www/cgi-bin/lib/__init__.py www/cgi-bin/lib/response.py www/cgi-bin/rest_admin.py www/cgi-bin/rest_voter.py www/htdocs/

Posted by "Alan D. Cabrera" <li...@toolazydogs.com>.
> On Mar 18, 2015, at 5:17 AM, humbedooh@apache.org wrote:
> 
> +version = 2
> +if sys.hexversion < 0x03000000:
> +    import ConfigParser as configparser
> +else:
> +    import configparser
> +    version = 3
> +

Daniel, I just caught this as I was transliterating your stuff.

The project decided a while back to focus on Python 2.6+.  This will make coding easier.  :D


Regards,
Alan