summary refs log tree commit diff
diff options
context:
space:
mode:
authorErik Johnston <erik@matrix.org>2015-01-23 15:51:48 +0000
committerErik Johnston <erik@matrix.org>2015-01-23 15:51:48 +0000
commit9b1e552b5163aad84a3ae4fd70116d3e782fb443 (patch)
tree19d827838d10688da26e3fa5503c63b71fd34db0
parentAdd storage method have_events (diff)
parentUpdate .gitignore (diff)
downloadsynapse-9b1e552b5163aad84a3ae4fd70116d3e782fb443.tar.xz
Merge branch 'develop' of github.com:matrix-org/synapse into rejections
-rw-r--r--.gitignore17
-rw-r--r--MANIFEST.in14
-rw-r--r--UPGRADE.rst4
-rwxr-xr-xcontrib/cmdclient/console.py (renamed from cmdclient/console.py)0
-rw-r--r--contrib/cmdclient/http.py (renamed from cmdclient/http.py)0
-rw-r--r--contrib/experiments/cursesio.py (renamed from experiments/cursesio.py)0
-rw-r--r--contrib/experiments/test_messaging.py (renamed from experiments/test_messaging.py)0
-rw-r--r--contrib/graph/graph.py (renamed from graph/graph.py)0
-rw-r--r--contrib/graph/graph2.py (renamed from graph/graph2.py)0
-rwxr-xr-xdemo/start.sh3
-rw-r--r--jsfiddles/create_room_send_msg/demo.css17
-rw-r--r--jsfiddles/create_room_send_msg/demo.html30
-rw-r--r--jsfiddles/create_room_send_msg/demo.js113
-rw-r--r--jsfiddles/event_stream/demo.css17
-rw-r--r--jsfiddles/event_stream/demo.html23
-rw-r--r--jsfiddles/event_stream/demo.js145
-rw-r--r--jsfiddles/example_app/demo.css43
-rw-r--r--jsfiddles/example_app/demo.details7
-rw-r--r--jsfiddles/example_app/demo.html56
-rw-r--r--jsfiddles/example_app/demo.js327
-rw-r--r--jsfiddles/register_login/demo.css7
-rw-r--r--jsfiddles/register_login/demo.html20
-rw-r--r--jsfiddles/register_login/demo.js79
-rw-r--r--jsfiddles/room_memberships/demo.css17
-rw-r--r--jsfiddles/room_memberships/demo.html37
-rw-r--r--jsfiddles/room_memberships/demo.js141
-rwxr-xr-xscripts/database-prepare-for-0.0.1.sh (renamed from database-prepare-for-0.0.1.sh)0
-rwxr-xr-xscripts/database-prepare-for-0.5.0.sh (renamed from database-prepare-for-0.5.0.sh)0
-rwxr-xr-xscripts/database-save.sh (renamed from database-save.sh)0
-rwxr-xr-xscripts/nuke-room-from-db.sh (renamed from nuke-room-from-db.sh)0
-rw-r--r--scripts/sphinx_api_docs.sh (renamed from sphinx_api_docs.sh)0
-rw-r--r--setup.cfg8
-rw-r--r--synapse/api/auth.py9
-rwxr-xr-xsynapse/app/homeserver.py9
-rw-r--r--synapse/handlers/_base.py5
-rw-r--r--synapse/handlers/directory.py3
-rw-r--r--synapse/handlers/events.py3
-rw-r--r--synapse/handlers/federation.py13
-rw-r--r--synapse/handlers/message.py13
-rw-r--r--synapse/handlers/presence.py23
-rw-r--r--synapse/handlers/profile.py3
-rw-r--r--synapse/handlers/room.py16
-rw-r--r--synapse/handlers/typing.py3
-rw-r--r--synapse/http/servlet.py (renamed from synapse/rest/base.py)24
-rw-r--r--synapse/rest/__init__.py35
-rw-r--r--synapse/rest/client/__init__.py14
-rw-r--r--synapse/rest/client/v1/__init__.py43
-rw-r--r--synapse/rest/client/v1/admin.py (renamed from synapse/rest/admin.py)8
-rw-r--r--synapse/rest/client/v1/base.py52
-rw-r--r--synapse/rest/client/v1/directory.py (renamed from synapse/rest/directory.py)11
-rw-r--r--synapse/rest/client/v1/events.py (renamed from synapse/rest/events.py)6
-rw-r--r--synapse/rest/client/v1/initial_sync.py (renamed from synapse/rest/initial_sync.py)4
-rw-r--r--synapse/rest/client/v1/login.py (renamed from synapse/rest/login.py)8
-rw-r--r--synapse/rest/client/v1/presence.py (renamed from synapse/rest/presence.py)19
-rw-r--r--synapse/rest/client/v1/profile.py (renamed from synapse/rest/profile.py)19
-rw-r--r--synapse/rest/client/v1/register.py (renamed from synapse/rest/register.py)4
-rw-r--r--synapse/rest/client/v1/room.py (renamed from synapse/rest/room.py)37
-rw-r--r--synapse/rest/client/v1/transactions.py (renamed from synapse/rest/transactions.py)0
-rw-r--r--synapse/rest/client/v1/voip.py (renamed from synapse/rest/voip.py)4
-rw-r--r--synapse/rest/media/__init__.py (renamed from synapse/media/__init__.py)0
-rw-r--r--synapse/rest/media/v0/__init__.py (renamed from synapse/media/v0/__init__.py)0
-rw-r--r--synapse/rest/media/v0/content_repository.py (renamed from synapse/media/v0/content_repository.py)0
-rw-r--r--synapse/rest/media/v1/__init__.py (renamed from synapse/media/v1/__init__.py)0
-rw-r--r--synapse/rest/media/v1/base_resource.py (renamed from synapse/media/v1/base_resource.py)0
-rw-r--r--synapse/rest/media/v1/download_resource.py (renamed from synapse/media/v1/download_resource.py)0
-rw-r--r--synapse/rest/media/v1/filepath.py (renamed from synapse/media/v1/filepath.py)0
-rw-r--r--synapse/rest/media/v1/media_repository.py (renamed from synapse/media/v1/media_repository.py)0
-rw-r--r--synapse/rest/media/v1/thumbnail_resource.py (renamed from synapse/media/v1/thumbnail_resource.py)0
-rw-r--r--synapse/rest/media/v1/thumbnailer.py (renamed from synapse/media/v1/thumbnailer.py)0
-rw-r--r--synapse/rest/media/v1/upload_resource.py (renamed from synapse/media/v1/upload_resource.py)0
-rw-r--r--synapse/server.py35
-rw-r--r--synapse/storage/roommember.py5
-rw-r--r--tests/handlers/test_directory.py7
-rw-r--r--tests/handlers/test_presence.py514
-rw-r--r--tests/handlers/test_presencelike.py9
-rw-r--r--tests/handlers/test_profile.py8
-rw-r--r--tests/handlers/test_room.py9
-rw-r--r--tests/handlers/test_typing.py7
-rw-r--r--tests/rest/__init__.py3
-rw-r--r--tests/rest/client/__init__.py14
-rw-r--r--tests/rest/client/v1/__init__.py15
-rw-r--r--tests/rest/client/v1/test_events.py (renamed from tests/rest/test_events.py)14
-rw-r--r--tests/rest/client/v1/test_presence.py (renamed from tests/rest/test_presence.py)30
-rw-r--r--tests/rest/client/v1/test_profile.py (renamed from tests/rest/test_profile.py)9
-rw-r--r--tests/rest/client/v1/test_rooms.py (renamed from tests/rest/test_rooms.py)39
-rw-r--r--tests/rest/client/v1/test_typing.py (renamed from tests/rest/test_typing.py)11
-rw-r--r--tests/rest/client/v1/utils.py (renamed from tests/rest/utils.py)0
-rw-r--r--tests/storage/TESTS_NEEDED_FOR5
-rw-r--r--tests/storage/test_directory.py5
-rw-r--r--tests/storage/test_presence.py5
-rw-r--r--tests/storage/test_profile.py3
-rw-r--r--tests/storage/test_redaction.py7
-rw-r--r--tests/storage/test_room.py9
-rw-r--r--tests/storage/test_roommember.py9
-rw-r--r--tests/storage/test_stream.py9
-rw-r--r--tests/test_types.py12
96 files changed, 599 insertions, 1707 deletions
diff --git a/.gitignore b/.gitignore
index af90668c89..3899c5a250 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,17 +26,18 @@ htmlcov
 
 demo/*.db
 demo/*.log
+demo/*.log.*
 demo/*.pid
+demo/media_store.*
 demo/etc
 
-graph/*.svg
-graph/*.png
-graph/*.dot
-
-**/webclient/config.js
-**/webclient/test/coverage/
-**/webclient/test/environment-protractor.js
-
 uploads
 
 .idea/
+media_store/
+
+*.tac
+
+build/
+
+localhost-800*/
diff --git a/MANIFEST.in b/MANIFEST.in
index a1a77ff540..8243a942ee 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,14 @@
+include synctl
+include LICENSE
+include VERSION
+include *.rst
+include demo/README
+
+recursive-include synapse/storage/schema *.sql
+
+recursive-include demo *.dh
+recursive-include demo *.py
+recursive-include demo *.sh
 recursive-include docs *
+recursive-include scripts *
 recursive-include tests *.py
-recursive-include synapse/storage/schema *.sql
-recursive-include syweb/webclient *
diff --git a/UPGRADE.rst b/UPGRADE.rst
index 9618ad2d57..0f81f3e11f 100644
--- a/UPGRADE.rst
+++ b/UPGRADE.rst
@@ -52,7 +52,7 @@ resulting conflicts during the upgrade process.
 Before running the command the homeserver should be first completely 
 shutdown. To run it, simply specify the location of the database, e.g.:
 
-  ./database-prepare-for-0.5.0.sh "homeserver.db"
+  ./scripts/database-prepare-for-0.5.0.sh "homeserver.db"
 
 Once this has successfully completed it will be safe to restart the 
 homeserver. You may notice that the homeserver takes a few seconds longer to 
@@ -147,7 +147,7 @@ rooms the home server was a member of and room alias mappings.
 Before running the command the homeserver should be first completely 
 shutdown. To run it, simply specify the location of the database, e.g.:
 
-  ./database-prepare-for-0.0.1.sh "homeserver.db"
+  ./scripts/database-prepare-for-0.0.1.sh "homeserver.db"
 
 Once this has successfully completed it will be safe to restart the 
 homeserver. You may notice that the homeserver takes a few seconds longer to 
diff --git a/cmdclient/console.py b/contrib/cmdclient/console.py
index d9c6ec6a70..d9c6ec6a70 100755
--- a/cmdclient/console.py
+++ b/contrib/cmdclient/console.py
diff --git a/cmdclient/http.py b/contrib/cmdclient/http.py
index 869f782ec1..869f782ec1 100644
--- a/cmdclient/http.py
+++ b/contrib/cmdclient/http.py
diff --git a/experiments/cursesio.py b/contrib/experiments/cursesio.py
index 95d87a1fda..95d87a1fda 100644
--- a/experiments/cursesio.py
+++ b/contrib/experiments/cursesio.py
diff --git a/experiments/test_messaging.py b/contrib/experiments/test_messaging.py
index fedf786cec..fedf786cec 100644
--- a/experiments/test_messaging.py
+++ b/contrib/experiments/test_messaging.py
diff --git a/graph/graph.py b/contrib/graph/graph.py
index b2acadcf5e..b2acadcf5e 100644
--- a/graph/graph.py
+++ b/contrib/graph/graph.py
diff --git a/graph/graph2.py b/contrib/graph/graph2.py
index 6b551d42e5..6b551d42e5 100644
--- a/graph/graph2.py
+++ b/contrib/graph/graph2.py
diff --git a/demo/start.sh b/demo/start.sh
index ce3e292486..bb2248770d 100755
--- a/demo/start.sh
+++ b/demo/start.sh
@@ -32,7 +32,8 @@ for port in 8080 8081 8082; do
         -D --pid-file "$DIR/$port.pid" \
         --manhole $((port + 1000)) \
         --tls-dh-params-path "demo/demo.tls.dh" \
-		$PARAMS $SYNAPSE_PARAMS
+        --media-store-path "demo/media_store.$port" \
+		$PARAMS $SYNAPSE_PARAMS \
 
     python -m synapse.app.homeserver \
         --config-path "demo/etc/$port.config" \
diff --git a/jsfiddles/create_room_send_msg/demo.css b/jsfiddles/create_room_send_msg/demo.css
deleted file mode 100644
index 48a55f372d..0000000000
--- a/jsfiddles/create_room_send_msg/demo.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.loggedin {
-    visibility: hidden;
-}
-
-p {
-    font-family: monospace;
-}
-
-table
-{
-    border-spacing:5px;
-}
-
-th,td
-{
-    padding:5px;
-}
diff --git a/jsfiddles/create_room_send_msg/demo.html b/jsfiddles/create_room_send_msg/demo.html
deleted file mode 100644
index 088ff7ac0f..0000000000
--- a/jsfiddles/create_room_send_msg/demo.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<div>
-    <p>This room creation / message sending demo requires a home server to be running on http://localhost:8008</p>
-</div>
-<form class="loginForm">
-    <input type="text" id="userLogin" placeholder="Username"></input>
-    <input type="password" id="passwordLogin" placeholder="Password"></input>
-    <input type="button" class="login" value="Login"></input>
-</form>
-<div class="loggedin">
-    <form class="createRoomForm">
-        <input type="text" id="roomAlias" placeholder="Room alias (optional)"></input>
-        <input type="button" class="createRoom" value="Create Room"></input>
-    </form>
-    <form class="sendMessageForm">
-        <input type="text" id="roomId" placeholder="Room ID"></input>
-        <input type="text" id="messageBody" placeholder="Message body"></input>
-        <input type="button" class="sendMessage" value="Send Message"></input>
-    </form>
-    <table id="rooms">
-        <tbody>
-            <tr>
-                <th>Room ID</th>
-                <th>My state</th>
-                <th>Room Alias</th>
-                <th>Latest message</th>
-            </tr>
-        </tbody>
-    </table>
-</div>
-    
diff --git a/jsfiddles/create_room_send_msg/demo.js b/jsfiddles/create_room_send_msg/demo.js
deleted file mode 100644
index 9c346e2f64..0000000000
--- a/jsfiddles/create_room_send_msg/demo.js
+++ /dev/null
@@ -1,113 +0,0 @@
-var accountInfo = {};
-
-var showLoggedIn = function(data) {
-    accountInfo = data;
-    getCurrentRoomList();
-    $(".loggedin").css({visibility: "visible"});
-};
-
-$('.login').live('click', function() {
-    var user = $("#userLogin").val();
-    var password = $("#passwordLogin").val();
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/login",
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
-        dataType: "json",
-        success: function(data) {
-            showLoggedIn(data);
-        },
-        error: function(err) {
-            var errMsg = "To try this, you need a home server running!";
-            var errJson = $.parseJSON(err.responseText);
-            if (errJson) {
-                errMsg = JSON.stringify(errJson);   
-            }
-            alert(errMsg);  
-        }
-    }); 
-});
-
-var getCurrentRoomList = function() {
-    var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
-    $.getJSON(url, function(data) {
-        var rooms = data.rooms;
-        for (var i=0; i<rooms.length; ++i) {
-            rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
-            addRoom(rooms[i]);   
-        }
-    }).fail(function(err) {
-        alert(JSON.stringify($.parseJSON(err.responseText)));
-    });
-};
-
-$('.createRoom').live('click', function() {
-    var roomAlias = $("#roomAlias").val();
-    var data = {};
-    if (roomAlias.length > 0) {
-        data.room_alias_name = roomAlias;   
-    }
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify(data),
-        dataType: "json",
-        success: function(data) {
-            data.membership = "join"; // you are automatically joined into every room you make.
-            data.latest_message = "";
-            addRoom(data);
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    }); 
-});
-
-var addRoom = function(data) {
-    row = "<tr>" +
-        "<td>"+data.room_id+"</td>" +
-        "<td>"+data.membership+"</td>" +
-        "<td>"+data.room_alias+"</td>" +
-        "<td>"+data.latest_message+"</td>" +
-        "</tr>";
-    $("#rooms").append(row);
-};
-
-$('.sendMessage').live('click', function() {
-    var roomId = $("#roomId").val();
-    var body = $("#messageBody").val();
-    var msgId = $.now();
-    
-    if (roomId.length === 0 || body.length === 0) {
-        return;
-    }
-    
-    var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
-    url = url.replace("$token", accountInfo.access_token);
-    url = url.replace("$roomid", encodeURIComponent(roomId));
-    
-    var data = {
-        msgtype: "m.text",
-        body: body
-    };
-    
-    $.ajax({
-        url: url,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify(data),
-        dataType: "json",
-        success: function(data) {
-            $("#messageBody").val("");
-            // wipe the table and reload it. Using the event stream would be the best
-            // solution but that is out of scope of this fiddle.
-            $("#rooms").find("tr:gt(0)").remove();
-            getCurrentRoomList();
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    }); 
-});
diff --git a/jsfiddles/event_stream/demo.css b/jsfiddles/event_stream/demo.css
deleted file mode 100644
index 48a55f372d..0000000000
--- a/jsfiddles/event_stream/demo.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.loggedin {
-    visibility: hidden;
-}
-
-p {
-    font-family: monospace;
-}
-
-table
-{
-    border-spacing:5px;
-}
-
-th,td
-{
-    padding:5px;
-}
diff --git a/jsfiddles/event_stream/demo.html b/jsfiddles/event_stream/demo.html
deleted file mode 100644
index 7657780d28..0000000000
--- a/jsfiddles/event_stream/demo.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<div>
-    <p>This event stream demo requires a home server to be running on http://localhost:8008</p>
-</div>
-<form class="loginForm">
-    <input type="text" id="userLogin" placeholder="Username"></input>
-    <input type="password" id="passwordLogin" placeholder="Password"></input>
-    <input type="button" class="login" value="Login"></input>
-</form>
-<div class="loggedin">
-    <form class="sendMessageForm">
-        <input type="button" class="sendMessage" value="Send random message"></input>
-    </form>
-    <p id="streamErrorText"></p>
-    <table id="rooms">
-        <tbody>
-            <tr>
-                <th>Room ID</th>
-                <th>Latest message</th>
-            </tr>
-        </tbody>
-    </table>
-</div>
-    
diff --git a/jsfiddles/event_stream/demo.js b/jsfiddles/event_stream/demo.js
deleted file mode 100644
index acba8391fa..0000000000
--- a/jsfiddles/event_stream/demo.js
+++ /dev/null
@@ -1,145 +0,0 @@
-var accountInfo = {};
-
-var eventStreamInfo = {
-    from: "END"
-};
-
-var roomInfo = [];
-
-var longpollEventStream = function() {
-    var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
-    url = url.replace("$token", accountInfo.access_token);
-    url = url.replace("$from", eventStreamInfo.from);
-    
-    $.getJSON(url, function(data) {
-        eventStreamInfo.from = data.end;
-        
-        var hasNewLatestMessage = false;
-        for (var i=0; i<data.chunk.length; ++i) {
-            if (data.chunk[i].type === "m.room.message") {
-                for (var j=0; j<roomInfo.length; ++j) {
-                    if (roomInfo[j].room_id === data.chunk[i].room_id) {
-                        roomInfo[j].latest_message = data.chunk[i].content.body;
-                        hasNewLatestMessage = true;
-                    }
-                }
-            }
-        }
-        
-        if (hasNewLatestMessage) {
-           setRooms(roomInfo);
-        }
-        $("#streamErrorText").text("");
-        longpollEventStream();
-    }).fail(function(err) {
-        $("#streamErrorText").text("Event stream error: "+JSON.stringify($.parseJSON(err.responseText)));
-        setTimeout(longpollEventStream, 5000);
-    });
-};
-
-var showLoggedIn = function(data) {
-    accountInfo = data;
-    longpollEventStream();
-    getCurrentRoomList();
-    $(".loggedin").css({visibility: "visible"});
-};
-
-$('.login').live('click', function() {
-    var user = $("#userLogin").val();
-    var password = $("#passwordLogin").val();
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/login",
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
-        dataType: "json",
-        success: function(data) {
-            $("#rooms").find("tr:gt(0)").remove();
-            showLoggedIn(data);
-        },
-        error: function(err) {
-            var errMsg = "To try this, you need a home server running!";
-            var errJson = $.parseJSON(err.responseText);
-            if (errJson) {
-                errMsg = JSON.stringify(errJson);   
-            }
-            alert(errMsg);  
-        }
-    }); 
-});
-
-var getCurrentRoomList = function() {
-    $("#roomId").val("");
-    var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
-    $.getJSON(url, function(data) {
-        var rooms = data.rooms;
-        for (var i=0; i<rooms.length; ++i) {
-            if ("messages" in rooms[i]) {
-                rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;   
-            }
-        }
-        roomInfo = rooms;
-        setRooms(roomInfo);  
-    }).fail(function(err) {
-        alert(JSON.stringify($.parseJSON(err.responseText)));
-    });
-};
-
-$('.sendMessage').live('click', function() {
-    if (roomInfo.length === 0) {
-        alert("There is no room to send a message to!");
-        return;
-    }
-    
-    var index = Math.floor(Math.random() * roomInfo.length);
-    
-    sendMessage(roomInfo[index].room_id);
-});
-
-var sendMessage = function(roomId) {
-    var body = "jsfiddle message @" + $.now();
-    
-    if (roomId.length === 0) {
-        return;
-    }
-    
-    var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
-    url = url.replace("$token", accountInfo.access_token);
-    url = url.replace("$roomid", encodeURIComponent(roomId));
-    
-    var data = {
-        msgtype: "m.text",
-        body: body
-    };
-    
-    $.ajax({
-        url: url,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify(data),
-        dataType: "json",
-        success: function(data) {
-            $("#messageBody").val("");
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    }); 
-};
-
-var setRooms = function(roomList) {
-    // wipe existing entries
-    $("#rooms").find("tr:gt(0)").remove();
-    
-    var rows = "";
-    for (var i=0; i<roomList.length; ++i) {
-        row = "<tr>" +
-            "<td>"+roomList[i].room_id+"</td>" +
-            "<td>"+roomList[i].latest_message+"</td>" +
-            "</tr>";  
-        rows += row;
-    }
-    
-    $("#rooms").append(rows);
-};
-
diff --git a/jsfiddles/example_app/demo.css b/jsfiddles/example_app/demo.css
deleted file mode 100644
index 4c1e157cc8..0000000000
--- a/jsfiddles/example_app/demo.css
+++ /dev/null
@@ -1,43 +0,0 @@
-.roomListDashboard, .roomContents, .sendMessageForm {
-    visibility: hidden;
-}
-
-.roomList {
-    background-color: #909090;
-}
-
-.messageWrapper {
-    background-color: #EEEEEE;
-    height: 400px;
-    overflow: scroll;
-}
-
-.membersWrapper {
-    background-color: #EEEEEE;
-    height: 200px;
-    width: 50%;
-    overflow: scroll;
-}
-
-.textEntry {
-    width: 100%
-}
-
-p {
-    font-family: monospace;
-}
-
-table
-{
-    border-spacing:5px;
-}
-
-th,td
-{
-    padding:5px;
-}
-
-.roomList tr:not(:first-child):hover {
-    background-color: orange;
-    cursor: pointer;
-}
diff --git a/jsfiddles/example_app/demo.details b/jsfiddles/example_app/demo.details
deleted file mode 100644
index 3f96d3e744..0000000000
--- a/jsfiddles/example_app/demo.details
+++ /dev/null
@@ -1,7 +0,0 @@
- name: Example Matrix Client
- description: Includes login, live event streaming, creating rooms, sending messages and viewing member lists.
- authors:
-   - matrix.org
- resources:
-   - http://matrix.org
- normalize_css: no
\ No newline at end of file
diff --git a/jsfiddles/example_app/demo.html b/jsfiddles/example_app/demo.html
deleted file mode 100644
index 7a9dffddd0..0000000000
--- a/jsfiddles/example_app/demo.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<div class="signUp">
-    <p>Matrix example application: Requires a local home server running at http://localhost:8008</p>
-    <form class="registrationForm">
-        <p>No account? Register:</p>
-        <input type="text" id="userReg" placeholder="Username"></input>
-        <input type="password" id="passwordReg" placeholder="Password"></input>
-        <input type="button" class="register" value="Register"></input>
-    </form>
-    <form class="loginForm">
-        <p>Got an account? Login:</p>
-        <input type="text" id="userLogin" placeholder="Username"></input>
-        <input type="password" id="passwordLogin" placeholder="Password"></input>
-        <input type="button" class="login" value="Login"></input>
-    </form>
-</div>
-
-<div class="roomListDashboard">
-    <form class="createRoomForm">
-        <input type="text" id="roomAlias" placeholder="Room alias"></input>
-        <input type="button" class="createRoom" value="Create Room"></input>
-    </form>
-    <table id="rooms" class="roomList">
-        <tbody>
-            <tr>
-                <th>Room</th>
-                <th>My state</th>
-                <th>Latest message</th>
-            </tr>
-        </tbody>
-    </table>
-</div>
-
-<div class="roomContents">
-    <p id="roomName">Select a room</p>
-    <div class="messageWrapper">
-        <table id="messages">
-            <tbody>
-            </tbody>
-        </table>
-    </div>
-    <form class="sendMessageForm">
-        <input type="text" class="textEntry" id="body" placeholder="Enter text here..." onkeydown="javascript:if (event.keyCode == 13) document.getElementById('sendMsg').focus()"></input>
-        <input type="button" class="sendMessage" id="sendMsg" value="Send"></input>
-    </form>
-</div>
-
-<div>
-    <p>Member list:</p>
-    <div class="membersWrapper">
-        <table id="members">
-            <tbody>
-            </tbody>
-        </table>
-    </div>
-</div>
-    
diff --git a/jsfiddles/example_app/demo.js b/jsfiddles/example_app/demo.js
deleted file mode 100644
index 13c9c2b339..0000000000
--- a/jsfiddles/example_app/demo.js
+++ /dev/null
@@ -1,327 +0,0 @@
-var accountInfo = {};
-
-var eventStreamInfo = {
-    from: "END"
-};
-
-var roomInfo = [];
-var memberInfo = [];
-var viewingRoomId;
-
-// ************** Event Streaming **************
-var longpollEventStream = function() {
-    var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
-    url = url.replace("$token", accountInfo.access_token);
-    url = url.replace("$from", eventStreamInfo.from);
-
-    $.getJSON(url, function(data) {
-        eventStreamInfo.from = data.end;
-        
-        var hasNewLatestMessage = false;
-        var updatedMemberList = false;
-        var i=0;
-        var j=0;
-        for (i=0; i<data.chunk.length; ++i) {
-            if (data.chunk[i].type === "m.room.message") {
-                console.log("Got new message: " + JSON.stringify(data.chunk[i]));
-                if (viewingRoomId === data.chunk[i].room_id) {
-                    addMessage(data.chunk[i]);
-                }
-                
-                for (j=0; j<roomInfo.length; ++j) {
-                    if (roomInfo[j].room_id === data.chunk[i].room_id) {
-                        roomInfo[j].latest_message = data.chunk[i].content.body;
-                        hasNewLatestMessage = true;
-                    }
-                }
-            }
-            else if (data.chunk[i].type === "m.room.member") {
-                if (viewingRoomId === data.chunk[i].room_id) {
-                    console.log("Got new member: " + JSON.stringify(data.chunk[i]));
-                    addMessage(data.chunk[i]);
-                    for (j=0; j<memberInfo.length; ++j) {
-                        if (memberInfo[j].state_key === data.chunk[i].state_key) {
-                            memberInfo[j] = data.chunk[i];
-                            updatedMemberList = true;
-                            break;
-                        }
-                    }
-                    if (!updatedMemberList) {
-                        memberInfo.push(data.chunk[i]);  
-                        updatedMemberList = true;
-                    }
-                }
-                if (data.chunk[i].state_key === accountInfo.user_id) {
-                    getCurrentRoomList(); // update our join/invite list
-                }
-            }
-            else {
-                console.log("Discarding: " + JSON.stringify(data.chunk[i]));
-            }
-        }
-        
-        if (hasNewLatestMessage) {
-           setRooms(roomInfo);
-        }
-        if (updatedMemberList) {
-            $("#members").empty();
-            for (i=0; i<memberInfo.length; ++i) { 
-                addMember(memberInfo[i]);
-            }
-        }
-        longpollEventStream();
-    }).fail(function(err) {
-        setTimeout(longpollEventStream, 5000);
-    });
-};
-
-// ************** Registration and Login **************
-var onLoggedIn = function(data) {
-    accountInfo = data;
-    longpollEventStream();
-    getCurrentRoomList();
-    $(".roomListDashboard").css({visibility: "visible"});
-    $(".roomContents").css({visibility: "visible"});
-    $(".signUp").css({display: "none"});
-};
-
-$('.login').live('click', function() {
-    var user = $("#userLogin").val();
-    var password = $("#passwordLogin").val();
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/login",
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
-        dataType: "json",
-        success: function(data) {
-            onLoggedIn(data);
-        },
-        error: function(err) {
-            alert("Unable to login: is the home server running?");  
-        }
-    }); 
-});
-
-$('.register').live('click', function() {
-    var user = $("#userReg").val();
-    var password = $("#passwordReg").val();
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/register",
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
-        dataType: "json",
-        success: function(data) {
-            onLoggedIn(data);
-        },
-        error: function(err) {
-            var msg = "Is the home server running?";
-            var errJson = $.parseJSON(err.responseText);
-            if (errJson !== null) {
-                msg = errJson.error;   
-            }
-            alert("Unable to register: "+msg);  
-        }
-    });
-});
-
-// ************** Creating a room ******************
-$('.createRoom').live('click', function() {
-    var roomAlias = $("#roomAlias").val();
-    var data = {};
-    if (roomAlias.length > 0) {
-        data.room_alias_name = roomAlias;   
-    }
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify(data),
-        dataType: "json",
-        success: function(response) {
-            $("#roomAlias").val("");
-            response.membership = "join"; // you are automatically joined into every room you make.
-            response.latest_message = "";
-            
-            roomInfo.push(response);
-            setRooms(roomInfo);
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    }); 
-});
-
-// ************** Getting current state **************
-var getCurrentRoomList = function() {
-    var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
-    $.getJSON(url, function(data) {
-        var rooms = data.rooms;
-        for (var i=0; i<rooms.length; ++i) {
-            if ("messages" in rooms[i]) {
-                rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;   
-            }
-        }
-        roomInfo = rooms;
-        setRooms(roomInfo);  
-    }).fail(function(err) {
-        alert(JSON.stringify($.parseJSON(err.responseText)));
-    });
-};
-
-var loadRoomContent = function(roomId) {
-    console.log("loadRoomContent " + roomId);
-    viewingRoomId = roomId;
-    $("#roomName").text("Room: "+roomId);
-    $(".sendMessageForm").css({visibility: "visible"});
-    getMessages(roomId);
-    getMemberList(roomId);
-};
-
-var getMessages = function(roomId) {
-    $("#messages").empty();
-    var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" + 
-              encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
-    $.getJSON(url, function(data) {
-        for (var i=data.chunk.length-1; i>=0; --i) {
-            addMessage(data.chunk[i]);   
-        }
-    });
-};
-
-var getMemberList = function(roomId) {
-    $("#members").empty();
-    memberInfo = [];
-    var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" + 
-              encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
-    $.getJSON(url, function(data) {
-        for (var i=0; i<data.chunk.length; ++i) {
-            memberInfo.push(data.chunk[i]);
-            addMember(data.chunk[i]);   
-        }
-    });
-};
-
-// ************** Sending messages **************
-$('.sendMessage').live('click', function() {
-    if (viewingRoomId === undefined) {
-        alert("There is no room to send a message to!");
-        return;
-    }
-    var body = $("#body").val();
-    sendMessage(viewingRoomId, body);
-});
-
-var sendMessage = function(roomId, body) {
-    var msgId = $.now();
-    
-    var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
-    url = url.replace("$token", accountInfo.access_token);
-    url = url.replace("$roomid", encodeURIComponent(roomId));
-    
-    var data = {
-        msgtype: "m.text",
-        body: body
-    };
-    
-    $.ajax({
-        url: url,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify(data),
-        dataType: "json",
-        success: function(data) {
-            $("#body").val("");
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    });
-};
-
-// ************** Navigation and DOM manipulation **************
-var setRooms = function(roomList) {
-    // wipe existing entries
-    $("#rooms").find("tr:gt(0)").remove();
-    
-    var rows = "";
-    for (var i=0; i<roomList.length; ++i) {
-        row = "<tr>" +
-              "<td>"+roomList[i].room_id+"</td>" +
-              "<td>"+roomList[i].membership+"</td>" +
-              "<td>"+roomList[i].latest_message+"</td>" +
-              "</tr>";  
-        rows += row;
-    }
-    
-    $("#rooms").append(rows);
-    
-    $('#rooms').find("tr").click(function(){
-        var roomId = $(this).find('td:eq(0)').text();
-        var membership = $(this).find('td:eq(1)').text();
-        if (membership !== "join") {
-            console.log("Joining room " + roomId); 
-            var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
-            url = url.replace("$token", accountInfo.access_token);
-            url = url.replace("$roomid", encodeURIComponent(roomId));
-            $.ajax({
-                url: url,
-                type: "POST",
-                contentType: "application/json; charset=utf-8",
-                data: JSON.stringify({membership: "join"}),
-                dataType: "json",
-                success: function(data) {
-                    loadRoomContent(roomId);
-                    getCurrentRoomList();
-                },
-                error: function(err) {
-                    alert(JSON.stringify($.parseJSON(err.responseText)));  
-                }
-            });
-        }
-        else {
-            loadRoomContent(roomId);
-        }
-    });
-};
-
-var addMessage = function(data) {
-
-    var msg = data.content.body;
-    if (data.type === "m.room.member") {
-        if (data.content.membership === undefined) {
-            return;
-        }
-        if (data.content.membership === "invite") {
-            msg = "<em>invited " + data.state_key + " to the room</em>";
-        }
-        else if (data.content.membership === "join") {
-            msg = "<em>joined the room</em>";
-        }
-        else if (data.content.membership === "leave") {
-            msg = "<em>left the room</em>";
-        }
-        else if (data.content.membership === "ban") {
-            msg = "<em>was banned from the room</em>";
-        }
-    }
-    if (msg === undefined) {
-        return;
-    }
-
-    var row = "<tr>" +
-              "<td>"+data.user_id+"</td>" +
-              "<td>"+msg+"</td>" +
-              "</tr>"; 
-    $("#messages").append(row);
-};
-
-var addMember = function(data) {
-    var row = "<tr>" +
-              "<td>"+data.state_key+"</td>" +
-              "<td>"+data.content.membership+"</td>" +
-              "</tr>"; 
-    $("#members").append(row);
-};
-
diff --git a/jsfiddles/register_login/demo.css b/jsfiddles/register_login/demo.css
deleted file mode 100644
index 11781c250f..0000000000
--- a/jsfiddles/register_login/demo.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.loggedin {
-    visibility: hidden;
-}
-
-p {
-    font-family: monospace;
-}
diff --git a/jsfiddles/register_login/demo.html b/jsfiddles/register_login/demo.html
deleted file mode 100644
index fcac453ac2..0000000000
--- a/jsfiddles/register_login/demo.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<div>
-    <p>This registration/login demo requires a home server to be running on http://localhost:8008</p>
-</div>
-<form class="registrationForm">
-    <input type="text" id="user" placeholder="Username"></input>
-    <input type="password" id="password" placeholder="Password"></input>
-    <input type="button" class="register" value="Register"></input>
-</form>
-<form class="loginForm">
-    <input type="text" id="userLogin" placeholder="Username"></input>
-    <input type="password" id="passwordLogin" placeholder="Password"></input>
-    <input type="button" class="login" value="Login"></input>
-</form>
-<div class="loggedin">
-    <p id="welcomeText"></p>
-    <input type="button" class="testToken" value="Test token"></input>
-    <input type="button" class="logout" value="Logout"></input>
-    <p id="imSyncText"></p>
-</div>
-    
diff --git a/jsfiddles/register_login/demo.js b/jsfiddles/register_login/demo.js
deleted file mode 100644
index 2e6957b631..0000000000
--- a/jsfiddles/register_login/demo.js
+++ /dev/null
@@ -1,79 +0,0 @@
-var accountInfo = {};
-
-var showLoggedIn = function(data) {
-    accountInfo = data;
-    $(".loggedin").css({visibility: "visible"});
-    $("#welcomeText").text("Welcome " + accountInfo.user_id+". Your access token is:  " +
-                           accountInfo.access_token);    
-};
-
-$('.register').live('click', function() {
-    var user = $("#user").val();
-    var password = $("#password").val();
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/register",
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
-        dataType: "json",
-        success: function(data) {
-            showLoggedIn(data);
-        },
-        error: function(err) {
-            var errMsg = "To try this, you need a home server running!";
-            var errJson = $.parseJSON(err.responseText);
-            if (errJson) {
-                errMsg = JSON.stringify(errJson);   
-            }
-            alert(errMsg);   
-        }
-    });
-});
-
-var login = function(user, password) {
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/login",
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
-        dataType: "json",
-        success: function(data) {
-            showLoggedIn(data);
-        },
-        error: function(err) {
-            var errMsg = "To try this, you need a home server running!";
-            var errJson = $.parseJSON(err.responseText);
-            if (errJson) {
-                errMsg = JSON.stringify(errJson);   
-            }
-            alert(errMsg);  
-        }
-    });  
-};
-
-$('.login').live('click', function() {
-    var user = $("#userLogin").val();
-    var password = $("#passwordLogin").val();
-    $.getJSON("http://localhost:8008/_matrix/client/api/v1/login", function(data) {
-        if (data.flows[0].type !== "m.login.password") {
-            alert("I don't know how to login with this type: " + data.type);
-            return;
-        }
-        login(user, password);
-    });
-});
-
-$('.logout').live('click', function() {
-    accountInfo = {};
-    $("#imSyncText").text("");
-    $(".loggedin").css({visibility: "hidden"});
-});
-
-$('.testToken').live('click', function() {
-    var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
-    $.getJSON(url, function(data) {
-         $("#imSyncText").text(JSON.stringify(data, undefined, 2));
-    }).fail(function(err) {
-        $("#imSyncText").text(JSON.stringify($.parseJSON(err.responseText)));
-    });
-});
diff --git a/jsfiddles/room_memberships/demo.css b/jsfiddles/room_memberships/demo.css
deleted file mode 100644
index 48a55f372d..0000000000
--- a/jsfiddles/room_memberships/demo.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.loggedin {
-    visibility: hidden;
-}
-
-p {
-    font-family: monospace;
-}
-
-table
-{
-    border-spacing:5px;
-}
-
-th,td
-{
-    padding:5px;
-}
diff --git a/jsfiddles/room_memberships/demo.html b/jsfiddles/room_memberships/demo.html
deleted file mode 100644
index e6f39df5aa..0000000000
--- a/jsfiddles/room_memberships/demo.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<div>
-    <p>This room membership demo requires a home server to be running on http://localhost:8008</p>
-</div>
-<form class="loginForm">
-    <input type="text" id="userLogin" placeholder="Username"></input>
-    <input type="password" id="passwordLogin" placeholder="Password"></input>
-    <input type="button" class="login" value="Login"></input>
-</form>
-<div class="loggedin">
-    <form class="createRoomForm">
-        <input type="button" class="createRoom" value="Create Room"></input>
-    </form>
-    <form class="changeMembershipForm">
-        <input type="text" id="roomId" placeholder="Room ID"></input>
-        <input type="text" id="targetUser" placeholder="Target User ID"></input>
-        <select id="membership">
-            <option value="invite">invite</option>
-            <option value="join">join</option>
-            <option value="leave">leave</option>
-        </select>
-        <input type="button" class="changeMembership" value="Change Membership"></input>
-    </form>
-    <form class="joinAliasForm">
-        <input type="text" id="roomAlias" placeholder="Room Alias (#name:domain)"></input>
-        <input type="button" class="joinAlias" value="Join via Alias"></input>
-    </form>
-    <table id="rooms">
-        <tbody>
-            <tr>
-                <th>Room ID</th>
-                <th>My state</th>
-                <th>Room Alias</th>
-            </tr>
-        </tbody>
-    </table>
-</div>
-    
diff --git a/jsfiddles/room_memberships/demo.js b/jsfiddles/room_memberships/demo.js
deleted file mode 100644
index 8a7b1aa88e..0000000000
--- a/jsfiddles/room_memberships/demo.js
+++ /dev/null
@@ -1,141 +0,0 @@
-var accountInfo = {};
-
-var showLoggedIn = function(data) {
-    accountInfo = data;
-    getCurrentRoomList();
-    $(".loggedin").css({visibility: "visible"});
-    $("#membership").change(function() {
-    if ($("#membership").val() === "invite") {
-        $("#targetUser").css({visibility: "visible"});
-    }
-    else {
-        $("#targetUser").css({visibility: "hidden"});
-    }
-});
-};
-
-$('.login').live('click', function() {
-    var user = $("#userLogin").val();
-    var password = $("#passwordLogin").val();
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/login",
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
-        dataType: "json",
-        success: function(data) {
-            $("#rooms").find("tr:gt(0)").remove();
-            showLoggedIn(data);
-        },
-        error: function(err) {
-            var errMsg = "To try this, you need a home server running!";
-            var errJson = $.parseJSON(err.responseText);
-            if (errJson) {
-                errMsg = JSON.stringify(errJson);   
-            }
-            alert(errMsg); 
-        }
-    }); 
-});
-
-var getCurrentRoomList = function() {
-    $("#roomId").val("");
-    // wipe the table and reload it. Using the event stream would be the best
-    // solution but that is out of scope of this fiddle.
-    $("#rooms").find("tr:gt(0)").remove();
-    
-    var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
-    $.getJSON(url, function(data) {
-        var rooms = data.rooms;
-        for (var i=0; i<rooms.length; ++i) {
-            addRoom(rooms[i]);   
-        }
-    }).fail(function(err) {
-        alert(JSON.stringify($.parseJSON(err.responseText)));
-    });
-};
-
-$('.createRoom').live('click', function() {
-    var data = {};
-    $.ajax({
-        url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify(data),
-        dataType: "json",
-        success: function(data) {
-            data.membership = "join"; // you are automatically joined into every room you make.
-            data.latest_message = "";
-            addRoom(data);
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    }); 
-});
-
-var addRoom = function(data) {
-    row = "<tr>" +
-        "<td>"+data.room_id+"</td>" +
-        "<td>"+data.membership+"</td>" +
-        "<td>"+data.room_alias+"</td>" +
-        "</tr>";
-    $("#rooms").append(row);
-};
-
-$('.changeMembership').live('click', function() {
-    var roomId = $("#roomId").val();
-    var member = $("#targetUser").val();
-    var membership = $("#membership").val();
-    
-    if (roomId.length === 0) {
-        return;
-    }
-    
-    var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
-    url = url.replace("$token", accountInfo.access_token);
-    url = url.replace("$roomid", encodeURIComponent(roomId));
-    url = url.replace("$membership", membership);
-    
-    var data = {};
-    
-    if (membership === "invite") {
-        data = {
-            user_id: member
-        };
-    }
-
-    $.ajax({
-        url: url,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify(data),
-        dataType: "json",
-        success: function(data) {
-            getCurrentRoomList();
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    }); 
-});
-
-$('.joinAlias').live('click', function() {
-    var roomAlias = $("#roomAlias").val();
-    var url = "http://localhost:8008/_matrix/client/api/v1/join/$roomalias?access_token=$token";
-    url = url.replace("$token", accountInfo.access_token);
-    url = url.replace("$roomalias", encodeURIComponent(roomAlias));
-    $.ajax({
-        url: url,
-        type: "POST",
-        contentType: "application/json; charset=utf-8",
-        data: JSON.stringify({}),
-        dataType: "json",
-        success: function(data) {
-            getCurrentRoomList();
-        },
-        error: function(err) {
-            alert(JSON.stringify($.parseJSON(err.responseText)));  
-        }
-    }); 
-});
diff --git a/database-prepare-for-0.0.1.sh b/scripts/database-prepare-for-0.0.1.sh
index 43d759a5cd..43d759a5cd 100755
--- a/database-prepare-for-0.0.1.sh
+++ b/scripts/database-prepare-for-0.0.1.sh
diff --git a/database-prepare-for-0.5.0.sh b/scripts/database-prepare-for-0.5.0.sh
index e824cb583e..e824cb583e 100755
--- a/database-prepare-for-0.5.0.sh
+++ b/scripts/database-prepare-for-0.5.0.sh
diff --git a/database-save.sh b/scripts/database-save.sh
index 040c8a4943..040c8a4943 100755
--- a/database-save.sh
+++ b/scripts/database-save.sh
diff --git a/nuke-room-from-db.sh b/scripts/nuke-room-from-db.sh
index 58c036c896..58c036c896 100755
--- a/nuke-room-from-db.sh
+++ b/scripts/nuke-room-from-db.sh
diff --git a/sphinx_api_docs.sh b/scripts/sphinx_api_docs.sh
index ee72b29657..ee72b29657 100644
--- a/sphinx_api_docs.sh
+++ b/scripts/sphinx_api_docs.sh
diff --git a/setup.cfg b/setup.cfg
index 2830831f00..888ad6ed4a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,3 +8,11 @@ test = trial
 
 [trial]
 test_suite = tests
+
+[check-manifest]
+ignore =
+    contrib
+    contrib/*
+    docs/*
+    pylint.cfg
+    tox.ini
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index e31482cfaa..a342a0e0da 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -21,6 +21,7 @@ from synapse.api.constants import EventTypes, Membership, JoinRules
 from synapse.api.errors import AuthError, StoreError, Codes, SynapseError
 from synapse.util.logutils import log_function
 from synapse.util.async import run_on_reactor
+from synapse.types import UserID
 
 import logging
 
@@ -104,7 +105,7 @@ class Auth(object):
         for event in curr_state:
             if event.type == EventTypes.Member:
                 try:
-                    if self.hs.parse_userid(event.state_key).domain != host:
+                    if UserID.from_string(event.state_key).domain != host:
                         continue
                 except:
                     logger.warn("state_key not user_id: %s", event.state_key)
@@ -337,7 +338,7 @@ class Auth(object):
             user_info = {
                 "admin": bool(ret.get("admin", False)),
                 "device_id": ret.get("device_id"),
-                "user": self.hs.parse_userid(ret.get("name")),
+                "user": UserID.from_string(ret.get("name")),
             }
 
             defer.returnValue(user_info)
@@ -461,7 +462,7 @@ class Auth(object):
                             "You are not allowed to set others state"
                         )
                     else:
-                        sender_domain = self.hs.parse_userid(
+                        sender_domain = UserID.from_string(
                             event.user_id
                         ).domain
 
@@ -496,7 +497,7 @@ class Auth(object):
         # Validate users
         for k, v in user_list.items():
             try:
-                self.hs.parse_userid(k)
+                UserID.from_string(k)
             except:
                 raise SynapseError(400, "Not a valid user_id: %s" % (k,))
 
diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py
index afe3d19760..fabe8ddacb 100755
--- a/synapse/app/homeserver.py
+++ b/synapse/app/homeserver.py
@@ -26,8 +26,8 @@ from twisted.web.resource import Resource
 from twisted.web.static import File
 from twisted.web.server import Site
 from synapse.http.server import JsonResource, RootRedirect
-from synapse.media.v0.content_repository import ContentRepoResource
-from synapse.media.v1.media_repository import MediaRepositoryResource
+from synapse.rest.media.v0.content_repository import ContentRepoResource
+from synapse.rest.media.v1.media_repository import MediaRepositoryResource
 from synapse.http.server_key_resource import LocalKey
 from synapse.http.matrixfederationclient import MatrixFederationHttpClient
 from synapse.api.urls import (
@@ -37,6 +37,7 @@ from synapse.api.urls import (
 from synapse.config.homeserver import HomeServerConfig
 from synapse.crypto import context_factory
 from synapse.util.logcontext import LoggingContext
+from synapse.rest.client.v1 import ClientV1RestResource
 
 from daemonize import Daemonize
 import twisted.manhole.telnet
@@ -59,7 +60,7 @@ class SynapseHomeServer(HomeServer):
         return MatrixFederationHttpClient(self)
 
     def build_resource_for_client(self):
-        return JsonResource()
+        return ClientV1RestResource(self)
 
     def build_resource_for_federation(self):
         return JsonResource()
@@ -224,8 +225,6 @@ def setup():
         content_addr=config.content_addr,
     )
 
-    hs.register_servlets()
-
     hs.create_resource_tree(
         web_client=config.webclient,
         redirect_root_to_web_client=True,
diff --git a/synapse/handlers/_base.py b/synapse/handlers/_base.py
index f33d17a31e..1773fa20aa 100644
--- a/synapse/handlers/_base.py
+++ b/synapse/handlers/_base.py
@@ -19,6 +19,7 @@ from synapse.api.errors import LimitExceededError, SynapseError
 from synapse.util.async import run_on_reactor
 from synapse.crypto.event_signing import add_hashes_and_signatures
 from synapse.api.constants import Membership, EventTypes
+from synapse.types import UserID
 
 import logging
 
@@ -113,7 +114,7 @@ class BaseHandler(object):
 
         if event.type == EventTypes.Member:
             if event.content["membership"] == Membership.INVITE:
-                invitee = self.hs.parse_userid(event.state_key)
+                invitee = UserID.from_string(event.state_key)
                 if not self.hs.is_mine(invitee):
                     # TODO: Can we add signature from remote server in a nicer
                     # way? If we have been invited by a remote server, we need
@@ -134,7 +135,7 @@ class BaseHandler(object):
                 if k[0] == EventTypes.Member:
                     if s.content["membership"] == Membership.JOIN:
                         destinations.add(
-                            self.hs.parse_userid(s.state_key).domain
+                            UserID.from_string(s.state_key).domain
                         )
             except SynapseError:
                 logger.warn(
diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py
index 91fceda2ac..58e9a91562 100644
--- a/synapse/handlers/directory.py
+++ b/synapse/handlers/directory.py
@@ -19,6 +19,7 @@ from ._base import BaseHandler
 
 from synapse.api.errors import SynapseError, Codes, CodeMessageException
 from synapse.api.constants import EventTypes
+from synapse.types import RoomAlias
 
 import logging
 
@@ -122,7 +123,7 @@ class DirectoryHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def on_directory_query(self, args):
-        room_alias = self.hs.parse_roomalias(args["room_alias"])
+        room_alias = RoomAlias.from_string(args["room_alias"])
         if not self.hs.is_mine(room_alias):
             raise SynapseError(
                 400, "Room Alias is not hosted on this Home Server"
diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py
index 103bc67c42..01e67b0818 100644
--- a/synapse/handlers/events.py
+++ b/synapse/handlers/events.py
@@ -17,6 +17,7 @@ from twisted.internet import defer
 
 from synapse.util.logcontext import PreserveLoggingContext
 from synapse.util.logutils import log_function
+from synapse.types import UserID
 
 from ._base import BaseHandler
 
@@ -48,7 +49,7 @@ class EventStreamHandler(BaseHandler):
     @log_function
     def get_stream(self, auth_user_id, pagin_config, timeout=0,
                    as_client_event=True):
-        auth_user = self.hs.parse_userid(auth_user_id)
+        auth_user = UserID.from_string(auth_user_id)
 
         try:
             if auth_user not in self._streams_per_user:
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index 81203bf1a3..bcdcc90a18 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -28,6 +28,7 @@ from synapse.crypto.event_signing import (
     compute_event_signature, check_event_content_hash,
     add_hashes_and_signatures,
 )
+from synapse.types import UserID
 from syutil.jsonutil import encode_canonical_json
 
 from twisted.internet import defer
@@ -227,7 +228,7 @@ class FederationHandler(BaseHandler):
             extra_users = []
             if event.type == EventTypes.Member:
                 target_user_id = event.state_key
-                target_user = self.hs.parse_userid(target_user_id)
+                target_user = UserID.from_string(target_user_id)
                 extra_users.append(target_user)
 
             yield self.notifier.on_new_room_event(
@@ -236,7 +237,7 @@ class FederationHandler(BaseHandler):
 
         if event.type == EventTypes.Member:
             if event.membership == Membership.JOIN:
-                user = self.hs.parse_userid(event.state_key)
+                user = UserID.from_string(event.state_key)
                 yield self.distributor.fire(
                     "user_joined_room", user=user, room_id=event.room_id
                 )
@@ -491,7 +492,7 @@ class FederationHandler(BaseHandler):
         extra_users = []
         if event.type == EventTypes.Member:
             target_user_id = event.state_key
-            target_user = self.hs.parse_userid(target_user_id)
+            target_user = UserID.from_string(target_user_id)
             extra_users.append(target_user)
 
         yield self.notifier.on_new_room_event(
@@ -500,7 +501,7 @@ class FederationHandler(BaseHandler):
 
         if event.type == EventTypes.Member:
             if event.content["membership"] == Membership.JOIN:
-                user = self.hs.parse_userid(event.state_key)
+                user = UserID.from_string(event.state_key)
                 yield self.distributor.fire(
                     "user_joined_room", user=user, room_id=event.room_id
                 )
@@ -514,7 +515,7 @@ class FederationHandler(BaseHandler):
                 if k[0] == EventTypes.Member:
                     if s.content["membership"] == Membership.JOIN:
                         destinations.add(
-                            self.hs.parse_userid(s.state_key).domain
+                            UserID.from_string(s.state_key).domain
                         )
             except:
                 logger.warn(
@@ -565,7 +566,7 @@ class FederationHandler(BaseHandler):
             backfilled=False,
         )
 
-        target_user = self.hs.parse_userid(event.state_key)
+        target_user = UserID.from_string(event.state_key)
         yield self.notifier.on_new_room_event(
             event, extra_users=[target_user],
         )
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index f2a2f16933..6a1104a890 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -20,6 +20,7 @@ from synapse.api.errors import RoomError
 from synapse.streams.config import PaginationConfig
 from synapse.events.validator import EventValidator
 from synapse.util.logcontext import PreserveLoggingContext
+from synapse.types import UserID
 
 from ._base import BaseHandler
 
@@ -89,7 +90,7 @@ class MessageHandler(BaseHandler):
                 yield self.hs.get_event_sources().get_current_token()
             )
 
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         events, next_key = yield data_source.get_pagination_rows(
             user, pagin_config.get_source_config("room"), room_id
@@ -130,13 +131,13 @@ class MessageHandler(BaseHandler):
         if ratelimit:
             self.ratelimit(builder.user_id)
         # TODO(paul): Why does 'event' not have a 'user' object?
-        user = self.hs.parse_userid(builder.user_id)
+        user = UserID.from_string(builder.user_id)
         assert self.hs.is_mine(user), "User must be our own: %s" % (user,)
 
         if builder.type == EventTypes.Member:
             membership = builder.content.get("membership", None)
             if membership == Membership.JOIN:
-                joinee = self.hs.parse_userid(builder.state_key)
+                joinee = UserID.from_string(builder.state_key)
                 # If event doesn't include a display name, add one.
                 yield self.distributor.fire(
                     "collect_presencelike_data",
@@ -237,7 +238,7 @@ class MessageHandler(BaseHandler):
             membership_list=[Membership.INVITE, Membership.JOIN]
         )
 
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         rooms_ret = []
 
@@ -316,7 +317,7 @@ class MessageHandler(BaseHandler):
 
         # TODO(paul): I wish I was called with user objects not user_id
         #   strings...
-        auth_user = self.hs.parse_userid(user_id)
+        auth_user = UserID.from_string(user_id)
 
         # TODO: These concurrently
         state_tuples = yield self.state_handler.get_current_state(room_id)
@@ -349,7 +350,7 @@ class MessageHandler(BaseHandler):
         for m in room_members:
             try:
                 member_presence = yield presence_handler.get_state(
-                    target_user=self.hs.parse_userid(m.user_id),
+                    target_user=UserID.from_string(m.user_id),
                     auth_user=auth_user,
                     as_event=True,
                 )
diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index 8aeed99274..d66bfea7b1 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -20,6 +20,7 @@ from synapse.api.constants import PresenceState
 
 from synapse.util.logutils import log_function
 from synapse.util.logcontext import PreserveLoggingContext
+from synapse.types import UserID
 
 from ._base import BaseHandler
 
@@ -96,22 +97,22 @@ class PresenceHandler(BaseHandler):
         self.federation.register_edu_handler(
             "m.presence_invite",
             lambda origin, content: self.invite_presence(
-                observed_user=hs.parse_userid(content["observed_user"]),
-                observer_user=hs.parse_userid(content["observer_user"]),
+                observed_user=UserID.from_string(content["observed_user"]),
+                observer_user=UserID.from_string(content["observer_user"]),
             )
         )
         self.federation.register_edu_handler(
             "m.presence_accept",
             lambda origin, content: self.accept_presence(
-                observed_user=hs.parse_userid(content["observed_user"]),
-                observer_user=hs.parse_userid(content["observer_user"]),
+                observed_user=UserID.from_string(content["observed_user"]),
+                observer_user=UserID.from_string(content["observer_user"]),
             )
         )
         self.federation.register_edu_handler(
             "m.presence_deny",
             lambda origin, content: self.deny_presence(
-                observed_user=hs.parse_userid(content["observed_user"]),
-                observer_user=hs.parse_userid(content["observer_user"]),
+                observed_user=UserID.from_string(content["observed_user"]),
+                observer_user=UserID.from_string(content["observer_user"]),
             )
         )
 
@@ -418,7 +419,7 @@ class PresenceHandler(BaseHandler):
         )
 
         for p in presence:
-            observed_user = self.hs.parse_userid(p.pop("observed_user_id"))
+            observed_user = UserID.from_string(p.pop("observed_user_id"))
             p["observed_user"] = observed_user
             p.update(self._get_or_offline_usercache(observed_user).get_state())
             if "last_active" in p:
@@ -441,7 +442,7 @@ class PresenceHandler(BaseHandler):
                 user.localpart, accepted=True
             )
             target_users = set([
-                self.hs.parse_userid(x["observed_user_id"]) for x in presence
+                UserID.from_string(x["observed_user_id"]) for x in presence
             ])
 
             # Also include people in all my rooms
@@ -646,7 +647,7 @@ class PresenceHandler(BaseHandler):
         deferreds = []
 
         for push in content.get("push", []):
-            user = self.hs.parse_userid(push["user_id"])
+            user = UserID.from_string(push["user_id"])
 
             logger.debug("Incoming presence update from %s", user)
 
@@ -694,7 +695,7 @@ class PresenceHandler(BaseHandler):
                 del self._user_cachemap[user]
 
         for poll in content.get("poll", []):
-            user = self.hs.parse_userid(poll)
+            user = UserID.from_string(poll)
 
             if not self.hs.is_mine(user):
                 continue
@@ -709,7 +710,7 @@ class PresenceHandler(BaseHandler):
             deferreds.append(self._push_presence_remote(user, origin))
 
         for unpoll in content.get("unpoll", []):
-            user = self.hs.parse_userid(unpoll)
+            user = UserID.from_string(unpoll)
 
             if not self.hs.is_mine(user):
                 continue
diff --git a/synapse/handlers/profile.py b/synapse/handlers/profile.py
index 7777d3cc94..03b2159c53 100644
--- a/synapse/handlers/profile.py
+++ b/synapse/handlers/profile.py
@@ -18,6 +18,7 @@ from twisted.internet import defer
 from synapse.api.errors import SynapseError, AuthError, CodeMessageException
 from synapse.api.constants import EventTypes, Membership
 from synapse.util.logcontext import PreserveLoggingContext
+from synapse.types import UserID
 
 from ._base import BaseHandler
 
@@ -169,7 +170,7 @@ class ProfileHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def on_profile_query(self, args):
-        user = self.hs.parse_userid(args["user_id"])
+        user = UserID.from_string(args["user_id"])
         if not self.hs.is_mine(user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index 6d0db18e51..edb96cec83 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -64,7 +64,7 @@ class RoomCreationHandler(BaseHandler):
         invite_list = config.get("invite", [])
         for i in invite_list:
             try:
-                self.hs.parse_userid(i)
+                UserID.from_string(i)
             except:
                 raise SynapseError(400, "Invalid user_id: %s" % (i,))
 
@@ -114,7 +114,7 @@ class RoomCreationHandler(BaseHandler):
                 servers=[self.hs.hostname],
             )
 
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
         creation_events = self._create_events_for_new_room(
             user, room_id, is_public=is_public
         )
@@ -246,11 +246,9 @@ class RoomMemberHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def get_room_members(self, room_id):
-        hs = self.hs
-
         users = yield self.store.get_users_in_room(room_id)
 
-        defer.returnValue([hs.parse_userid(u) for u in users])
+        defer.returnValue([UserID.from_string(u) for u in users])
 
     @defer.inlineCallbacks
     def fetch_room_distributions_into(self, room_id, localusers=None,
@@ -368,7 +366,7 @@ class RoomMemberHandler(BaseHandler):
             )
 
             if prev_state and prev_state.membership == Membership.JOIN:
-                user = self.hs.parse_userid(event.user_id)
+                user = UserID.from_string(event.user_id)
                 self.distributor.fire(
                     "user_left_room", user=user, room_id=event.room_id
                 )
@@ -412,7 +410,7 @@ class RoomMemberHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def _do_join(self, event, context, room_host=None, do_auth=True):
-        joinee = self.hs.parse_userid(event.state_key)
+        joinee = UserID.from_string(event.state_key)
         # room_id = RoomID.from_string(event.room_id, self.hs)
         room_id = event.room_id
 
@@ -476,7 +474,7 @@ class RoomMemberHandler(BaseHandler):
                 do_auth=do_auth,
             )
 
-        user = self.hs.parse_userid(event.user_id)
+        user = UserID.from_string(event.user_id)
         yield self.distributor.fire(
             "user_joined_room", user=user, room_id=room_id
         )
@@ -526,7 +524,7 @@ class RoomMemberHandler(BaseHandler):
                                     do_auth):
         yield run_on_reactor()
 
-        target_user = self.hs.parse_userid(event.state_key)
+        target_user = UserID.from_string(event.state_key)
 
         yield self.handle_new_client_event(
             event,
diff --git a/synapse/handlers/typing.py b/synapse/handlers/typing.py
index cd9638dd04..c69787005f 100644
--- a/synapse/handlers/typing.py
+++ b/synapse/handlers/typing.py
@@ -18,6 +18,7 @@ from twisted.internet import defer
 from ._base import BaseHandler
 
 from synapse.api.errors import SynapseError, AuthError
+from synapse.types import UserID
 
 import logging
 
@@ -185,7 +186,7 @@ class TypingNotificationHandler(BaseHandler):
     @defer.inlineCallbacks
     def _recv_edu(self, origin, content):
         room_id = content["room_id"]
-        user = self.homeserver.parse_userid(content["user_id"])
+        user = UserID.from_string(content["user_id"])
 
         localusers = set()
 
diff --git a/synapse/rest/base.py b/synapse/http/servlet.py
index c583945527..d5ccf2742f 100644
--- a/synapse/rest/base.py
+++ b/synapse/http/servlet.py
@@ -14,9 +14,6 @@
 # limitations under the License.
 
 """ This module contains base REST classes for constructing REST servlets. """
-from synapse.api.urls import CLIENT_PREFIX
-from synapse.rest.transactions import HttpTransactionStore
-import re
 
 import logging
 
@@ -24,19 +21,6 @@ import logging
 logger = logging.getLogger(__name__)
 
 
-def client_path_pattern(path_regex):
-    """Creates a regex compiled client path with the correct client path
-    prefix.
-
-    Args:
-        path_regex (str): The regex string to match. This should NOT have a ^
-        as this will be prefixed.
-    Returns:
-        SRE_Pattern
-    """
-    return re.compile("^" + CLIENT_PREFIX + path_regex)
-
-
 class RestServlet(object):
 
     """ A Synapse REST Servlet.
@@ -59,14 +43,6 @@ class RestServlet(object):
     into the appropriate HTTP response.
     """
 
-    def __init__(self, hs):
-        self.hs = hs
-
-        self.handlers = hs.get_handlers()
-        self.builder_factory = hs.get_event_builder_factory()
-        self.auth = hs.get_auth()
-        self.txns = HttpTransactionStore()
-
     def register(self, http_server):
         """ Register this servlet with the given HTTP server. """
         if hasattr(self, "PATTERN"):
diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py
index 88ec9cd27d..1a84d94cd9 100644
--- a/synapse/rest/__init__.py
+++ b/synapse/rest/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014, 2015 OpenMarket Ltd
+# Copyright 2015 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,36 +12,3 @@
 # 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.
-
-
-from . import (
-    room, events, register, login, profile, presence, initial_sync, directory,
-    voip, admin,
-)
-
-
-class RestServletFactory(object):
-
-    """ A factory for creating REST servlets.
-
-    These REST servlets represent the entire client-server REST API. Generally
-    speaking, they serve as wrappers around events and the handlers that
-    process them.
-
-    See synapse.events for information on synapse events.
-    """
-
-    def __init__(self, hs):
-        client_resource = hs.get_resource_for_client()
-
-        # TODO(erikj): There *must* be a better way of doing this.
-        room.register_servlets(hs, client_resource)
-        events.register_servlets(hs, client_resource)
-        register.register_servlets(hs, client_resource)
-        login.register_servlets(hs, client_resource)
-        profile.register_servlets(hs, client_resource)
-        presence.register_servlets(hs, client_resource)
-        initial_sync.register_servlets(hs, client_resource)
-        directory.register_servlets(hs, client_resource)
-        voip.register_servlets(hs, client_resource)
-        admin.register_servlets(hs, client_resource)
diff --git a/synapse/rest/client/__init__.py b/synapse/rest/client/__init__.py
new file mode 100644
index 0000000000..1a84d94cd9
--- /dev/null
+++ b/synapse/rest/client/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Copyright 2015 OpenMarket Ltd
+#
+# Licensed 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.
diff --git a/synapse/rest/client/v1/__init__.py b/synapse/rest/client/v1/__init__.py
new file mode 100644
index 0000000000..8bb89b2f6a
--- /dev/null
+++ b/synapse/rest/client/v1/__init__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014, 2015 OpenMarket Ltd
+#
+# Licensed 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.
+
+
+from . import (
+    room, events, register, login, profile, presence, initial_sync, directory,
+    voip, admin,
+)
+
+from synapse.http.server import JsonResource
+
+
+class ClientV1RestResource(JsonResource):
+    """A resource for version 1 of the matrix client API."""
+
+    def __init__(self, hs):
+        JsonResource.__init__(self)
+        self.register_servlets(self, hs)
+
+    @staticmethod
+    def register_servlets(client_resource, hs):
+        room.register_servlets(hs, client_resource)
+        events.register_servlets(hs, client_resource)
+        register.register_servlets(hs, client_resource)
+        login.register_servlets(hs, client_resource)
+        profile.register_servlets(hs, client_resource)
+        presence.register_servlets(hs, client_resource)
+        initial_sync.register_servlets(hs, client_resource)
+        directory.register_servlets(hs, client_resource)
+        voip.register_servlets(hs, client_resource)
+        admin.register_servlets(hs, client_resource)
diff --git a/synapse/rest/admin.py b/synapse/rest/client/v1/admin.py
index 0aa83514c8..1051d96f96 100644
--- a/synapse/rest/admin.py
+++ b/synapse/rest/client/v1/admin.py
@@ -16,19 +16,21 @@
 from twisted.internet import defer
 
 from synapse.api.errors import AuthError, SynapseError
-from base import RestServlet, client_path_pattern
+from synapse.types import UserID
+
+from base import ClientV1RestServlet, client_path_pattern
 
 import logging
 
 logger = logging.getLogger(__name__)
 
 
-class WhoisRestServlet(RestServlet):
+class WhoisRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/admin/whois/(?P<user_id>[^/]*)")
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
-        target_user = self.hs.parse_userid(user_id)
+        target_user = UserID.from_string(user_id)
         auth_user = yield self.auth.get_user_by_req(request)
         is_admin = yield self.auth.is_server_admin(auth_user)
 
diff --git a/synapse/rest/client/v1/base.py b/synapse/rest/client/v1/base.py
new file mode 100644
index 0000000000..72332bdb10
--- /dev/null
+++ b/synapse/rest/client/v1/base.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014, 2015 OpenMarket Ltd
+#
+# Licensed 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.
+
+"""This module contains base REST classes for constructing client v1 servlets.
+"""
+
+from synapse.http.servlet import RestServlet
+from synapse.api.urls import CLIENT_PREFIX
+from .transactions import HttpTransactionStore
+import re
+
+import logging
+
+
+logger = logging.getLogger(__name__)
+
+
+def client_path_pattern(path_regex):
+    """Creates a regex compiled client path with the correct client path
+    prefix.
+
+    Args:
+        path_regex (str): The regex string to match. This should NOT have a ^
+        as this will be prefixed.
+    Returns:
+        SRE_Pattern
+    """
+    return re.compile("^" + CLIENT_PREFIX + path_regex)
+
+
+class ClientV1RestServlet(RestServlet):
+    """A base Synapse REST Servlet for the client version 1 API.
+    """
+
+    def __init__(self, hs):
+        self.hs = hs
+        self.handlers = hs.get_handlers()
+        self.builder_factory = hs.get_event_builder_factory()
+        self.auth = hs.get_auth()
+        self.txns = HttpTransactionStore()
diff --git a/synapse/rest/directory.py b/synapse/rest/client/v1/directory.py
index 7ff44fdd9e..15ae8749b8 100644
--- a/synapse/rest/directory.py
+++ b/synapse/rest/client/v1/directory.py
@@ -17,7 +17,8 @@
 from twisted.internet import defer
 
 from synapse.api.errors import AuthError, SynapseError, Codes
-from base import RestServlet, client_path_pattern
+from synapse.types import RoomAlias
+from .base import ClientV1RestServlet, client_path_pattern
 
 import json
 import logging
@@ -30,12 +31,12 @@ def register_servlets(hs, http_server):
     ClientDirectoryServer(hs).register(http_server)
 
 
-class ClientDirectoryServer(RestServlet):
+class ClientDirectoryServer(ClientV1RestServlet):
     PATTERN = client_path_pattern("/directory/room/(?P<room_alias>[^/]*)$")
 
     @defer.inlineCallbacks
     def on_GET(self, request, room_alias):
-        room_alias = self.hs.parse_roomalias(room_alias)
+        room_alias = RoomAlias.from_string(room_alias)
 
         dir_handler = self.handlers.directory_handler
         res = yield dir_handler.get_association(room_alias)
@@ -53,7 +54,7 @@ class ClientDirectoryServer(RestServlet):
 
         logger.debug("Got content: %s", content)
 
-        room_alias = self.hs.parse_roomalias(room_alias)
+        room_alias = RoomAlias.from_string(room_alias)
 
         logger.debug("Got room name: %s", room_alias.to_string())
 
@@ -92,7 +93,7 @@ class ClientDirectoryServer(RestServlet):
 
         dir_handler = self.handlers.directory_handler
 
-        room_alias = self.hs.parse_roomalias(room_alias)
+        room_alias = RoomAlias.from_string(room_alias)
 
         yield dir_handler.delete_association(
             user.to_string(), room_alias
diff --git a/synapse/rest/events.py b/synapse/rest/client/v1/events.py
index bedcb2bcc6..c69de56863 100644
--- a/synapse/rest/events.py
+++ b/synapse/rest/client/v1/events.py
@@ -18,7 +18,7 @@ from twisted.internet import defer
 
 from synapse.api.errors import SynapseError
 from synapse.streams.config import PaginationConfig
-from synapse.rest.base import RestServlet, client_path_pattern
+from .base import ClientV1RestServlet, client_path_pattern
 
 import logging
 
@@ -26,7 +26,7 @@ import logging
 logger = logging.getLogger(__name__)
 
 
-class EventStreamRestServlet(RestServlet):
+class EventStreamRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/events$")
 
     DEFAULT_LONGPOLL_TIME_MS = 30000
@@ -61,7 +61,7 @@ class EventStreamRestServlet(RestServlet):
 
 
 # TODO: Unit test gets, with and without auth, with different kinds of events.
-class EventRestServlet(RestServlet):
+class EventRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/events/(?P<event_id>[^/]*)$")
 
     @defer.inlineCallbacks
diff --git a/synapse/rest/initial_sync.py b/synapse/rest/client/v1/initial_sync.py
index b13d56b286..357fa845b4 100644
--- a/synapse/rest/initial_sync.py
+++ b/synapse/rest/client/v1/initial_sync.py
@@ -16,11 +16,11 @@
 from twisted.internet import defer
 
 from synapse.streams.config import PaginationConfig
-from base import RestServlet, client_path_pattern
+from base import ClientV1RestServlet, client_path_pattern
 
 
 # TODO: Needs unit testing
-class InitialSyncRestServlet(RestServlet):
+class InitialSyncRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/initialSync$")
 
     @defer.inlineCallbacks
diff --git a/synapse/rest/login.py b/synapse/rest/client/v1/login.py
index 6b8deff67b..7116ac98e8 100644
--- a/synapse/rest/login.py
+++ b/synapse/rest/client/v1/login.py
@@ -17,12 +17,12 @@ from twisted.internet import defer
 
 from synapse.api.errors import SynapseError
 from synapse.types import UserID
-from base import RestServlet, client_path_pattern
+from base import ClientV1RestServlet, client_path_pattern
 
 import json
 
 
-class LoginRestServlet(RestServlet):
+class LoginRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/login$")
     PASS_TYPE = "m.login.password"
 
@@ -64,7 +64,7 @@ class LoginRestServlet(RestServlet):
         defer.returnValue((200, result))
 
 
-class LoginFallbackRestServlet(RestServlet):
+class LoginFallbackRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/login/fallback$")
 
     def on_GET(self, request):
@@ -73,7 +73,7 @@ class LoginFallbackRestServlet(RestServlet):
         return (200, {})
 
 
-class PasswordResetRestServlet(RestServlet):
+class PasswordResetRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/login/reset")
 
     @defer.inlineCallbacks
diff --git a/synapse/rest/presence.py b/synapse/rest/client/v1/presence.py
index ca4d2d21f0..b6c207e662 100644
--- a/synapse/rest/presence.py
+++ b/synapse/rest/client/v1/presence.py
@@ -18,7 +18,8 @@
 from twisted.internet import defer
 
 from synapse.api.errors import SynapseError
-from base import RestServlet, client_path_pattern
+from synapse.types import UserID
+from .base import ClientV1RestServlet, client_path_pattern
 
 import json
 import logging
@@ -26,13 +27,13 @@ import logging
 logger = logging.getLogger(__name__)
 
 
-class PresenceStatusRestServlet(RestServlet):
+class PresenceStatusRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/presence/(?P<user_id>[^/]*)/status")
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         state = yield self.handlers.presence_handler.get_state(
             target_user=user, auth_user=auth_user)
@@ -42,7 +43,7 @@ class PresenceStatusRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_PUT(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         state = {}
         try:
@@ -71,13 +72,13 @@ class PresenceStatusRestServlet(RestServlet):
         return (200, {})
 
 
-class PresenceListRestServlet(RestServlet):
+class PresenceListRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/presence/list/(?P<user_id>[^/]*)")
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         if not self.hs.is_mine(user):
             raise SynapseError(400, "User not hosted on this Home Server")
@@ -97,7 +98,7 @@ class PresenceListRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_POST(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         if not self.hs.is_mine(user):
             raise SynapseError(400, "User not hosted on this Home Server")
@@ -118,7 +119,7 @@ class PresenceListRestServlet(RestServlet):
                     raise SynapseError(400, "Bad invite value.")
                 if len(u) == 0:
                     continue
-                invited_user = self.hs.parse_userid(u)
+                invited_user = UserID.from_string(u)
                 yield self.handlers.presence_handler.send_invite(
                     observer_user=user, observed_user=invited_user
                 )
@@ -129,7 +130,7 @@ class PresenceListRestServlet(RestServlet):
                     raise SynapseError(400, "Bad drop value.")
                 if len(u) == 0:
                     continue
-                dropped_user = self.hs.parse_userid(u)
+                dropped_user = UserID.from_string(u)
                 yield self.handlers.presence_handler.drop(
                     observer_user=user, observed_user=dropped_user
                 )
diff --git a/synapse/rest/profile.py b/synapse/rest/client/v1/profile.py
index dc6eb424b0..24f8d56952 100644
--- a/synapse/rest/profile.py
+++ b/synapse/rest/client/v1/profile.py
@@ -16,17 +16,18 @@
 """ This module contains REST servlets to do with profile: /profile/<paths> """
 from twisted.internet import defer
 
-from base import RestServlet, client_path_pattern
+from .base import ClientV1RestServlet, client_path_pattern
+from synapse.types import UserID
 
 import json
 
 
-class ProfileDisplaynameRestServlet(RestServlet):
+class ProfileDisplaynameRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)/displayname")
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         displayname = yield self.handlers.profile_handler.get_displayname(
             user,
@@ -37,7 +38,7 @@ class ProfileDisplaynameRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_PUT(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         try:
             content = json.loads(request.content.read())
@@ -54,12 +55,12 @@ class ProfileDisplaynameRestServlet(RestServlet):
         return (200, {})
 
 
-class ProfileAvatarURLRestServlet(RestServlet):
+class ProfileAvatarURLRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)/avatar_url")
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         avatar_url = yield self.handlers.profile_handler.get_avatar_url(
             user,
@@ -70,7 +71,7 @@ class ProfileAvatarURLRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_PUT(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         try:
             content = json.loads(request.content.read())
@@ -87,12 +88,12 @@ class ProfileAvatarURLRestServlet(RestServlet):
         return (200, {})
 
 
-class ProfileRestServlet(RestServlet):
+class ProfileRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)")
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         displayname = yield self.handlers.profile_handler.get_displayname(
             user,
diff --git a/synapse/rest/register.py b/synapse/rest/client/v1/register.py
index e3b26902d9..c0423c2d45 100644
--- a/synapse/rest/register.py
+++ b/synapse/rest/client/v1/register.py
@@ -18,7 +18,7 @@ from twisted.internet import defer
 
 from synapse.api.errors import SynapseError, Codes
 from synapse.api.constants import LoginType
-from base import RestServlet, client_path_pattern
+from base import ClientV1RestServlet, client_path_pattern
 import synapse.util.stringutils as stringutils
 
 from synapse.util.async import run_on_reactor
@@ -42,7 +42,7 @@ else:
     compare_digest = lambda a, b: a == b
 
 
-class RegisterRestServlet(RestServlet):
+class RegisterRestServlet(ClientV1RestServlet):
     """Handles registration with the home server.
 
     This servlet is in control of the registration flow; the registration
diff --git a/synapse/rest/room.py b/synapse/rest/client/v1/room.py
index 48bba2a5f3..f06e3ddb98 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/client/v1/room.py
@@ -16,10 +16,11 @@
 """ This module contains REST servlets to do with rooms: /rooms/<paths> """
 from twisted.internet import defer
 
-from base import RestServlet, client_path_pattern
+from base import ClientV1RestServlet, client_path_pattern
 from synapse.api.errors import SynapseError, Codes
 from synapse.streams.config import PaginationConfig
 from synapse.api.constants import EventTypes, Membership
+from synapse.types import UserID, RoomID, RoomAlias
 
 import json
 import logging
@@ -29,7 +30,7 @@ import urllib
 logger = logging.getLogger(__name__)
 
 
-class RoomCreateRestServlet(RestServlet):
+class RoomCreateRestServlet(ClientV1RestServlet):
     # No PATTERN; we have custom dispatch rules here
 
     def register(self, http_server):
@@ -93,7 +94,7 @@ class RoomCreateRestServlet(RestServlet):
 
 
 # TODO: Needs unit testing for generic events
-class RoomStateEventRestServlet(RestServlet):
+class RoomStateEventRestServlet(ClientV1RestServlet):
     def register(self, http_server):
         # /room/$roomid/state/$eventtype
         no_state_key = "/rooms/(?P<room_id>[^/]*)/state/(?P<event_type>[^/]*)$"
@@ -162,7 +163,7 @@ class RoomStateEventRestServlet(RestServlet):
 
 
 # TODO: Needs unit testing for generic events + feedback
-class RoomSendEventRestServlet(RestServlet):
+class RoomSendEventRestServlet(ClientV1RestServlet):
 
     def register(self, http_server):
         # /rooms/$roomid/send/$event_type[/$txn_id]
@@ -205,7 +206,7 @@ class RoomSendEventRestServlet(RestServlet):
 
 
 # TODO: Needs unit testing for room ID + alias joins
-class JoinRoomAliasServlet(RestServlet):
+class JoinRoomAliasServlet(ClientV1RestServlet):
 
     def register(self, http_server):
         # /join/$room_identifier[/$txn_id]
@@ -223,10 +224,10 @@ class JoinRoomAliasServlet(RestServlet):
         identifier = None
         is_room_alias = False
         try:
-            identifier = self.hs.parse_roomalias(room_identifier)
+            identifier = RoomAlias.from_string(room_identifier)
             is_room_alias = True
         except SynapseError:
-            identifier = self.hs.parse_roomid(room_identifier)
+            identifier = RoomID.from_string(room_identifier)
 
         # TODO: Support for specifying the home server to join with?
 
@@ -264,7 +265,7 @@ class JoinRoomAliasServlet(RestServlet):
 
 
 # TODO: Needs unit testing
-class PublicRoomListRestServlet(RestServlet):
+class PublicRoomListRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/publicRooms$")
 
     @defer.inlineCallbacks
@@ -275,7 +276,7 @@ class PublicRoomListRestServlet(RestServlet):
 
 
 # TODO: Needs unit testing
-class RoomMemberListRestServlet(RestServlet):
+class RoomMemberListRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/members$")
 
     @defer.inlineCallbacks
@@ -289,7 +290,7 @@ class RoomMemberListRestServlet(RestServlet):
 
         for event in members["chunk"]:
             # FIXME: should probably be state_key here, not user_id
-            target_user = self.hs.parse_userid(event["user_id"])
+            target_user = UserID.from_string(event["user_id"])
             # Presence is an optional cache; don't fail if we can't fetch it
             try:
                 presence_handler = self.handlers.presence_handler
@@ -304,7 +305,7 @@ class RoomMemberListRestServlet(RestServlet):
 
 
 # TODO: Needs unit testing
-class RoomMessageListRestServlet(RestServlet):
+class RoomMessageListRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/messages$")
 
     @defer.inlineCallbacks
@@ -328,7 +329,7 @@ class RoomMessageListRestServlet(RestServlet):
 
 
 # TODO: Needs unit testing
-class RoomStateRestServlet(RestServlet):
+class RoomStateRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/state$")
 
     @defer.inlineCallbacks
@@ -344,7 +345,7 @@ class RoomStateRestServlet(RestServlet):
 
 
 # TODO: Needs unit testing
-class RoomInitialSyncRestServlet(RestServlet):
+class RoomInitialSyncRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/initialSync$")
 
     @defer.inlineCallbacks
@@ -359,7 +360,7 @@ class RoomInitialSyncRestServlet(RestServlet):
         defer.returnValue((200, content))
 
 
-class RoomTriggerBackfill(RestServlet):
+class RoomTriggerBackfill(ClientV1RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/backfill$")
 
     @defer.inlineCallbacks
@@ -378,7 +379,7 @@ class RoomTriggerBackfill(RestServlet):
 
 
 # TODO: Needs unit testing
-class RoomMembershipRestServlet(RestServlet):
+class RoomMembershipRestServlet(ClientV1RestServlet):
 
     def register(self, http_server):
         # /rooms/$roomid/[invite|join|leave]
@@ -430,7 +431,7 @@ class RoomMembershipRestServlet(RestServlet):
         defer.returnValue(response)
 
 
-class RoomRedactEventRestServlet(RestServlet):
+class RoomRedactEventRestServlet(ClientV1RestServlet):
     def register(self, http_server):
         PATTERN = ("/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)")
         register_txn_path(self, PATTERN, http_server)
@@ -468,7 +469,7 @@ class RoomRedactEventRestServlet(RestServlet):
         defer.returnValue(response)
 
 
-class RoomTypingRestServlet(RestServlet):
+class RoomTypingRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern(
         "/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$"
     )
@@ -478,7 +479,7 @@ class RoomTypingRestServlet(RestServlet):
         auth_user = yield self.auth.get_user_by_req(request)
 
         room_id = urllib.unquote(room_id)
-        target_user = self.hs.parse_userid(urllib.unquote(user_id))
+        target_user = UserID.from_string(urllib.unquote(user_id))
 
         content = _parse_json(request)
 
diff --git a/synapse/rest/transactions.py b/synapse/rest/client/v1/transactions.py
index d933fea18a..d933fea18a 100644
--- a/synapse/rest/transactions.py
+++ b/synapse/rest/client/v1/transactions.py
diff --git a/synapse/rest/voip.py b/synapse/rest/client/v1/voip.py
index 011c35e69b..822d863ce6 100644
--- a/synapse/rest/voip.py
+++ b/synapse/rest/client/v1/voip.py
@@ -15,7 +15,7 @@
 
 from twisted.internet import defer
 
-from base import RestServlet, client_path_pattern
+from base import ClientV1RestServlet, client_path_pattern
 
 
 import hmac
@@ -23,7 +23,7 @@ import hashlib
 import base64
 
 
-class VoipRestServlet(RestServlet):
+class VoipRestServlet(ClientV1RestServlet):
     PATTERN = client_path_pattern("/voip/turnServer$")
 
     @defer.inlineCallbacks
diff --git a/synapse/media/__init__.py b/synapse/rest/media/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/synapse/media/__init__.py
+++ b/synapse/rest/media/__init__.py
diff --git a/synapse/media/v0/__init__.py b/synapse/rest/media/v0/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/synapse/media/v0/__init__.py
+++ b/synapse/rest/media/v0/__init__.py
diff --git a/synapse/media/v0/content_repository.py b/synapse/rest/media/v0/content_repository.py
index 79ae0e3d74..79ae0e3d74 100644
--- a/synapse/media/v0/content_repository.py
+++ b/synapse/rest/media/v0/content_repository.py
diff --git a/synapse/media/v1/__init__.py b/synapse/rest/media/v1/__init__.py
index d6c6690577..d6c6690577 100644
--- a/synapse/media/v1/__init__.py
+++ b/synapse/rest/media/v1/__init__.py
diff --git a/synapse/media/v1/base_resource.py b/synapse/rest/media/v1/base_resource.py
index 688e7376ad..688e7376ad 100644
--- a/synapse/media/v1/base_resource.py
+++ b/synapse/rest/media/v1/base_resource.py
diff --git a/synapse/media/v1/download_resource.py b/synapse/rest/media/v1/download_resource.py
index c585bb11f7..c585bb11f7 100644
--- a/synapse/media/v1/download_resource.py
+++ b/synapse/rest/media/v1/download_resource.py
diff --git a/synapse/media/v1/filepath.py b/synapse/rest/media/v1/filepath.py
index ed9a58e9d9..ed9a58e9d9 100644
--- a/synapse/media/v1/filepath.py
+++ b/synapse/rest/media/v1/filepath.py
diff --git a/synapse/media/v1/media_repository.py b/synapse/rest/media/v1/media_repository.py
index 461cc001f1..461cc001f1 100644
--- a/synapse/media/v1/media_repository.py
+++ b/synapse/rest/media/v1/media_repository.py
diff --git a/synapse/media/v1/thumbnail_resource.py b/synapse/rest/media/v1/thumbnail_resource.py
index 84f5e3463c..84f5e3463c 100644
--- a/synapse/media/v1/thumbnail_resource.py
+++ b/synapse/rest/media/v1/thumbnail_resource.py
diff --git a/synapse/media/v1/thumbnailer.py b/synapse/rest/media/v1/thumbnailer.py
index 28404f2b7b..28404f2b7b 100644
--- a/synapse/media/v1/thumbnailer.py
+++ b/synapse/rest/media/v1/thumbnailer.py
diff --git a/synapse/media/v1/upload_resource.py b/synapse/rest/media/v1/upload_resource.py
index b1718a630b..b1718a630b 100644
--- a/synapse/media/v1/upload_resource.py
+++ b/synapse/rest/media/v1/upload_resource.py
diff --git a/synapse/server.py b/synapse/server.py
index d861efd2fd..32013b1a91 100644
--- a/synapse/server.py
+++ b/synapse/server.py
@@ -24,10 +24,8 @@ from synapse.events.utils import serialize_event
 from synapse.notifier import Notifier
 from synapse.api.auth import Auth
 from synapse.handlers import Handlers
-from synapse.rest import RestServletFactory
 from synapse.state import StateHandler
 from synapse.storage import DataStore
-from synapse.types import UserID, RoomAlias, RoomID, EventID
 from synapse.util import Clock
 from synapse.util.distributor import Distributor
 from synapse.util.lockutils import LockManager
@@ -125,30 +123,6 @@ class BaseHomeServer(object):
 
         setattr(BaseHomeServer, "get_%s" % (depname), _get)
 
-    # TODO: Why are these parse_ methods so high up along with other globals?
-    # Surely these should be in a util package or in the api package?
-
-    # Other utility methods
-    def parse_userid(self, s):
-        """Parse the string given by 's' as a User ID and return a UserID
-        object."""
-        return UserID.from_string(s)
-
-    def parse_roomalias(self, s):
-        """Parse the string given by 's' as a Room Alias and return a RoomAlias
-        object."""
-        return RoomAlias.from_string(s)
-
-    def parse_roomid(self, s):
-        """Parse the string given by 's' as a Room ID and return a RoomID
-        object."""
-        return RoomID.from_string(s)
-
-    def parse_eventid(self, s):
-        """Parse the string given by 's' as a Event ID and return a EventID
-        object."""
-        return EventID.from_string(s)
-
     def serialize_event(self, e, as_client_event=True):
         return serialize_event(self, e, as_client_event)
 
@@ -203,9 +177,6 @@ class HomeServer(BaseHomeServer):
     def build_auth(self):
         return Auth(self)
 
-    def build_rest_servlet_factory(self):
-        return RestServletFactory(self)
-
     def build_state_handler(self):
         return StateHandler(self)
 
@@ -229,9 +200,3 @@ class HomeServer(BaseHomeServer):
             clock=self.get_clock(),
             hostname=self.hostname,
         )
-
-    def register_servlets(self):
-        """ Register all servlets associated with this HomeServer.
-        """
-        # Simply building the ServletFactory is sufficient to have it register
-        self.get_rest_servlet_factory()
diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py
index e59e65529b..c69dd995ce 100644
--- a/synapse/storage/roommember.py
+++ b/synapse/storage/roommember.py
@@ -20,6 +20,7 @@ from collections import namedtuple
 from ._base import SQLBaseStore
 
 from synapse.api.constants import Membership
+from synapse.types import UserID
 
 import logging
 
@@ -39,7 +40,7 @@ class RoomMemberStore(SQLBaseStore):
         """
         try:
             target_user_id = event.state_key
-            domain = self.hs.parse_userid(target_user_id).domain
+            domain = UserID.from_string(target_user_id).domain
         except:
             logger.exception(
                 "Failed to parse target_user_id=%s", target_user_id
@@ -84,7 +85,7 @@ class RoomMemberStore(SQLBaseStore):
             for e in member_events:
                 try:
                     joined_domains.add(
-                        self.hs.parse_userid(e.state_key).domain
+                        UserID.from_string(e.state_key).domain
                     )
                 except:
                     # FIXME: How do we deal with invalid user ids in the db?
diff --git a/tests/handlers/test_directory.py b/tests/handlers/test_directory.py
index 8e164e4be0..22119de46a 100644
--- a/tests/handlers/test_directory.py
+++ b/tests/handlers/test_directory.py
@@ -21,6 +21,7 @@ from mock import Mock
 
 from synapse.server import HomeServer
 from synapse.handlers.directory import DirectoryHandler
+from synapse.types import RoomAlias
 
 from tests.utils import SQLiteMemoryDbPool, MockKey
 
@@ -65,9 +66,9 @@ class DirectoryTestCase(unittest.TestCase):
 
         self.store = hs.get_datastore()
 
-        self.my_room = hs.parse_roomalias("#my-room:test")
-        self.your_room = hs.parse_roomalias("#your-room:test")
-        self.remote_room = hs.parse_roomalias("#another:remote")
+        self.my_room = RoomAlias.from_string("#my-room:test")
+        self.your_room = RoomAlias.from_string("#your-room:test")
+        self.remote_room = RoomAlias.from_string("#another:remote")
 
     @defer.inlineCallbacks
     def test_get_local_association(self):
diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py
index b85a89052a..5621a8afaf 100644
--- a/tests/handlers/test_presence.py
+++ b/tests/handlers/test_presence.py
@@ -17,7 +17,7 @@
 from tests import unittest
 from twisted.internet import defer, reactor
 
-from mock import Mock, call, ANY, NonCallableMock, patch
+from mock import Mock, call, ANY, NonCallableMock
 import json
 
 from tests.utils import (
@@ -31,6 +31,7 @@ from synapse.api.errors import SynapseError
 from synapse.handlers.presence import PresenceHandler, UserPresenceCache
 from synapse.streams.config import SourcePaginationConfig
 from synapse.storage.transactions import DestinationsTable
+from synapse.types import UserID
 
 OFFLINE = PresenceState.OFFLINE
 UNAVAILABLE = PresenceState.UNAVAILABLE
@@ -59,68 +60,216 @@ class JustPresenceHandlers(object):
     def __init__(self, hs):
         self.presence_handler = PresenceHandler(hs)
 
-class PresenceStateTestCase(unittest.TestCase):
-    """ Tests presence management. """
 
+class PresenceTestCase(unittest.TestCase):
     @defer.inlineCallbacks
     def setUp(self):
-        db_pool = SQLiteMemoryDbPool()
-        yield db_pool.prepare()
+        self.clock = MockClock()
 
         self.mock_config = NonCallableMock()
         self.mock_config.signing_key = [MockKey()]
 
+        self.mock_federation_resource = MockHttpResource()
+
+        self.mock_http_client = Mock(spec=[])
+        self.mock_http_client.put_json = DeferredMockCallable()
+
+        db_pool = None
+        hs_kwargs = {}
+
+        if hasattr(self, "make_datastore_mock"):
+            hs_kwargs["datastore"] = self.make_datastore_mock()
+        else:
+            db_pool = SQLiteMemoryDbPool()
+            yield db_pool.prepare()
+
         hs = HomeServer("test",
-            clock=MockClock(),
+            clock=self.clock,
             db_pool=db_pool,
             handlers=None,
-            resource_for_federation=Mock(),
-            http_client=None,
+            resource_for_federation=self.mock_federation_resource,
+            http_client=self.mock_http_client,
             config=self.mock_config,
             keyring=Mock(),
+            **hs_kwargs
         )
         hs.handlers = JustPresenceHandlers(hs)
 
-        self.store = hs.get_datastore()
+        self.datastore = hs.get_datastore()
 
-        # Mock the RoomMemberHandler
-        room_member_handler = Mock(spec=[])
-        hs.handlers.room_member_handler = room_member_handler
+        self.setUp_roommemberhandler_mocks(hs.handlers)
 
-        # Some local users to test with
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
-        self.u_clementine = hs.parse_userid("@clementine:test")
+        self.handler = hs.get_handlers().presence_handler
+        self.event_source = hs.get_event_sources().sources["presence"]
 
-        yield self.store.create_presence(self.u_apple.localpart)
-        yield self.store.set_presence_state(
-            self.u_apple.localpart, {"state": ONLINE, "status_msg": "Online"}
-        )
+        self.distributor = hs.get_distributor()
+        self.distributor.declare("user_joined_room")
 
-        self.handler = hs.get_handlers().presence_handler
+        yield self.setUp_users(hs)
 
+    def setUp_roommemberhandler_mocks(self, handlers):
+        self.room_id = "a-room"
         self.room_members = []
 
+        room_member_handler = handlers.room_member_handler = Mock(spec=[
+            "get_rooms_for_user",
+            "get_room_members",
+            "fetch_room_distributions_into",
+        ])
+        self.room_member_handler = room_member_handler
+
         def get_rooms_for_user(user):
             if user in self.room_members:
-                return defer.succeed(["a-room"])
+                return defer.succeed([self.room_id])
             else:
                 return defer.succeed([])
         room_member_handler.get_rooms_for_user = get_rooms_for_user
 
         def get_room_members(room_id):
-            if room_id == "a-room":
+            if room_id == self.room_id:
                 return defer.succeed(self.room_members)
             else:
                 return defer.succeed([])
         room_member_handler.get_room_members = get_room_members
 
+        @defer.inlineCallbacks
+        def fetch_room_distributions_into(room_id, localusers=None,
+                remotedomains=None, ignore_user=None):
+
+            members = yield get_room_members(room_id)
+            for member in members:
+                if ignore_user is not None and member == ignore_user:
+                    continue
+
+                if member.is_mine:
+                    if localusers is not None:
+                        localusers.add(member)
+                else:
+                    if remotedomains is not None:
+                        remotedomains.add(member.domain)
+        room_member_handler.fetch_room_distributions_into = (
+                fetch_room_distributions_into)
+
+        self.setUp_datastore_room_mocks(self.datastore)
+
+    def setUp_datastore_room_mocks(self, datastore):
+        def get_room_hosts(room_id):
+            if room_id == self.room_id:
+                hosts = set([u.domain for u in self.room_members])
+                return defer.succeed(hosts)
+            else:
+                return defer.succeed([])
+        datastore.get_joined_hosts_for_room = get_room_hosts
+
         def user_rooms_intersect(userlist):
             room_member_ids = map(lambda u: u.to_string(), self.room_members)
 
             shared = all(map(lambda i: i in room_member_ids, userlist))
             return defer.succeed(shared)
-        self.store.user_rooms_intersect = user_rooms_intersect
+        datastore.user_rooms_intersect = user_rooms_intersect
+
+    @defer.inlineCallbacks
+    def setUp_users(self, hs):
+        # Some local users to test with
+        self.u_apple = UserID.from_string("@apple:test")
+        self.u_banana = UserID.from_string("@banana:test")
+        self.u_clementine = UserID.from_string("@clementine:test")
+
+        for u in self.u_apple, self.u_banana, self.u_clementine:
+            yield self.datastore.create_presence(u.localpart)
+
+        yield self.datastore.set_presence_state(
+            self.u_apple.localpart, {"state": ONLINE, "status_msg": "Online"}
+        )
+
+        # ID of a local user that does not exist
+        self.u_durian = UserID.from_string("@durian:test")
+
+        # A remote user
+        self.u_cabbage = UserID.from_string("@cabbage:elsewhere")
+
+
+class MockedDatastorePresenceTestCase(PresenceTestCase):
+    def make_datastore_mock(self):
+        datastore = Mock(spec=[
+            # Bits that Federation needs
+            "prep_send_transaction",
+            "delivered_txn",
+            "get_received_txn_response",
+            "set_received_txn_response",
+            "get_destination_retry_timings",
+        ])
+
+        self.setUp_datastore_federation_mocks(datastore)
+        self.setUp_datastore_presence_mocks(datastore)
+
+        return datastore
+
+    def setUp_datastore_federation_mocks(self, datastore):
+        datastore.get_destination_retry_timings.return_value = (
+            defer.succeed(DestinationsTable.EntryType("", 0, 0))
+        )
+
+        def get_received_txn_response(*args):
+            return defer.succeed(None)
+        datastore.get_received_txn_response = get_received_txn_response
+
+    def setUp_datastore_presence_mocks(self, datastore):
+        self.current_user_state = {
+            "apple": OFFLINE,
+            "banana": OFFLINE,
+            "clementine": OFFLINE,
+            "fig": OFFLINE,
+        }
+
+        def get_presence_state(user_localpart):
+            return defer.succeed(
+                    {"state": self.current_user_state[user_localpart],
+                     "status_msg": None,
+                     "mtime": 123456000}
+            )
+        datastore.get_presence_state = get_presence_state
+
+        def set_presence_state(user_localpart, new_state):
+            was = self.current_user_state[user_localpart]
+            self.current_user_state[user_localpart] = new_state["state"]
+            return defer.succeed({"state": was})
+        datastore.set_presence_state = set_presence_state
+
+        def get_presence_list(user_localpart, accepted):
+            if not user_localpart in self.PRESENCE_LIST:
+                return defer.succeed([])
+            return defer.succeed([
+                {"observed_user_id": u} for u in
+                self.PRESENCE_LIST[user_localpart]])
+        datastore.get_presence_list = get_presence_list
+
+        def is_presence_visible(observed_localpart, observer_userid):
+            return True
+        datastore.is_presence_visible = is_presence_visible
+
+    @defer.inlineCallbacks
+    def setUp_users(self, hs):
+        # Some local users to test with
+        self.u_apple = UserID.from_string("@apple:test")
+        self.u_banana = UserID.from_string("@banana:test")
+        self.u_clementine = UserID.from_string("@clementine:test")
+        self.u_durian = UserID.from_string("@durian:test")
+        self.u_elderberry = UserID.from_string("@elderberry:test")
+        self.u_fig = UserID.from_string("@fig:test")
+
+        # Remote user
+        self.u_onion = UserID.from_string("@onion:farm")
+        self.u_potato = UserID.from_string("@potato:remote")
+
+        yield
+
+
+class PresenceStateTestCase(PresenceTestCase):
+    """ Tests presence management. """
+    @defer.inlineCallbacks
+    def setUp(self):
+        yield super(PresenceStateTestCase, self).setUp()
 
         self.mock_start = Mock()
         self.mock_stop = Mock()
@@ -141,7 +290,7 @@ class PresenceStateTestCase(unittest.TestCase):
 
     @defer.inlineCallbacks
     def test_get_allowed_state(self):
-        yield self.store.allow_presence_visible(
+        yield self.datastore.allow_presence_visible(
             observed_localpart=self.u_apple.localpart,
             observer_userid=self.u_banana.to_string(),
         )
@@ -189,7 +338,7 @@ class PresenceStateTestCase(unittest.TestCase):
             {"state": UNAVAILABLE,
              "status_msg": "Away",
              "mtime": 1000000},
-            (yield self.store.get_presence_state(self.u_apple.localpart))
+            (yield self.datastore.get_presence_state(self.u_apple.localpart))
         )
 
         self.mock_start.assert_called_with(self.u_apple,
@@ -206,49 +355,11 @@ class PresenceStateTestCase(unittest.TestCase):
         self.mock_stop.assert_called_with(self.u_apple)
 
 
-class PresenceInvitesTestCase(unittest.TestCase):
+class PresenceInvitesTestCase(PresenceTestCase):
     """ Tests presence management. """
-
     @defer.inlineCallbacks
     def setUp(self):
-        self.mock_http_client = Mock(spec=[])
-        self.mock_http_client.put_json = DeferredMockCallable()
-
-        self.mock_federation_resource = MockHttpResource()
-
-        db_pool = SQLiteMemoryDbPool()
-        yield db_pool.prepare()
-
-        self.mock_config = NonCallableMock()
-        self.mock_config.signing_key = [MockKey()]
-
-        hs = HomeServer("test",
-            clock=MockClock(),
-            db_pool=db_pool,
-            handlers=None,
-            resource_for_client=Mock(),
-            resource_for_federation=self.mock_federation_resource,
-            http_client=self.mock_http_client,
-            config=self.mock_config,
-            keyring=Mock(),
-        )
-        hs.handlers = JustPresenceHandlers(hs)
-
-        self.store = hs.get_datastore()
-
-        # Some local users to test with
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
-        yield self.store.create_presence(self.u_apple.localpart)
-        yield self.store.create_presence(self.u_banana.localpart)
-
-        # ID of a local user that does not exist
-        self.u_durian = hs.parse_userid("@durian:test")
-
-        # A remote user
-        self.u_cabbage = hs.parse_userid("@cabbage:elsewhere")
-
-        self.handler = hs.get_handlers().presence_handler
+        yield super(PresenceInvitesTestCase, self).setUp()
 
         self.mock_start = Mock()
         self.mock_stop = Mock()
@@ -266,10 +377,10 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
         self.assertEquals(
             [{"observed_user_id": "@banana:test", "accepted": 1}],
-            (yield self.store.get_presence_list(self.u_apple.localpart))
+            (yield self.datastore.get_presence_list(self.u_apple.localpart))
         )
         self.assertTrue(
-            (yield self.store.is_presence_visible(
+            (yield self.datastore.is_presence_visible(
                 observed_localpart=self.u_banana.localpart,
                 observer_userid=self.u_apple.to_string(),
             ))
@@ -285,7 +396,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
         self.assertEquals(
             [],
-            (yield self.store.get_presence_list(self.u_apple.localpart))
+            (yield self.datastore.get_presence_list(self.u_apple.localpart))
         )
 
     @defer.inlineCallbacks
@@ -310,7 +421,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
         self.assertEquals(
             [{"observed_user_id": "@cabbage:elsewhere", "accepted": 0}],
-            (yield self.store.get_presence_list(self.u_apple.localpart))
+            (yield self.datastore.get_presence_list(self.u_apple.localpart))
         )
 
         yield put_json.await_calls()
@@ -345,7 +456,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
         )
 
         self.assertTrue(
-            (yield self.store.is_presence_visible(
+            (yield self.datastore.is_presence_visible(
                 observed_localpart=self.u_apple.localpart,
                 observer_userid=self.u_cabbage.to_string(),
             ))
@@ -384,7 +495,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
     @defer.inlineCallbacks
     def test_accepted_remote(self):
-        yield self.store.add_presence_list_pending(
+        yield self.datastore.add_presence_list_pending(
             observer_localpart=self.u_apple.localpart,
             observed_userid=self.u_cabbage.to_string(),
         )
@@ -401,7 +512,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
         self.assertEquals(
             [{"observed_user_id": "@cabbage:elsewhere", "accepted": 1}],
-            (yield self.store.get_presence_list(self.u_apple.localpart))
+            (yield self.datastore.get_presence_list(self.u_apple.localpart))
         )
 
         self.mock_start.assert_called_with(
@@ -409,7 +520,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
     @defer.inlineCallbacks
     def test_denied_remote(self):
-        yield self.store.add_presence_list_pending(
+        yield self.datastore.add_presence_list_pending(
             observer_localpart=self.u_apple.localpart,
             observed_userid="@eggplant:elsewhere",
         )
@@ -426,16 +537,16 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
         self.assertEquals(
             [],
-            (yield self.store.get_presence_list(self.u_apple.localpart))
+            (yield self.datastore.get_presence_list(self.u_apple.localpart))
         )
 
     @defer.inlineCallbacks
     def test_drop_local(self):
-        yield self.store.add_presence_list_pending(
+        yield self.datastore.add_presence_list_pending(
             observer_localpart=self.u_apple.localpart,
             observed_userid=self.u_banana.to_string(),
         )
-        yield self.store.set_presence_list_accepted(
+        yield self.datastore.set_presence_list_accepted(
             observer_localpart=self.u_apple.localpart,
             observed_userid=self.u_banana.to_string(),
         )
@@ -447,7 +558,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
         self.assertEquals(
             [],
-            (yield self.store.get_presence_list(self.u_apple.localpart))
+            (yield self.datastore.get_presence_list(self.u_apple.localpart))
         )
 
         self.mock_stop.assert_called_with(
@@ -455,11 +566,11 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
     @defer.inlineCallbacks
     def test_drop_remote(self):
-        yield self.store.add_presence_list_pending(
+        yield self.datastore.add_presence_list_pending(
             observer_localpart=self.u_apple.localpart,
             observed_userid=self.u_cabbage.to_string(),
         )
-        yield self.store.set_presence_list_accepted(
+        yield self.datastore.set_presence_list_accepted(
             observer_localpart=self.u_apple.localpart,
             observed_userid=self.u_cabbage.to_string(),
         )
@@ -471,16 +582,16 @@ class PresenceInvitesTestCase(unittest.TestCase):
 
         self.assertEquals(
             [],
-            (yield self.store.get_presence_list(self.u_apple.localpart))
+            (yield self.datastore.get_presence_list(self.u_apple.localpart))
         )
 
     @defer.inlineCallbacks
     def test_get_presence_list(self):
-        yield self.store.add_presence_list_pending(
+        yield self.datastore.add_presence_list_pending(
             observer_localpart=self.u_apple.localpart,
             observed_userid=self.u_banana.to_string(),
         )
-        yield self.store.set_presence_list_accepted(
+        yield self.datastore.set_presence_list_accepted(
             observer_localpart=self.u_apple.localpart,
             observed_userid=self.u_banana.to_string(),
         )
@@ -495,7 +606,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
         ], presence)
 
 
-class PresencePushTestCase(unittest.TestCase):
+class PresencePushTestCase(MockedDatastorePresenceTestCase):
     """ Tests steady-state presence status updates.
 
     They assert that presence state update messages are pushed around the place
@@ -505,138 +616,9 @@ class PresencePushTestCase(unittest.TestCase):
     presence handler; namely the _local_pushmap and _remote_recvmap.
     BE WARNED...
     """
-    def setUp(self):
-        self.clock = MockClock()
-
-        self.mock_http_client = Mock(spec=[])
-        self.mock_http_client.put_json = DeferredMockCallable()
-
-        self.mock_federation_resource = MockHttpResource()
-
-        self.mock_config = NonCallableMock()
-        self.mock_config.signing_key = [MockKey()]
-
-        hs = HomeServer("test",
-                clock=self.clock,
-                db_pool=None,
-                datastore=Mock(spec=[
-                    "set_presence_state",
-                    "get_joined_hosts_for_room",
-
-                    # Bits that Federation needs
-                    "prep_send_transaction",
-                    "delivered_txn",
-                    "get_received_txn_response",
-                    "set_received_txn_response",
-                    "get_destination_retry_timings",
-                ]),
-                handlers=None,
-                resource_for_client=Mock(),
-                resource_for_federation=self.mock_federation_resource,
-                http_client=self.mock_http_client,
-                config=self.mock_config,
-                keyring=Mock(),
-            )
-        hs.handlers = JustPresenceHandlers(hs)
-
-        self.datastore = hs.get_datastore()
-        self.datastore.get_destination_retry_timings.return_value = (
-            defer.succeed(DestinationsTable.EntryType("", 0, 0))
-        )
-
-        def get_received_txn_response(*args):
-            return defer.succeed(None)
-        self.datastore.get_received_txn_response = get_received_txn_response
-
-        self.handler = hs.get_handlers().presence_handler
-        self.event_source = hs.get_event_sources().sources["presence"]
-
-        # Mock the RoomMemberHandler
-        hs.handlers.room_member_handler = Mock(spec=[
-            "get_rooms_for_user",
-            "get_room_members",
-        ])
-        self.room_member_handler = hs.handlers.room_member_handler
-
-        self.room_members = []
-
-        def get_rooms_for_user(user):
-            if user in self.room_members:
-                return defer.succeed(["a-room"])
-            else:
-                return defer.succeed([])
-        self.room_member_handler.get_rooms_for_user = get_rooms_for_user
-
-        def get_room_members(room_id):
-            if room_id == "a-room":
-                return defer.succeed(self.room_members)
-            else:
-                return defer.succeed([])
-        self.room_member_handler.get_room_members = get_room_members
-
-        def get_room_hosts(room_id):
-            if room_id == "a-room":
-                hosts = set([u.domain for u in self.room_members])
-                return defer.succeed(hosts)
-            else:
-                return defer.succeed([])
-        self.datastore.get_joined_hosts_for_room = get_room_hosts
-
-        def user_rooms_intersect(userlist):
-            room_member_ids = map(lambda u: u.to_string(), self.room_members)
-
-            shared = all(map(lambda i: i in room_member_ids, userlist))
-            return defer.succeed(shared)
-        self.datastore.user_rooms_intersect = user_rooms_intersect
-
-        @defer.inlineCallbacks
-        def fetch_room_distributions_into(room_id, localusers=None,
-                remotedomains=None, ignore_user=None):
-
-            members = yield get_room_members(room_id)
-            for member in members:
-                if ignore_user is not None and member == ignore_user:
-                    continue
-
-                if member.is_mine:
-                    if localusers is not None:
-                        localusers.add(member)
-                else:
-                    if remotedomains is not None:
-                        remotedomains.add(member.domain)
-        self.room_member_handler.fetch_room_distributions_into = (
-                fetch_room_distributions_into)
-
-        def get_presence_list(user_localpart, accepted=None):
-            if user_localpart == "apple":
-                return defer.succeed([
-                    {"observed_user_id": "@banana:test"},
-                    {"observed_user_id": "@clementine:test"},
-                ])
-            else:
-                return defer.succeed([])
-        self.datastore.get_presence_list = get_presence_list
-
-        def is_presence_visible(observer_userid, observed_localpart):
-            if (observed_localpart == "clementine" and
-                observer_userid == "@banana:test"):
-                return False
-            return False
-        self.datastore.is_presence_visible = is_presence_visible
-
-        self.distributor = hs.get_distributor()
-        self.distributor.declare("user_joined_room")
-
-        # Some local users to test with
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
-        self.u_clementine = hs.parse_userid("@clementine:test")
-        self.u_durian = hs.parse_userid("@durian:test")
-        self.u_elderberry = hs.parse_userid("@elderberry:test")
-
-        # Remote user
-        self.u_onion = hs.parse_userid("@onion:farm")
-        self.u_potato = hs.parse_userid("@potato:remote")
+    PRESENCE_LIST = {
+            'apple': [ "@banana:test", "@clementine:test" ],
+    }
 
     @defer.inlineCallbacks
     def test_push_local(self):
@@ -911,7 +893,7 @@ class PresencePushTestCase(unittest.TestCase):
         )
 
         yield self.distributor.fire("user_joined_room", self.u_clementine,
-            "a-room"
+            self.room_id
         )
 
         self.room_members.append(self.u_clementine)
@@ -974,7 +956,7 @@ class PresencePushTestCase(unittest.TestCase):
         self.room_members = [self.u_apple, self.u_banana]
 
         yield self.distributor.fire("user_joined_room", self.u_potato,
-            "a-room"
+            self.room_id
         )
 
         yield put_json.await_calls()
@@ -1003,13 +985,13 @@ class PresencePushTestCase(unittest.TestCase):
         self.room_members.append(self.u_potato)
 
         yield self.distributor.fire("user_joined_room", self.u_clementine,
-            "a-room"
+            self.room_id
         )
 
         put_json.await_calls()
 
 
-class PresencePollingTestCase(unittest.TestCase):
+class PresencePollingTestCase(MockedDatastorePresenceTestCase):
     """ Tests presence status polling. """
 
     # For this test, we have three local users; apple is watching and is
@@ -1022,106 +1004,18 @@ class PresencePollingTestCase(unittest.TestCase):
             'fig': [ "@potato:remote" ],
     }
 
-
+    @defer.inlineCallbacks
     def setUp(self):
-        self.mock_http_client = Mock(spec=[])
-        self.mock_http_client.put_json = DeferredMockCallable()
-
-        self.mock_federation_resource = MockHttpResource()
-
-        self.mock_config = NonCallableMock()
-        self.mock_config.signing_key = [MockKey()]
-
-        hs = HomeServer("test",
-                clock=MockClock(),
-                db_pool=None,
-                datastore=Mock(spec=[
-                    # Bits that Federation needs
-                    "prep_send_transaction",
-                    "delivered_txn",
-                    "get_received_txn_response",
-                    "set_received_txn_response",
-                    "get_destination_retry_timings",
-                ]),
-                handlers=None,
-                resource_for_client=Mock(),
-                resource_for_federation=self.mock_federation_resource,
-                http_client=self.mock_http_client,
-                config=self.mock_config,
-                keyring=Mock(),
-            )
-        hs.handlers = JustPresenceHandlers(hs)
-
-        self.datastore = hs.get_datastore()
-        self.datastore.get_destination_retry_timings.return_value = (
-            defer.succeed(DestinationsTable.EntryType("", 0, 0))
-        )
-
-        def get_received_txn_response(*args):
-            return defer.succeed(None)
-        self.datastore.get_received_txn_response = get_received_txn_response
+        yield super(PresencePollingTestCase, self).setUp()
 
         self.mock_update_client = Mock()
 
         def update(*args,**kwargs):
-            # print "mock_update_client: Args=%s, kwargs=%s" %(args, kwargs,)
             return defer.succeed(None)
-
         self.mock_update_client.side_effect = update
 
-        self.handler = hs.get_handlers().presence_handler
         self.handler.push_update_to_clients = self.mock_update_client
 
-        hs.handlers.room_member_handler = Mock(spec=[
-            "get_rooms_for_user",
-        ])
-        # For this test no users are ever in rooms
-        def get_rooms_for_user(user):
-            return defer.succeed([])
-        hs.handlers.room_member_handler.get_rooms_for_user = get_rooms_for_user
-
-        # Mocked database state
-        # Local users always start offline
-        self.current_user_state = {
-            "apple": OFFLINE,
-            "banana": OFFLINE,
-            "clementine": OFFLINE,
-            "fig": OFFLINE,
-        }
-
-        def get_presence_state(user_localpart):
-            return defer.succeed(
-                    {"state": self.current_user_state[user_localpart],
-                     "status_msg": None,
-                     "mtime": 123456000}
-            )
-        self.datastore.get_presence_state = get_presence_state
-
-        def set_presence_state(user_localpart, new_state):
-            was = self.current_user_state[user_localpart]
-            self.current_user_state[user_localpart] = new_state["state"]
-            return defer.succeed({"state": was})
-        self.datastore.set_presence_state = set_presence_state
-
-        def get_presence_list(user_localpart, accepted):
-            return defer.succeed([
-                {"observed_user_id": u} for u in
-                self.PRESENCE_LIST[user_localpart]])
-        self.datastore.get_presence_list = get_presence_list
-
-        def is_presence_visible(observed_localpart, observer_userid):
-            return True
-        self.datastore.is_presence_visible = is_presence_visible
-
-        # Local users
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
-        self.u_clementine = hs.parse_userid("@clementine:test")
-        self.u_fig = hs.parse_userid("@fig:test")
-
-        # Remote users
-        self.u_potato = hs.parse_userid("@potato:remote")
-
     @defer.inlineCallbacks
     def test_push_local(self):
         # apple goes online
diff --git a/tests/handlers/test_presencelike.py b/tests/handlers/test_presencelike.py
index 0584e4c8b9..3cdbb186ae 100644
--- a/tests/handlers/test_presencelike.py
+++ b/tests/handlers/test_presencelike.py
@@ -27,6 +27,7 @@ from synapse.server import HomeServer
 from synapse.api.constants import PresenceState
 from synapse.handlers.presence import PresenceHandler
 from synapse.handlers.profile import ProfileHandler
+from synapse.types import UserID
 
 
 OFFLINE = PresenceState.OFFLINE
@@ -136,12 +137,12 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
                 lambda u: defer.succeed([]))
 
         # Some local users to test with
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
-        self.u_clementine = hs.parse_userid("@clementine:test")
+        self.u_apple = UserID.from_string("@apple:test")
+        self.u_banana = UserID.from_string("@banana:test")
+        self.u_clementine = UserID.from_string("@clementine:test")
 
         # Remote user
-        self.u_potato = hs.parse_userid("@potato:remote")
+        self.u_potato = UserID.from_string("@potato:remote")
 
         self.mock_get_joined = (
             self.datastore.get_rooms_for_user_where_membership_is
diff --git a/tests/handlers/test_profile.py b/tests/handlers/test_profile.py
index 25b172aa5e..7b9590c110 100644
--- a/tests/handlers/test_profile.py
+++ b/tests/handlers/test_profile.py
@@ -22,7 +22,7 @@ from mock import Mock, NonCallableMock
 from synapse.api.errors import AuthError
 from synapse.server import HomeServer
 from synapse.handlers.profile import ProfileHandler
-from synapse.api.constants import Membership
+from synapse.types import UserID
 
 from tests.utils import SQLiteMemoryDbPool, MockKey
 
@@ -71,9 +71,9 @@ class ProfileTestCase(unittest.TestCase):
 
         self.store = hs.get_datastore()
 
-        self.frank = hs.parse_userid("@1234ABCD:test")
-        self.bob   = hs.parse_userid("@4567:test")
-        self.alice = hs.parse_userid("@alice:remote")
+        self.frank = UserID.from_string("@1234ABCD:test")
+        self.bob   = UserID.from_string("@4567:test")
+        self.alice = UserID.from_string("@alice:remote")
 
         yield self.store.create_profile(self.frank.localpart)
 
diff --git a/tests/handlers/test_room.py b/tests/handlers/test_room.py
index d3253b48b8..9a23b3812d 100644
--- a/tests/handlers/test_room.py
+++ b/tests/handlers/test_room.py
@@ -15,12 +15,13 @@
 
 
 from twisted.internet import defer
-from tests import unittest
+from .. import unittest
 
 from synapse.api.constants import EventTypes, Membership
 from synapse.handlers.room import RoomMemberHandler, RoomCreationHandler
 from synapse.handlers.profile import ProfileHandler
 from synapse.server import HomeServer
+from synapse.types import UserID
 from ..utils import MockKey
 
 from mock import Mock, NonCallableMock
@@ -164,7 +165,7 @@ class RoomMemberHandlerTestCase(unittest.TestCase):
             event, context=context,
         )
         self.notifier.on_new_room_event.assert_called_once_with(
-            event, extra_users=[self.hs.parse_userid(target_user_id)]
+            event, extra_users=[UserID.from_string(target_user_id)]
         )
         self.assertFalse(self.datastore.get_room.called)
         self.assertFalse(self.datastore.store_room.called)
@@ -174,7 +175,7 @@ class RoomMemberHandlerTestCase(unittest.TestCase):
     def test_simple_join(self):
         room_id = "!foo:red"
         user_id = "@bob:red"
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         join_signal_observer = Mock()
         self.distributor.observe("user_joined_room", join_signal_observer)
@@ -252,7 +253,7 @@ class RoomMemberHandlerTestCase(unittest.TestCase):
     def test_simple_leave(self):
         room_id = "!foo:red"
         user_id = "@bob:red"
-        user = self.hs.parse_userid(user_id)
+        user = UserID.from_string(user_id)
 
         builder = self.hs.get_event_builder_factory().new({
             "type": EventTypes.Member,
diff --git a/tests/handlers/test_typing.py b/tests/handlers/test_typing.py
index 6a498b23a4..8a7fc028d1 100644
--- a/tests/handlers/test_typing.py
+++ b/tests/handlers/test_typing.py
@@ -27,6 +27,7 @@ from synapse.server import HomeServer
 from synapse.handlers.typing import TypingNotificationHandler
 
 from synapse.storage.transactions import DestinationsTable
+from synapse.types import UserID
 
 
 def _expect_edu(destination, edu_type, content, origin="test"):
@@ -153,11 +154,11 @@ class TypingNotificationsTestCase(unittest.TestCase):
         self.auth.check_joined_room = check_joined_room
 
         # Some local users to test with
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
+        self.u_apple = UserID.from_string("@apple:test")
+        self.u_banana = UserID.from_string("@banana:test")
 
         # Remote user
-        self.u_onion = hs.parse_userid("@onion:farm")
+        self.u_onion = UserID.from_string("@onion:farm")
 
     @defer.inlineCallbacks
     def test_started_typing_local(self):
diff --git a/tests/rest/__init__.py b/tests/rest/__init__.py
index 9bff9ec169..1a84d94cd9 100644
--- a/tests/rest/__init__.py
+++ b/tests/rest/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 OpenMarket Ltd
+# Copyright 2015 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,4 +12,3 @@
 # 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.
-
diff --git a/tests/rest/client/__init__.py b/tests/rest/client/__init__.py
new file mode 100644
index 0000000000..1a84d94cd9
--- /dev/null
+++ b/tests/rest/client/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Copyright 2015 OpenMarket Ltd
+#
+# Licensed 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.
diff --git a/tests/rest/client/v1/__init__.py b/tests/rest/client/v1/__init__.py
new file mode 100644
index 0000000000..9bff9ec169
--- /dev/null
+++ b/tests/rest/client/v1/__init__.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 OpenMarket Ltd
+#
+# Licensed 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.
+
diff --git a/tests/rest/test_events.py b/tests/rest/client/v1/test_events.py
index d3159e2cf4..0384ffbb3d 100644
--- a/tests/rest/test_events.py
+++ b/tests/rest/client/v1/test_events.py
@@ -19,13 +19,13 @@ from tests import unittest
 # twisted imports
 from twisted.internet import defer
 
-import synapse.rest.events
-import synapse.rest.register
-import synapse.rest.room
+import synapse.rest.client.v1.events
+import synapse.rest.client.v1.register
+import synapse.rest.client.v1.room
 
 from synapse.server import HomeServer
 
-from ..utils import MockHttpResource, SQLiteMemoryDbPool, MockKey
+from ....utils import MockHttpResource, SQLiteMemoryDbPool, MockKey
 from .utils import RestTestCase
 
 from mock import Mock, NonCallableMock
@@ -144,9 +144,9 @@ class EventStreamPermissionsTestCase(RestTestCase):
         hs.get_clock().time_msec.return_value = 1000000
         hs.get_clock().time.return_value = 1000
 
-        synapse.rest.register.register_servlets(hs, self.mock_resource)
-        synapse.rest.events.register_servlets(hs, self.mock_resource)
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.register.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.events.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
         # register an account
         self.user_id = "sid1"
diff --git a/tests/rest/test_presence.py b/tests/rest/client/v1/test_presence.py
index 769c7824bc..65d5cc4916 100644
--- a/tests/rest/test_presence.py
+++ b/tests/rest/client/v1/test_presence.py
@@ -20,11 +20,14 @@ from twisted.internet import defer
 
 from mock import Mock
 
-from ..utils import MockHttpResource, MockKey
+from ....utils import MockHttpResource, MockKey
 
 from synapse.api.constants import PresenceState
 from synapse.handlers.presence import PresenceHandler
 from synapse.server import HomeServer
+from synapse.rest.client.v1 import presence
+from synapse.rest.client.v1 import events
+from synapse.types import UserID
 
 
 OFFLINE = PresenceState.OFFLINE
@@ -69,7 +72,7 @@ class PresenceStateTestCase(unittest.TestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(myid),
+                "user": UserID.from_string(myid),
                 "admin": False,
                 "device_id": None,
             }
@@ -86,9 +89,9 @@ class PresenceStateTestCase(unittest.TestCase):
             return defer.succeed([])
         room_member_handler.get_rooms_for_user = get_rooms_for_user
 
-        hs.register_servlets()
+        presence.register_servlets(hs, self.mock_resource)
 
-        self.u_apple = hs.parse_userid(myid)
+        self.u_apple = UserID.from_string(myid)
 
     @defer.inlineCallbacks
     def test_get_my_status(self):
@@ -159,12 +162,12 @@ class PresenceListTestCase(unittest.TestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(myid),
+                "user": UserID.from_string(myid),
                 "admin": False,
                 "device_id": None,
             }
 
-        room_member_handler = hs.handlers.room_member_handler = Mock(
+        hs.handlers.room_member_handler = Mock(
             spec=[
                 "get_rooms_for_user",
             ]
@@ -172,10 +175,10 @@ class PresenceListTestCase(unittest.TestCase):
 
         hs.get_auth().get_user_by_token = _get_user_by_token
 
-        hs.register_servlets()
+        presence.register_servlets(hs, self.mock_resource)
 
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
+        self.u_apple = UserID.from_string("@apple:test")
+        self.u_banana = UserID.from_string("@banana:test")
 
     @defer.inlineCallbacks
     def test_get_my_list(self):
@@ -279,11 +282,12 @@ class PresenceEventStreamTestCase(unittest.TestCase):
         hs.get_clock().time_msec.return_value = 1000000
 
         def _get_user_by_req(req=None):
-            return hs.parse_userid(myid)
+            return UserID.from_string(myid)
 
         hs.get_auth().get_user_by_req = _get_user_by_req
 
-        hs.register_servlets()
+        presence.register_servlets(hs, self.mock_resource)
+        events.register_servlets(hs, self.mock_resource)
 
         hs.handlers.room_member_handler = Mock(spec=[])
 
@@ -319,8 +323,8 @@ class PresenceEventStreamTestCase(unittest.TestCase):
 
         self.presence = hs.get_handlers().presence_handler
 
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
+        self.u_apple = UserID.from_string("@apple:test")
+        self.u_banana = UserID.from_string("@banana:test")
 
     @defer.inlineCallbacks
     def test_shortpoll(self):
diff --git a/tests/rest/test_profile.py b/tests/rest/client/v1/test_profile.py
index 3a0d1e700a..39cd68d829 100644
--- a/tests/rest/test_profile.py
+++ b/tests/rest/client/v1/test_profile.py
@@ -20,10 +20,13 @@ from twisted.internet import defer
 
 from mock import Mock, NonCallableMock
 
-from ..utils import MockHttpResource, MockKey
+from ....utils import MockHttpResource, MockKey
 
 from synapse.api.errors import SynapseError, AuthError
 from synapse.server import HomeServer
+from synapse.types import UserID
+
+from synapse.rest.client.v1 import profile
 
 myid = "@1234ABCD:test"
 PATH_PREFIX = "/_matrix/client/api/v1"
@@ -55,13 +58,13 @@ class ProfileTestCase(unittest.TestCase):
         )
 
         def _get_user_by_req(request=None):
-            return hs.parse_userid(myid)
+            return UserID.from_string(myid)
 
         hs.get_auth().get_user_by_req = _get_user_by_req
 
         hs.get_handlers().profile_handler = self.mock_handler
 
-        hs.register_servlets()
+        profile.register_servlets(hs, self.mock_resource)
 
     @defer.inlineCallbacks
     def test_get_my_name(self):
diff --git a/tests/rest/test_rooms.py b/tests/rest/client/v1/test_rooms.py
index 8e65ff9a1c..76ed550b75 100644
--- a/tests/rest/test_rooms.py
+++ b/tests/rest/client/v1/test_rooms.py
@@ -18,19 +18,16 @@
 # twisted imports
 from twisted.internet import defer
 
-import synapse.rest.room
+import synapse.rest.client.v1.room
 from synapse.api.constants import Membership
 
 from synapse.server import HomeServer
+from synapse.types import UserID
 
-from tests import unittest
-
-# python imports
 import json
 import urllib
-import types
 
-from ..utils import MockHttpResource, SQLiteMemoryDbPool, MockKey
+from ....utils import MockHttpResource, SQLiteMemoryDbPool, MockKey
 from .utils import RestTestCase
 
 from mock import Mock, NonCallableMock
@@ -70,7 +67,7 @@ class RoomPermissionsTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -82,7 +79,7 @@ class RoomPermissionsTestCase(RestTestCase):
 
         self.auth_user_id = self.rmcreator_id
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
         self.auth = hs.get_auth()
 
@@ -466,7 +463,7 @@ class RoomsMemberListTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -476,7 +473,7 @@ class RoomsMemberListTestCase(RestTestCase):
             return defer.succeed(None)
         hs.get_datastore().insert_client_ip = _insert_client_ip
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
     def tearDown(self):
         pass
@@ -555,7 +552,7 @@ class RoomsCreateTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -565,7 +562,7 @@ class RoomsCreateTestCase(RestTestCase):
             return defer.succeed(None)
         hs.get_datastore().insert_client_ip = _insert_client_ip
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
     def tearDown(self):
         pass
@@ -657,7 +654,7 @@ class RoomTopicTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -668,7 +665,7 @@ class RoomTopicTestCase(RestTestCase):
             return defer.succeed(None)
         hs.get_datastore().insert_client_ip = _insert_client_ip
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
         # create the room
         self.room_id = yield self.create_room_as(self.user_id)
@@ -773,7 +770,7 @@ class RoomMemberStateTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -783,7 +780,7 @@ class RoomMemberStateTestCase(RestTestCase):
             return defer.succeed(None)
         hs.get_datastore().insert_client_ip = _insert_client_ip
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
         self.room_id = yield self.create_room_as(self.user_id)
 
@@ -909,7 +906,7 @@ class RoomMessagesTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -919,7 +916,7 @@ class RoomMessagesTestCase(RestTestCase):
             return defer.succeed(None)
         hs.get_datastore().insert_client_ip = _insert_client_ip
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
         self.room_id = yield self.create_room_as(self.user_id)
 
@@ -1013,7 +1010,7 @@ class RoomInitialSyncTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -1023,12 +1020,12 @@ class RoomInitialSyncTestCase(RestTestCase):
             return defer.succeed(None)
         hs.get_datastore().insert_client_ip = _insert_client_ip
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
         # Since I'm getting my own presence I need to exist as far as presence
         # is concerned.
         hs.get_handlers().presence_handler.registered_user(
-            hs.parse_userid(self.user_id)
+            UserID.from_string(self.user_id)
         )
 
         # create the room
diff --git a/tests/rest/test_typing.py b/tests/rest/client/v1/test_typing.py
index 18138af1b5..c89b37d004 100644
--- a/tests/rest/test_typing.py
+++ b/tests/rest/client/v1/test_typing.py
@@ -18,10 +18,11 @@
 # twisted imports
 from twisted.internet import defer
 
-import synapse.rest.room
+import synapse.rest.client.v1.room
 from synapse.server import HomeServer
+from synapse.types import UserID
 
-from ..utils import MockHttpResource, MockClock, SQLiteMemoryDbPool, MockKey
+from ....utils import MockHttpResource, MockClock, SQLiteMemoryDbPool, MockKey
 from .utils import RestTestCase
 
 from mock import Mock, NonCallableMock
@@ -69,7 +70,7 @@ class RoomTypingTestCase(RestTestCase):
 
         def _get_user_by_token(token=None):
             return {
-                "user": hs.parse_userid(self.auth_user_id),
+                "user": UserID.from_string(self.auth_user_id),
                 "admin": False,
                 "device_id": None,
             }
@@ -82,7 +83,7 @@ class RoomTypingTestCase(RestTestCase):
 
         def get_room_members(room_id):
             if room_id == self.room_id:
-                return defer.succeed([hs.parse_userid(self.user_id)])
+                return defer.succeed([UserID.from_string(self.user_id)])
             else:
                 return defer.succeed([])
 
@@ -104,7 +105,7 @@ class RoomTypingTestCase(RestTestCase):
         hs.get_handlers().room_member_handler.fetch_room_distributions_into = (
                 fetch_room_distributions_into)
 
-        synapse.rest.room.register_servlets(hs, self.mock_resource)
+        synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource)
 
         self.room_id = yield self.create_room_as(self.user_id)
         # Need another user to make notifications actually work
diff --git a/tests/rest/utils.py b/tests/rest/client/v1/utils.py
index 579441fb4a..579441fb4a 100644
--- a/tests/rest/utils.py
+++ b/tests/rest/client/v1/utils.py
diff --git a/tests/storage/TESTS_NEEDED_FOR b/tests/storage/TESTS_NEEDED_FOR
deleted file mode 100644
index 8e5d0cbdc4..0000000000
--- a/tests/storage/TESTS_NEEDED_FOR
+++ /dev/null
@@ -1,5 +0,0 @@
-synapse/storage/feedback.py
-synapse/storage/keys.py
-synapse/storage/pdu.py
-synapse/storage/stream.py
-synapse/storage/transactions.py
diff --git a/tests/storage/test_directory.py b/tests/storage/test_directory.py
index e9c242cc07..bc9ebf35e2 100644
--- a/tests/storage/test_directory.py
+++ b/tests/storage/test_directory.py
@@ -19,6 +19,7 @@ from twisted.internet import defer
 
 from synapse.server import HomeServer
 from synapse.storage.directory import DirectoryStore
+from synapse.types import RoomID, RoomAlias
 
 from tests.utils import SQLiteMemoryDbPool
 
@@ -37,8 +38,8 @@ class DirectoryStoreTestCase(unittest.TestCase):
 
         self.store = DirectoryStore(hs)
 
-        self.room = hs.parse_roomid("!abcde:test")
-        self.alias = hs.parse_roomalias("#my-room:test")
+        self.room = RoomID.from_string("!abcde:test")
+        self.alias = RoomAlias.from_string("#my-room:test")
 
     @defer.inlineCallbacks
     def test_room_to_alias(self):
diff --git a/tests/storage/test_presence.py b/tests/storage/test_presence.py
index 9655d3cf42..1ab193736b 100644
--- a/tests/storage/test_presence.py
+++ b/tests/storage/test_presence.py
@@ -19,6 +19,7 @@ from twisted.internet import defer
 
 from synapse.server import HomeServer
 from synapse.storage.presence import PresenceStore
+from synapse.types import UserID
 
 from tests.utils import SQLiteMemoryDbPool, MockClock
 
@@ -37,8 +38,8 @@ class PresenceStoreTestCase(unittest.TestCase):
 
         self.store = PresenceStore(hs)
 
-        self.u_apple = hs.parse_userid("@apple:test")
-        self.u_banana = hs.parse_userid("@banana:test")
+        self.u_apple = UserID.from_string("@apple:test")
+        self.u_banana = UserID.from_string("@banana:test")
 
     @defer.inlineCallbacks
     def test_state(self):
diff --git a/tests/storage/test_profile.py b/tests/storage/test_profile.py
index 5d36723c28..84381241bc 100644
--- a/tests/storage/test_profile.py
+++ b/tests/storage/test_profile.py
@@ -19,6 +19,7 @@ from twisted.internet import defer
 
 from synapse.server import HomeServer
 from synapse.storage.profile import ProfileStore
+from synapse.types import UserID
 
 from tests.utils import SQLiteMemoryDbPool
 
@@ -36,7 +37,7 @@ class ProfileStoreTestCase(unittest.TestCase):
 
         self.store = ProfileStore(hs)
 
-        self.u_frank = hs.parse_userid("@frank:test")
+        self.u_frank = UserID.from_string("@frank:test")
 
     @defer.inlineCallbacks
     def test_displayname(self):
diff --git a/tests/storage/test_redaction.py b/tests/storage/test_redaction.py
index 9806fbc69b..0713dfab64 100644
--- a/tests/storage/test_redaction.py
+++ b/tests/storage/test_redaction.py
@@ -19,6 +19,7 @@ from twisted.internet import defer
 
 from synapse.server import HomeServer
 from synapse.api.constants import EventTypes, Membership
+from synapse.types import UserID, RoomID
 
 from tests.utils import SQLiteMemoryDbPool, MockKey
 
@@ -48,10 +49,10 @@ class RedactionTestCase(unittest.TestCase):
         self.handlers = hs.get_handlers()
         self.message_handler = self.handlers.message_handler
 
-        self.u_alice = hs.parse_userid("@alice:test")
-        self.u_bob = hs.parse_userid("@bob:test")
+        self.u_alice = UserID.from_string("@alice:test")
+        self.u_bob = UserID.from_string("@bob:test")
 
-        self.room1 = hs.parse_roomid("!abc123:test")
+        self.room1 = RoomID.from_string("!abc123:test")
 
         self.depth = 1
 
diff --git a/tests/storage/test_room.py b/tests/storage/test_room.py
index e7739776ec..71e5d34143 100644
--- a/tests/storage/test_room.py
+++ b/tests/storage/test_room.py
@@ -19,6 +19,7 @@ from twisted.internet import defer
 
 from synapse.server import HomeServer
 from synapse.api.constants import EventTypes
+from synapse.types import UserID, RoomID, RoomAlias
 
 from tests.utils import SQLiteMemoryDbPool
 
@@ -38,9 +39,9 @@ class RoomStoreTestCase(unittest.TestCase):
         # management of the 'room_aliases' table
         self.store = hs.get_datastore()
 
-        self.room = hs.parse_roomid("!abcde:test")
-        self.alias = hs.parse_roomalias("#a-room-name:test")
-        self.u_creator = hs.parse_userid("@creator:test")
+        self.room = RoomID.from_string("!abcde:test")
+        self.alias = RoomAlias.from_string("#a-room-name:test")
+        self.u_creator = UserID.from_string("@creator:test")
 
         yield self.store.store_room(self.room.to_string(),
             room_creator_user_id=self.u_creator.to_string(),
@@ -97,7 +98,7 @@ class RoomEventsStoreTestCase(unittest.TestCase):
         self.store = hs.get_datastore()
         self.event_factory = hs.get_event_factory();
 
-        self.room = hs.parse_roomid("!abcde:test")
+        self.room = RoomID.from_string("!abcde:test")
 
         yield self.store.store_room(self.room.to_string(),
             room_creator_user_id="@creator:text",
diff --git a/tests/storage/test_roommember.py b/tests/storage/test_roommember.py
index a23a8189df..2b9048e2a9 100644
--- a/tests/storage/test_roommember.py
+++ b/tests/storage/test_roommember.py
@@ -19,6 +19,7 @@ from twisted.internet import defer
 
 from synapse.server import HomeServer
 from synapse.api.constants import EventTypes, Membership
+from synapse.types import UserID, RoomID
 
 from tests.utils import SQLiteMemoryDbPool, MockKey
 
@@ -49,13 +50,13 @@ class RoomMemberStoreTestCase(unittest.TestCase):
         self.handlers = hs.get_handlers()
         self.message_handler = self.handlers.message_handler
 
-        self.u_alice = hs.parse_userid("@alice:test")
-        self.u_bob = hs.parse_userid("@bob:test")
+        self.u_alice = UserID.from_string("@alice:test")
+        self.u_bob = UserID.from_string("@bob:test")
 
         # User elsewhere on another host
-        self.u_charlie = hs.parse_userid("@charlie:elsewhere")
+        self.u_charlie = UserID.from_string("@charlie:elsewhere")
 
-        self.room = hs.parse_roomid("!abc123:test")
+        self.room = RoomID.from_string("!abc123:test")
 
     @defer.inlineCallbacks
     def inject_room_member(self, room, user, membership, replaces_state=None):
diff --git a/tests/storage/test_stream.py b/tests/storage/test_stream.py
index 9247fc579e..b7f6e2aa80 100644
--- a/tests/storage/test_stream.py
+++ b/tests/storage/test_stream.py
@@ -19,6 +19,7 @@ from twisted.internet import defer
 
 from synapse.server import HomeServer
 from synapse.api.constants import EventTypes, Membership
+from synapse.types import UserID, RoomID
 
 from tests.utils import SQLiteMemoryDbPool, MockKey
 
@@ -48,11 +49,11 @@ class StreamStoreTestCase(unittest.TestCase):
         self.handlers = hs.get_handlers()
         self.message_handler = self.handlers.message_handler
 
-        self.u_alice = hs.parse_userid("@alice:test")
-        self.u_bob = hs.parse_userid("@bob:test")
+        self.u_alice = UserID.from_string("@alice:test")
+        self.u_bob = UserID.from_string("@bob:test")
 
-        self.room1 = hs.parse_roomid("!abc123:test")
-        self.room2 = hs.parse_roomid("!xyx987:test")
+        self.room1 = RoomID.from_string("!abc123:test")
+        self.room2 = RoomID.from_string("!xyx987:test")
 
         self.depth = 1
 
diff --git a/tests/test_types.py b/tests/test_types.py
index bfb9e6f548..b29a8415b1 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -42,12 +42,6 @@ class UserIDTestCase(unittest.TestCase):
         self.assertTrue(userA == userAagain)
         self.assertTrue(userA != userB)
 
-    def test_via_homeserver(self):
-        user = mock_homeserver.parse_userid("@3456ijkl:my.domain")
-
-        self.assertEquals("3456ijkl", user.localpart)
-        self.assertEquals("my.domain", user.domain)
-
 
 class RoomAliasTestCase(unittest.TestCase):
 
@@ -62,9 +56,3 @@ class RoomAliasTestCase(unittest.TestCase):
         room = RoomAlias("channel", "my.domain")
 
         self.assertEquals(room.to_string(), "#channel:my.domain")
-
-    def test_via_homeserver(self):
-        room = mock_homeserver.parse_roomalias("#elsewhere:my.domain")
-
-        self.assertEquals("elsewhere", room.localpart)
-        self.assertEquals("my.domain", room.domain)