Browse Source

Implement favourites/shares in the backend

pull/85/head
Toromino 8 months ago
parent
commit
19139e27d7
7 changed files with 175 additions and 23 deletions
  1. 24
    0
      src/activity.rs
  2. 13
    0
      src/activitypub/controller.rs
  3. 1
    1
      src/actor.rs
  4. 59
    3
      src/kibou_api/mod.rs
  5. 2
    0
      src/lib.rs
  6. 65
    18
      src/mastodon_api/controller.rs
  7. 11
    1
      src/mastodon_api/routes.rs

+ 24
- 0
src/activity.rs View File

@@ -174,6 +174,30 @@ pub fn get_ap_object_replies_by_id(
174 174
     }
175 175
 }
176 176
 
177
+pub fn type_exists_for_object_id(
178
+    db_connection: &PgConnection,
179
+    _type: &str,
180
+    actor: &str,
181
+    object_id: &str,
182
+) -> Result<bool, diesel::result::Error> {
183
+    match sql_query(format!(
184
+        "SELECT * FROM activities WHERE data->'type' = '{}' AND data->'actor' = '{}' AND data->'object' = '{}' LIMIT 1;",
185
+        _type, runtime_escape(actor), runtime_escape(object_id)
186
+    ))
187
+        .clone()
188
+        .load::<QueryActivity>(db_connection)
189
+        {
190
+            Ok(activity) => {
191
+                if !activity.is_empty() {
192
+                    Ok(true)
193
+                } else {
194
+                    Ok(false)
195
+                }
196
+            }
197
+            Err(e) => Err(e),
198
+        }
199
+}
200
+
177 201
 pub fn serialize_activity(sql_activity: QueryActivity) -> Activity {
178 202
     Activity {
179 203
         id: sql_activity.id,

+ 13
- 0
src/activitypub/controller.rs View File

@@ -54,6 +54,19 @@ pub fn actor_exists(actor_id: &str) -> bool {
54 54
     }
55 55
 }
56 56
 
57
+/// Creates a new `Announce` activity, inserts it into the database and returns the newly created activity
58
+///
59
+/// # Parameters
60
+///
61
+/// * `actor`  -        &str | Reference to an ActivityPub actor
62
+/// * `object` -        &str | Reference to an ActivityStreams object
63
+/// * `to`     - Vec<String> | A vector of strings that provides direct receipients
64
+/// * `cc`     - Vec<String> | A vector of strings that provides passive receipients
65
+///
66
+pub fn announce(actor: &str, object: &str, to: Vec<String>, cc: Vec<String>) -> Activity {
67
+    activity_build("Announce", actor, serde_json::json!(object), to, cc)
68
+}
69
+
57 70
 /// Creates a new `Create` activity, inserts it into the database and returns the newly created activity
58 71
 ///
59 72
 /// # Parameters

+ 1
- 1
src/actor.rs View File

@@ -290,7 +290,7 @@ pub fn get_actor_by_acct(
290 290
 
291 291
 pub fn get_actor_by_id(
292 292
     db_connection: &PgConnection,
293
-    _id: i64,
293
+    _id: &i64,
294 294
 ) -> Result<Actor, diesel::result::Error> {
295 295
     match actors
296 296
         .filter(id.eq(_id))

+ 59
- 3
src/kibou_api/mod.rs View File

@@ -6,11 +6,14 @@
6 6
 
7 7
 pub mod routes;
8 8
 
9
-use activity::{get_activity_by_id, get_ap_activity_by_id, get_ap_object_by_id};
10
-use activitypub::activity::Tag;
9
+use activity::{
10
+    get_activity_by_id, get_ap_activity_by_id, get_ap_object_by_id, type_exists_for_object_id,
11
+};
12
+use activitypub::activity::{serialize_from_internal_activity, Tag};
11 13
 use activitypub::actor::{add_follow, remove_follow};
12 14
 use activitypub::controller as ap_controller;
13
-use actor::{get_actor_by_acct, get_actor_by_uri, is_actor_followed_by, Actor};
15
+use activitypub::routes::object;
16
+use actor::{get_actor_by_acct, get_actor_by_id, get_actor_by_uri, is_actor_followed_by, Actor};
14 17
 use database;
15 18
 use diesel::PgConnection;
16 19
 use html;
@@ -48,6 +51,59 @@ pub fn follow(sender: &str, receipient: &str) {
48 51
     }
49 52
 }
50 53
 
54
+pub fn react(actor: &i64, _type: &str, object_id: &str) {
55
+    let database = database::establish_connection();
56
+    let serialized_actor: Actor = get_actor_by_id(&database, actor).expect("Actor should exist!");
57
+
58
+    if !type_exists_for_object_id(&database, _type, &serialized_actor.actor_uri, object_id)
59
+        .unwrap_or_else(|_| true)
60
+    {
61
+        match get_ap_object_by_id(&database, object_id) {
62
+            Ok(activity) => {
63
+                let ap_activity = serialize_from_internal_activity(activity);
64
+
65
+                let mut to: Vec<String> = ap_activity.to.clone();
66
+                let mut cc: Vec<String> = ap_activity.cc.clone();
67
+                let mut inboxes: Vec<String> = Vec::new();
68
+
69
+                to.retain(|x| x != &format!("{}/followers", &ap_activity.actor));
70
+                cc.retain(|x| x != &format!("{}/followers", &ap_activity.actor));
71
+
72
+                if to.contains(&"https://www.w3.org/ns/activitystreams#Public".to_string()) {
73
+                    cc.push(format!("{}/followers", serialized_actor.actor_uri));
74
+                    inboxes = handle_follower_inboxes(&database, &serialized_actor.followers);
75
+                } else if cc.contains(&"https://www.w3.org/ns/activitystreams#Public".to_string()) {
76
+                    to.push(format!("{}/followers", serialized_actor.actor_uri));
77
+                    inboxes = handle_follower_inboxes(&database, &serialized_actor.followers);
78
+                }
79
+
80
+                match _type {
81
+                    "Announce" => {
82
+                        let new_activity =
83
+                            ap_controller::announce(&serialized_actor.actor_uri, object_id, to, cc);
84
+                        federator::enqueue(
85
+                            serialized_actor,
86
+                            serde_json::json!(&new_activity),
87
+                            inboxes,
88
+                        );
89
+                    }
90
+                    "Like" => {
91
+                        let new_activity =
92
+                            ap_controller::like(&serialized_actor.actor_uri, object_id, to, cc);
93
+                        federator::enqueue(
94
+                            serialized_actor,
95
+                            serde_json::json!(&new_activity),
96
+                            inboxes,
97
+                        );
98
+                    }
99
+                    _ => (),
100
+                }
101
+            }
102
+            Err(_) => (),
103
+        }
104
+    }
105
+}
106
+
51 107
 pub fn route_activities() -> JsonValue {
52 108
     return json!(public_activities());
53 109
 }

+ 2
- 0
src/lib.rs View File

@@ -64,7 +64,9 @@ pub fn rocket_app(config: rocket::config::Config) -> rocket::Rocket {
64 64
                 mastodon_api::routes::notifications,
65 65
                 mastodon_api::routes::status,
66 66
                 mastodon_api::routes::status_context,
67
+                mastodon_api::routes::status_favourite,
67 68
                 mastodon_api::routes::status_post,
69
+                mastodon_api::routes::status_reblog,
68 70
                 mastodon_api::routes::public_timeline,
69 71
                 mastodon_api::routes::options_account,
70 72
                 mastodon_api::routes::options_account_statuses,

+ 65
- 18
src/mastodon_api/controller.rs View File

@@ -1,5 +1,5 @@
1 1
 use activity;
2
-use activity::{get_ap_object_by_id, get_ap_object_replies_by_id};
2
+use activity::{get_ap_object_by_id, get_ap_object_replies_by_id, type_exists_for_object_id};
3 3
 use activitypub;
4 4
 use actor;
5 5
 use actor::get_actor_by_id;
@@ -25,7 +25,7 @@ use timeline::{home_timeline as get_home_timeline, public_timeline as get_public
25 25
 pub fn account_json_by_id(id: i64) -> JsonValue {
26 26
     let database = database::establish_connection();
27 27
 
28
-    match actor::get_actor_by_id(&database, id) {
28
+    match actor::get_actor_by_id(&database, &id) {
29 29
         Ok(actor) => json!(serialize_account(actor, false)),
30 30
         Err(_) => json!({"error": "User not found."}),
31 31
     }
@@ -110,7 +110,7 @@ pub fn account_statuses_json_by_id(
110 110
 ) -> JsonValue {
111 111
     let database = database::establish_connection();
112 112
 
113
-    match actor::get_actor_by_id(&database, id) {
113
+    match actor::get_actor_by_id(&database, &id) {
114 114
         Ok(actor) => {
115 115
             match timeline::user_timeline(&database, actor, max_id, since_id, min_id, limit) {
116 116
                 Ok(statuses) => {
@@ -154,23 +154,35 @@ pub fn context_json_for_id(id: i64) -> JsonValue {
154 154
     }
155 155
 }
156 156
 
157
-pub fn follow_json(token: String, id: i64) -> JsonValue {
158
-    match follow(&token, id) {
159
-        Ok(relationship) => json!(relationship),
160
-        Err(e) => json!({ "error": e }),
157
+pub fn favourite(token: String, id: i64) -> JsonValue {
158
+    let database = database::establish_connection();
159
+
160
+    match activity::get_activity_by_id(&database, id) {
161
+        Ok(activity) => match account_by_oauth_token(token) {
162
+            Ok(account) => {
163
+                kibou_api::react(
164
+                    &account.id.parse::<i64>().unwrap(),
165
+                    "Like",
166
+                    activity.data["object"]["id"].as_str().unwrap(),
167
+                );
168
+                json!(status_cached_by_id(id))
169
+            }
170
+            Err(_) => json!({"error": "Token invalid!"}),
171
+        },
172
+        Err(_) => json!({"error": "Status not found"}),
161 173
     }
162 174
 }
163 175
 
164
-pub fn follow(token: &str, target_id: i64) -> Result<Relationship, &'static str> {
176
+pub fn follow(token: String, id: i64) -> JsonValue {
165 177
     let database = database::establish_connection();
166 178
 
167 179
     match verify_token(&database, token.to_string()) {
168 180
         Ok(token) => match actor::get_local_actor_by_preferred_username(&database, &token.actor) {
169 181
             Ok(actor) => {
170
-                let followee = actor::get_actor_by_id(&database, target_id).unwrap();
182
+                let followee = actor::get_actor_by_id(&database, &id).unwrap();
171 183
 
172 184
                 kibou_api::follow(&actor.actor_uri, &followee.actor_uri);
173
-                return Ok(Relationship {
185
+                return json!(Relationship {
174 186
                     id: followee.id.to_string(),
175 187
                     following: true,
176 188
                     followed_by: false,
@@ -180,9 +192,9 @@ pub fn follow(token: &str, target_id: i64) -> Result<Relationship, &'static str>
180 192
                     requested: false,
181 193
                 });
182 194
             }
183
-            Err(_) => Err("User not found."),
195
+            Err(_) => json!({"error": "User not found."}),
184 196
         },
185
-        Err(_) => Err("Token invalid!"),
197
+        Err(_) => json!({"error": "Token invalid!"}),
186 198
     }
187 199
 }
188 200
 
@@ -207,6 +219,25 @@ pub fn relationships_json_by_token(token: &str, ids: Vec<i64>) -> JsonValue {
207 219
     }
208 220
 }
209 221
 
222
+pub fn reblog(token: String, id: i64) -> JsonValue {
223
+    let database = database::establish_connection();
224
+
225
+    match activity::get_activity_by_id(&database, id) {
226
+        Ok(activity) => match account_by_oauth_token(token) {
227
+            Ok(account) => {
228
+                kibou_api::react(
229
+                    &account.id.parse::<i64>().unwrap(),
230
+                    "Announce",
231
+                    activity.data["object"]["id"].as_str().unwrap(),
232
+                );
233
+                json!(status_cached_by_id(id))
234
+            }
235
+            Err(_) => json!({"error": "Token invalid!"}),
236
+        },
237
+        Err(_) => json!({"error": "Status not found"}),
238
+    }
239
+}
240
+
210 241
 pub fn relationships_by_token(
211 242
     token: &str,
212 243
     ids: Vec<i64>,
@@ -223,7 +254,7 @@ pub fn relationships_by_token(
223 254
                     actor::get_actor_followees(&database, &actor.actor_uri).unwrap();
224 255
 
225 256
                 for id in ids {
226
-                    let follower_actor = actor::get_actor_by_id(&database, id).unwrap();
257
+                    let follower_actor = actor::get_actor_by_id(&database, &id).unwrap();
227 258
 
228 259
                     match activitypub_followers.iter().position(|ref follower| {
229 260
                         follower["href"].as_str().unwrap() == follower_actor.actor_uri
@@ -391,7 +422,7 @@ pub fn unfollow(token: String, target_id: i64) -> JsonValue {
391 422
     match verify_token(&database, token) {
392 423
         Ok(token) => match actor::get_local_actor_by_preferred_username(&database, &token.actor) {
393 424
             Ok(actor) => {
394
-                let followee = actor::get_actor_by_id(&database, target_id).unwrap();
425
+                let followee = actor::get_actor_by_id(&database, &target_id).unwrap();
395 426
 
396 427
                 kibou_api::unfollow(actor.actor_uri, followee.actor_uri);
397 428
                 return json!(Relationship {
@@ -451,7 +482,7 @@ fn count_followees(db_connection: &PgConnection, account_id: &i64) -> i64 {
451 482
 }
452 483
 
453 484
 fn count_followers(db_connection: &PgConnection, account_id: &i64) -> i64 {
454
-    match get_actor_by_id(db_connection, *account_id) {
485
+    match get_actor_by_id(db_connection, account_id) {
455 486
         Ok(actor) => {
456 487
             let activitypub_followers: Vec<serde_json::Value> =
457 488
                 serde_json::from_value(actor.followers["activitypub"].to_owned())
@@ -581,7 +612,7 @@ fn serialize_status_from_activitystreams(activity: activity::Activity) -> Result
581 612
     let serialized_activity: activitypub::activity::Activity =
582 613
         serde_json::from_value(activity.data).unwrap();
583 614
     let serialized_account =
584
-        account_cached_by_uri(Box::leak(activity.actor.into_boxed_str())).unwrap();
615
+        account_cached_by_uri(Box::leak(activity.actor.clone().into_boxed_str())).unwrap();
585 616
 
586 617
     match serialized_activity._type.as_str() {
587 618
         "Create" => {
@@ -627,8 +658,24 @@ fn serialize_status_from_activitystreams(activity: activity::Activity) -> Result
627 658
                 replies_count: count_replies(&database, &serialized_object.id),
628 659
                 reblogs_count: count_reblogs(&database, &serialized_object.id),
629 660
                 favourites_count: count_favourites(&database, &serialized_object.id),
630
-                reblogged: Some(false),
631
-                favourited: Some(false),
661
+                reblogged: Some(
662
+                    type_exists_for_object_id(
663
+                        &database,
664
+                        "Announce",
665
+                        &activity.actor,
666
+                        &serialized_object.id,
667
+                    )
668
+                    .unwrap_or_else(|_| false),
669
+                ),
670
+                favourited: Some(
671
+                    type_exists_for_object_id(
672
+                        &database,
673
+                        "Like",
674
+                        &activity.actor,
675
+                        &serialized_object.id,
676
+                    )
677
+                    .unwrap_or_else(|_| false),
678
+                ),
632 679
                 muted: Some(false),
633 680
                 sensitive: serialized_object.sensitive.unwrap_or_else(|| false),
634 681
                 spoiler_text: String::new(),

+ 11
- 1
src/mastodon_api/routes.rs View File

@@ -19,7 +19,7 @@ pub fn options_account(id: i64) -> JsonValue {
19 19
 
20 20
 #[post("/api/v1/accounts/<id>/follow")]
21 21
 pub fn account_follow(_token: AuthorizationHeader, id: i64) -> JsonValue {
22
-    controller::follow_json(parse_authorization_header(&_token.to_string()), id)
22
+    controller::follow(parse_authorization_header(&_token.to_string()), id)
23 23
 }
24 24
 
25 25
 #[get("/api/v1/accounts/<id>/statuses?<only_media>&<pinned>&<exclude_replies>&<max_id>&<since_id>&<min_id>&<limit>&<exclude_reblogs>")]
@@ -136,6 +136,11 @@ pub fn status_context(id: i64) -> JsonValue {
136 136
     controller::context_json_for_id(id)
137 137
 }
138 138
 
139
+#[post("/api/v1/statuses/<id>/favourite")]
140
+pub fn status_favourite(_token: AuthorizationHeader, id: i64) -> JsonValue {
141
+    controller::favourite(parse_authorization_header(&_token.to_string()), id)
142
+}
143
+
139 144
 #[post("/api/v1/statuses", data = "<form>")]
140 145
 pub fn status_post(form: LenientForm<StatusForm>, _token: AuthorizationHeader) -> JsonValue {
141 146
     controller::status_post(
@@ -144,6 +149,11 @@ pub fn status_post(form: LenientForm<StatusForm>, _token: AuthorizationHeader) -
144 149
     )
145 150
 }
146 151
 
152
+#[post("/api/v1/statuses/<id>/reblog")]
153
+pub fn status_reblog(_token: AuthorizationHeader, id: i64) -> JsonValue {
154
+    controller::reblog(parse_authorization_header(&_token.to_string()), id)
155
+}
156
+
147 157
 #[get("/api/v1/timelines/home?<max_id>&<since_id>&<min_id>&<limit>")]
148 158
 pub fn home_timeline(
149 159
     max_id: Option<i64>,

Loading…
Cancel
Save