Skip to content
This repository has been archived by the owner on Sep 2, 2021. It is now read-only.

Commit

Permalink
Add basic implementation for the tags endpoint.
Browse files Browse the repository at this point in the history
Add basic implementation for the tags endpoint.
  • Loading branch information
farodin91 committed Oct 15, 2016
1 parent 259d93e commit 0d59ebc
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 0 deletions.
1 change: 1 addition & 0 deletions migrations/001_prerelease/down.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ DROP TABLE room_aliases;
DROP TABLE room_memberships;
DROP TABLE rooms;
DROP TABLE users;
DROP TABLE room_tags;
9 changes: 9 additions & 0 deletions migrations/001_prerelease/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,12 @@ CREATE TABLE users (
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now()
);

CREATE TABLE room_tags (
id BIGSERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
room_id TEXT NOT NULL,
tag TEXT NOT NULL,
content TEXT NOT NULL,
UNIQUE (user_id, room_id, tag)
);
2 changes: 2 additions & 0 deletions src/api/r0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub use self::members::Members;
pub use self::profile::{Profile, GetAvatarUrl, PutAvatarUrl, GetDisplayname, PutDisplayname};
pub use self::registration::Register;
pub use self::room_creation::CreateRoom;
pub use self::tags::{DeleteTag, GetTags, PutTag};
pub use self::versions::Versions;

mod account;
Expand All @@ -27,4 +28,5 @@ mod members;
mod profile;
mod registration;
mod room_creation;
mod tags;
mod versions;
284 changes: 284 additions & 0 deletions src/api/r0/tags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
//! Endpoints for tags.
use std::collections::BTreeMap;
use std::io::Read;

use iron::{Chain, Handler, IronResult, Request, Response};
use iron::status::Status;
use router::Router;
use serde_json::Value;
use serde_json::de::from_str;

use db::DB;
use error::ApiError;
use middleware::{AccessTokenAuth, RoomIdParam, UserIdParam};
use modifier::SerializableResponse;
use tags::RoomTag;
use user::User;

pub type MapTags = BTreeMap<String, Value>;

/// The `/user/:user_id/rooms/:room_id/tags` endpoint.
pub struct GetTags;

#[derive(Debug, Serialize)]
struct GetTagsResponse {
tags: MapTags,
}

impl GetTags {
/// Create a `GetTags` with all necessary middleware.
pub fn chain() -> Chain {
let mut chain = Chain::new(GetTags);

chain.link_before(UserIdParam);
chain.link_before(RoomIdParam);
chain.link_before(AccessTokenAuth);

chain
}
}

impl Handler for GetTags {
fn handle(&self, request: &mut Request) -> IronResult<Response> {
let user_id = request.extensions.get::<UserIdParam>()
.expect("UserIdParam should ensure a UserId").clone();
let room_id = request.extensions.get::<RoomIdParam>()
.expect("RoomIdParam should ensure a RoomId").clone();

let connection = DB::from_request(request)?;

let tags = RoomTag::find(&connection, user_id, room_id)?;
let mut map = MapTags::new();
for tag in tags {
let content = from_str(&tag.content).map_err(ApiError::from)?;
map.insert(tag.tag, content);
}

let response = GetTagsResponse { tags: map };

Ok(Response::with((Status::Ok, SerializableResponse(response))))
}
}

/// The `/user/:user_id/rooms/:room_id/tags/:tag` endpoint.
pub struct PutTag;

impl PutTag {
/// Create a `GetTags` with all necessary middleware.
pub fn chain() -> Chain {
let mut chain = Chain::new(PutTag);

chain.link_before(UserIdParam);
chain.link_before(RoomIdParam);
chain.link_before(AccessTokenAuth);

chain
}
}

impl Handler for PutTag {
fn handle(&self, request: &mut Request) -> IronResult<Response> {
let user_id = request.extensions.get::<UserIdParam>()
.expect("UserIdParam should ensure a UserId").clone();
let room_id = request.extensions.get::<RoomIdParam>()
.expect("RoomIdParam should ensure a RoomId").clone();
let user = request.extensions.get::<User>()
.expect("AccessTokenAuth should ensure a user").clone();
let params = request.extensions.get::<Router>().expect("Params object is missing").clone();
let tag = match params.find("tag") {
Some(tag) => Ok(String::from(tag)),
None => {
Err(ApiError::missing_param("tag"))
}
}?;

// Check if the given user_id corresponds to the authenticated user.
if user_id != user.id {
Err(ApiError::unauthorized(None))?;
}

let mut content = String::new();
if let Err(_) = request.body.read_to_string(&mut content) {
Err(ApiError::not_found(None))?;
}
let content = match content.as_ref() {
"" => Value::Null,
_ => from_str(&content).map_err(ApiError::from)?
};

let connection = DB::from_request(request)?;

RoomTag::upsert(&connection, user_id, room_id, tag, content)?;

Ok(Response::with(Status::Ok))
}
}

/// The `/user/:user_id/rooms/:room_id/tags/:tag` endpoint.
pub struct DeleteTag;

impl DeleteTag {
/// Create a `GetTags` with all necessary middleware.
pub fn chain() -> Chain {
let mut chain = Chain::new(DeleteTag);

chain.link_before(UserIdParam);
chain.link_before(RoomIdParam);
chain.link_before(AccessTokenAuth);

chain
}
}

impl Handler for DeleteTag {
fn handle(&self, request: &mut Request) -> IronResult<Response> {
let user_id = request.extensions.get::<UserIdParam>()
.expect("UserIdParam should ensure a UserId").clone();
let room_id = request.extensions.get::<RoomIdParam>()
.expect("RoomIdParam should ensure a RoomId").clone();
let user = request.extensions.get::<User>()
.expect("AccessTokenAuth should ensure a user").clone();
let params = request.extensions.get::<Router>().expect("Params object is missing").clone();
let tag = match params.find("tag") {
Some(tag) => Ok(String::from(tag)),
None => {
Err(ApiError::missing_param("tag"))
}
}?;

// Check if the given user_id corresponds to the authenticated user.
if user_id != user.id {
Err(ApiError::unauthorized(None))?;
}

let connection = DB::from_request(request)?;

RoomTag::delete(&connection, user_id, room_id, tag)?;

Ok(Response::with(Status::Ok))
}
}


#[cfg(test)]
mod tests {
use test::Test;
use iron::status::Status;

#[test]
fn basic_create_tag() {
let test = Test::new();
let access_token = test.create_access_token(); // @carl:ruma.test

let room_id = test.create_public_room(&access_token);

test.create_tag(&access_token, &room_id, "@carl:ruma.test", "work", r#"{"test":"test"}"#);

let get_tags_path = format!(
"/_matrix/client/r0/user/@carl:ruma.test/rooms/{}/tags?access_token={}",
room_id,
access_token
);

let response = test.get(&get_tags_path);
assert_eq!(response.status, Status::Ok);
let chunk = response.json().find("tags").unwrap();
assert!(chunk.is_object());
let chunk = chunk.as_object().unwrap();
assert_eq!(chunk.len(), 1);
let content = chunk.get("work").unwrap();
assert_eq!(content.to_string(), r#"{"test":"test"}"#);
}

#[test]
fn create_tag_forbidden() {
let test = Test::new();
let access_token = test.create_access_token(); // @carl:ruma.test

let room_id = test.create_public_room(&access_token);
let put_tag_path = format!(
"/_matrix/client/r0/user/{}/rooms/{}/tags/{}?access_token={}",
"@carls:ruma.test",
room_id,
"work",
access_token
);

let response = test.put(&put_tag_path, r#"{}"#);
assert_eq!(response.status, Status::Forbidden);
}

#[test]
fn delete_tag_forbidden() {
let test = Test::new();
let access_token = test.create_access_token(); // @carl:ruma.test

let room_id = test.create_public_room(&access_token);

test.create_tag(&access_token, &room_id, "@carl:ruma.test", "delete", r#"{"test":"test"}"#);

let delete_tag_path = format!(
"/_matrix/client/r0/user/@carl:ruma.test/rooms/{}/tags/delete?access_token={}",
room_id,
access_token
);

let response = test.delete(&delete_tag_path);
assert_eq!(response.status, Status::Ok);
}

#[test]
fn update_tag() {
let test = Test::new();
let access_token = test.create_access_token(); // @carl:ruma.test

let room_id = test.create_public_room(&access_token);

test.create_tag(&access_token, &room_id, "@carl:ruma.test", "test", r#"{"test":"test"}"#);

test.create_tag(&access_token, &room_id, "@carl:ruma.test", "test", r#"{"test":"test2"}"#);

let get_tags_path = format!(
"/_matrix/client/r0/user/@carl:ruma.test/rooms/{}/tags?access_token={}",
room_id,
access_token
);

let response = test.get(&get_tags_path);
let chunk = response.json().find("tags").unwrap();
let chunk = chunk.as_object().unwrap();
let content = chunk.get("test").unwrap();
assert_eq!(content.to_string(), r#"{"test":"test2"}"#);
}

#[test]
fn delete_tag() {
let test = Test::new();
let access_token = test.create_access_token(); // @carl:ruma.test

let room_id = test.create_public_room(&access_token);

test.create_tag(&access_token, &room_id, "@carl:ruma.test", "delete", r#"{"test":"test"}"#);

let delete_tag_path = format!(
"/_matrix/client/r0/user/@carl:ruma.test/rooms/{}/tags/delete?access_token={}",
room_id,
access_token
);

let response = test.delete(&delete_tag_path);
assert_eq!(response.status, Status::Ok);

let get_tags_path = format!(
"/_matrix/client/r0/user/@carl:ruma.test/rooms/{}/tags?access_token={}",
room_id,
access_token
);

let response = test.get(&get_tags_path);
assert_eq!(response.status, Status::Ok);
let chunk = response.json().find("tags").unwrap();
let chunk = chunk.as_object().unwrap();
assert_eq!(chunk.len(), 0);
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub mod schema;
pub mod server;
pub mod swagger;
pub mod room_membership;
pub mod tags;
#[cfg(test)] pub mod test;
pub mod user;

Expand Down
10 changes: 10 additions & 0 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,13 @@ table! {
content -> Text,
}
}

table! {
room_tags {
id -> BigSerial,
user_id -> Text,
room_id -> Text,
tag -> Text,
content -> Text,
}
}
6 changes: 6 additions & 0 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ use api::r0::{
CreateRoom,
DeactivateAccount,
DeleteRoomAlias,
DeleteTag,
GetAvatarUrl,
GetDisplayname,
GetRoomAlias,
GetTags,
JoinRoom,
Login,
Logout,
Expand All @@ -27,6 +29,7 @@ use api::r0::{
PutDisplayname,
PutRoomAccountData,
PutRoomAlias,
PutTag,
Register,
SendMessageEvent,
StateMessageEvent,
Expand Down Expand Up @@ -92,6 +95,9 @@ impl<'a> Server<'a> {
r0_router.get("/profile/:user_id/displayname", GetDisplayname::chain());
r0_router.put("/profile/:user_id/avatar_url", PutAvatarUrl::chain());
r0_router.put("/profile/:user_id/displayname", PutDisplayname::chain());
r0_router.get("/user/:user_id/rooms/:room_id/tags", GetTags::chain());
r0_router.put("/user/:user_id/rooms/:room_id/tags/:tag", PutTag::chain());
r0_router.delete("/user/:user_id/rooms/:room_id/tags/:tag", DeleteTag::chain());

let mut r0 = Chain::new(r0_router);

Expand Down
Loading

0 comments on commit 0d59ebc

Please sign in to comment.