summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <colin@vpzom.click>2020-09-16 16:50:19 -0600
committerColin Reeder <colin@vpzom.click>2020-09-16 16:50:19 -0600
commit3fb5dbdcf22d3ea269e93b0b17eca8cfea18f379 (patch)
treed713661b2db2cf0de66f3f017a7a79911900115f
parentf443d25dbd6227e30b40bc8a75a926166b9f1c50 (diff)
Add API for listing and adding community moderators
-rw-r--r--openapi/openapi.json55
-rw-r--r--res/lang/en.ftl2
-rw-r--r--src/routes/api/communities.rs129
3 files changed, 185 insertions, 1 deletions
diff --git a/openapi/openapi.json b/openapi/openapi.json
index a5b4eae..3b04b05 100644
--- a/openapi/openapi.json
+++ b/openapi/openapi.json
@@ -2,7 +2,7 @@
"openapi": "3.0.1",
"info": {
"title": "lotide API",
- "version": "0.5.0-pre"
+ "version": "0.6.0-pre"
},
"components": {
"schemas": {
@@ -586,6 +586,59 @@
"security": [{"bearer": []}]
}
},
+ "/api/unstable/communities/{communityID}/moderators": {
+ "get": {
+ "summary": "List moderators of a community",
+ "parameters": [
+ {
+ "name": "communityID",
+ "in": "path",
+ "required": true,
+ "schema": {"type": "integer"}
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/MinimalUserInfo"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/unstable/communities/{communityID}/moderators/{userID}": {
+ "put": {
+ "summary": "Add a moderator to a community",
+ "parameters": [
+ {
+ "name": "communityID",
+ "in": "path",
+ "required": true,
+ "schema": {"type": "integer"}
+ },
+ {
+ "name": "userID",
+ "in": "path",
+ "required": true,
+ "schema": {"type": "integer"}
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Successfully added."
+ }
+ },
+ "security": [{"bearer": []}]
+ }
+ },
"/api/unstable/communities/{communityID}/posts": {
"get": {
"summary": "List posts published to a community",
diff --git a/res/lang/en.ftl b/res/lang/en.ftl
index 16fadd4..8b9aab1 100644
--- a/res/lang/en.ftl
+++ b/res/lang/en.ftl
@@ -2,6 +2,8 @@ comment_content_conflict = Exactly one of content_markdown and content_text must
comment_not_yours = That's not your comment
community_edit_denied = You are not authorized to modify this community
community_name_disallowed_chars = Community name contains disallowed characters
+moderators_only_local = Only local users can be community moderators
+must_be_moderator = You must be a community moderator to perform this action
name_in_use = That name is already in use
no_password = No password set for this user
no_such_comment = No such comment
diff --git a/src/routes/api/communities.rs b/src/routes/api/communities.rs
index 8f9c71f..81c52f1 100644
--- a/src/routes/api/communities.rs
+++ b/src/routes/api/communities.rs
@@ -340,6 +340,124 @@ async fn route_unstable_communities_follow(
.body(serde_json::to_vec(&output)?.into())?)
}
+async fn route_unstable_communities_moderators_list(
+ params: (CommunityLocalID,),
+ ctx: Arc<crate::RouteContext>,
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let (community_id,) = params;
+
+ let lang = crate::get_lang_for_req(&req);
+
+ let db = ctx.db_pool.get().await?;
+
+ ({
+ let row = db
+ .query_opt("SELECT 1 FROM community WHERE id=$1", &[&community_id])
+ .await?;
+
+ match row {
+ None => Err(crate::Error::UserError(crate::simple_response(
+ hyper::StatusCode::NOT_FOUND,
+ lang.tr("no_such_community", None).into_owned(),
+ ))),
+ Some(_) => Ok(()),
+ }
+ })?;
+
+ let rows = db.query(
+ "SELECT id, username, local, ap_id, avatar FROM person WHERE id IN (SELECT person FROM community_moderator WHERE community=$1)",
+ &[&community_id],
+ ).await?;
+
+ let output: Vec<_> = rows
+ .iter()
+ .map(|row| {
+ let local = row.get(2);
+ let ap_id = row.get(3);
+
+ RespMinimalAuthorInfo {
+ id: UserLocalID(row.get(0)),
+ username: Cow::Borrowed(row.get(1)),
+ local,
+ host: crate::get_actor_host_or_unknown(local, ap_id, &ctx.local_hostname),
+ remote_url: ap_id.map(|x| x.into()),
+ avatar: row
+ .get::<_, Option<&str>>(4)
+ .map(|url| RespAvatarInfo { url: url.into() }),
+ }
+ })
+ .collect();
+
+ let output = serde_json::to_vec(&output)?;
+
+ Ok(hyper::Response::builder()
+ .header(hyper::header::CONTENT_TYPE, "application/json")
+ .body(output.into())?)
+}
+
+async fn route_unstable_communities_moderators_add(
+ params: (CommunityLocalID, UserLocalID),
+ ctx: Arc<crate::RouteContext>,
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let (community_id, user_id) = params;
+
+ let db = ctx.db_pool.get().await?;
+
+ let lang = crate::get_lang_for_req(&req);
+ let login_user = crate::require_login(&req, &db).await?;
+
+ ({
+ let row = db
+ .query_opt(
+ "SELECT 1 FROM community_moderator WHERE community=$1 AND person=$2",
+ &[&community_id, &login_user],
+ )
+ .await?;
+ match row {
+ None => Err(crate::Error::UserError(crate::simple_response(
+ hyper::StatusCode::FORBIDDEN,
+ lang.tr("must_be_moderator", None).into_owned(),
+ ))),
+ Some(_) => Ok(()),
+ }
+ })?;
+
+ ({
+ let row = db
+ .query_opt("SELECT local FROM person WHERE id=$1", &[&user_id])
+ .await?;
+
+ match row {
+ None => Err(crate::Error::UserError(crate::simple_response(
+ hyper::StatusCode::FORBIDDEN,
+ lang.tr("no_such_user", None).into_owned(),
+ ))),
+ Some(row) => {
+ let local: bool = row.get(0);
+
+ if local {
+ Ok(())
+ } else {
+ Err(crate::Error::UserError(crate::simple_response(
+ hyper::StatusCode::FORBIDDEN,
+ lang.tr("moderators_only_local", None).into_owned(),
+ )))
+ }
+ }
+ }
+ })?;
+
+ db.execute(
+ "INSERT INTO community_moderator (community, person) VALUES ($1, $2)",
+ &[&community_id, &user_id],
+ )
+ .await?;
+
+ Ok(crate::empty_response())
+}
+
async fn route_unstable_communities_unfollow(
params: (CommunityLocalID,),
ctx: Arc<crate::RouteContext>,
@@ -613,6 +731,17 @@ pub fn route_communities() -> crate::RouteNode<()> {
.with_handler_async("POST", route_unstable_communities_follow),
)
.with_child(
+ "moderators",
+ crate::RouteNode::new()
+ .with_handler_async("GET", route_unstable_communities_moderators_list)
+ .with_child_parse::<UserLocalID, _>(
+ crate::RouteNode::new().with_handler_async(
+ "PUT",
+ route_unstable_communities_moderators_add,
+ ),
+ ),
+ )
+ .with_child(
"unfollow",
crate::RouteNode::new()
.with_handler_async("POST", route_unstable_communities_unfollow),