();
255 | final port = singleCompletePort(completer);
256 | final buffer = req.writeToBuffer().asPtr();
257 | final result = _raw.subsocial_dispatch(port.nativePort, buffer);
258 | _assertOk(result);
259 | final resBytes = await completer.future;
260 | final res = Response.fromBuffer(resBytes);
261 | if (res.hasError()) {
262 | throw res.error;
263 | }
264 | return res;
265 | }
266 | }
267 |
268 | class BadProtoMessageException implements Exception {}
269 |
270 | class ClientNotInitializedException implements Exception {}
271 |
272 | class SubxtException implements Exception {
273 | final String message;
274 | const SubxtException(this.message);
275 | @override
276 | String toString() {
277 | return 'SubxtException($message)';
278 | }
279 | }
280 |
281 | void _assertOk(int result) {
282 | if (result == 0xbadc0de) {
283 | throw BadProtoMessageException();
284 | } else if (result == 0xdead) {
285 | throw ClientNotInitializedException();
286 | } else {
287 | // all good
288 | }
289 | }
290 |
291 | /// Loads the Subsocial [DynamicLibrary] depending on the [Platform]
292 | DynamicLibrary _load() {
293 | if (Platform.isAndroid) {
294 | return DynamicLibrary.open('libsubsocial.so');
295 | } else if (Platform.isIOS) {
296 | return DynamicLibrary.executable();
297 | } else if (Platform.isLinux) {
298 | return DynamicLibrary.open('target/debug/libsubsocial.so');
299 | } else if (Platform.isMacOS) {
300 | return DynamicLibrary.open('target/debug/libsubsocial.dylib');
301 | } else {
302 | throw UnsupportedError('The Current Platform is not supported.');
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/lib/utils.dart:
--------------------------------------------------------------------------------
1 | /// original: https://github.com/dart-lang/isolate/blob/ca133acb5af3a60a026fa2aab12b81e60048b3be/lib/ports.dart
2 |
3 | import 'dart:async';
4 | import 'dart:isolate';
5 |
6 | /// Create a [SendPort] that accepts only one message.
7 | ///
8 | /// The [callback] function is called once, with the first message
9 | /// received by the receive port.
10 | /// All further messages are ignored.
11 | ///
12 | /// If [timeout] is supplied, it is used as a limit on how
13 | /// long it can take before the message is received. If a
14 | /// message isn't received in time, the `callback` function
15 | /// is called once with the [timeoutValue] instead.
16 | ///
17 | /// If the received value is not a [T], it will cause an uncaught
18 | /// asynchronous error in the current zone.
19 | ///
20 | /// Returns the `SendPort` expecting the single message.
21 | ///
22 | /// Equivalent to:
23 | /// ```dart
24 | /// (new ReceivePort()
25 | /// ..first.timeout(duration, () => timeoutValue).then(callback))
26 | /// .sendPort
27 | /// ```
28 | SendPort singleCallbackPort(void Function(P? response) callback,
29 | {Duration? timeout, P? timeoutValue}) {
30 | final responsePort = RawReceivePort();
31 | final zone = Zone.current;
32 | // ignore: parameter_assignments
33 | callback = zone.registerUnaryCallback(callback);
34 | Timer? timer;
35 | responsePort.handler = (response) {
36 | responsePort.close();
37 | timer?.cancel();
38 | zone.runUnary(callback, response as P);
39 | };
40 | if (timeout != null) {
41 | timer = Timer(timeout, () {
42 | responsePort.close();
43 | callback(timeoutValue);
44 | });
45 | }
46 | return responsePort.sendPort;
47 | }
48 |
49 | /// Create a [SendPort] that accepts only one message.
50 | ///
51 | /// When the first message is received, the [callback] function is
52 | /// called with the message as argument,
53 | /// and the [completer] is completed with the result of that call.
54 | /// All further messages are ignored.
55 | ///
56 | /// If `callback` is omitted, it defaults to an identity function.
57 | /// The `callback` call may return a future, and the completer will
58 | /// wait for that future to complete. If [callback] is omitted, the
59 | /// message on the port must be an instance of [R].
60 | ///
61 | /// If [timeout] is supplied, it is used as a limit on how
62 | /// long it can take before the message is received. If a
63 | /// message isn't received in time, the [onTimeout] is called,
64 | /// and `completer` is completed with the result of that call
65 | /// instead.
66 | /// The [callback] function will not be interrupted by the time-out,
67 | /// as long as the initial message is received in time.
68 | /// If `onTimeout` is omitted, it defaults to completing the `completer` with
69 | /// a [TimeoutException].
70 | ///
71 | /// The [completer] may be a synchronous completer. It is only
72 | /// completed in response to another event, either a port message or a timer.
73 | ///
74 | /// Returns the `SendPort` expecting the single message.
75 | SendPort singleCompletePort(Completer completer,
76 | {FutureOr Function(P? message)? callback,
77 | Duration? timeout,
78 | FutureOr Function()? onTimeout}) {
79 | if (callback == null && timeout == null) {
80 | return singleCallbackPort((response) {
81 | _castComplete(completer, response);
82 | });
83 | }
84 | final responsePort = RawReceivePort();
85 | Timer? timer;
86 | if (callback == null) {
87 | responsePort.handler = (response) {
88 | responsePort.close();
89 | timer?.cancel();
90 | _castComplete(completer, response);
91 | };
92 | } else {
93 | final zone = Zone.current;
94 | final action = zone.registerUnaryCallback((response) {
95 | try {
96 | // Also catch it if callback throws.
97 | completer.complete(callback(response as P));
98 | } catch (error, stack) {
99 | completer.completeError(error, stack);
100 | }
101 | });
102 | responsePort.handler = (response) {
103 | responsePort.close();
104 | timer?.cancel();
105 | zone.runUnary(action, response as P);
106 | };
107 | }
108 | if (timeout != null) {
109 | timer = Timer(timeout, () {
110 | responsePort.close();
111 | if (onTimeout != null) {
112 | completer.complete(Future.sync(onTimeout));
113 | } else {
114 | completer
115 | .completeError(TimeoutException('Future not completed', timeout));
116 | }
117 | });
118 | }
119 | return responsePort.sendPort;
120 | }
121 |
122 | // Helper function that casts an object to a type and completes a
123 | // corresponding completer, or completes with the error if the cast fails.
124 | void _castComplete(Completer completer, Object? value) {
125 | try {
126 | completer.complete(value as R);
127 | } catch (error, stack) {
128 | completer.completeError(error, stack);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/native/ffi/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "subsocial-ffi"
3 | version = "0.1.0"
4 | authors = ["Shady Khalifa "]
5 | edition = "2018"
6 |
7 | [lib]
8 | name = "subsocial"
9 | # this value will change depending on the target os
10 | # for iOS it would be `staticlib`
11 | # for android it would be `c-dylib`
12 | crate-type = ["rlib"]
13 |
14 | [dependencies]
15 | sdk = { path = "../sdk", package = "subsocial-sdk" }
16 | bytes = "1.0"
17 | prost = "0.7"
18 | prost-types = "0.7"
19 | once_cell = "1.7"
20 | allo-isolate = { version = "0.1.8-beta", features = ["catch-unwind"] }
21 | # Runtime
22 | async-std = { version = "1.8", features = [] }
23 |
24 | [build-dependencies]
25 | cbindgen = "0.19"
26 | prost-build = "0.7"
27 |
--------------------------------------------------------------------------------
/native/ffi/binding.h:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | struct SubscoialConfig {
4 | const char *url;
5 | };
6 |
7 | // Owned version of Dart's [Uint8List] in Rust.
8 | //
9 | // **Note**: Automatically frees the underlying memory allocated from Dart.
10 | struct Uint8List {
11 | uint8_t *buf;
12 | uintptr_t len;
13 | };
14 |
15 | int32_t subsocial_init_client(int64_t port, struct SubscoialConfig *config);
16 |
17 | int32_t subsocial_dispatch(int64_t port, struct Uint8List *buffer);
18 |
19 | // a no-op function that forces xcode to link to our lib.
20 | // ## Safety
21 | // lol
22 | void subsocial_link_me_plz(void);
23 |
--------------------------------------------------------------------------------
/native/ffi/build.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | fn main() {
4 | // Tell Cargo that if the given file changes, to rerun this build script.
5 | println!("cargo:rerun-if-changed=def.proto");
6 | println!("cargo:rerun-if-changed=src/lib.rs");
7 |
8 | prost_build::Config::new()
9 | .out_dir("src/pb")
10 | .compile_protos(&["def.proto"], &["."])
11 | .expect("Failed to compile protobuf");
12 |
13 | let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
14 | let config = cbindgen::Config {
15 | language: cbindgen::Language::C,
16 | documentation_style: cbindgen::DocumentationStyle::C99,
17 | line_length: 100,
18 | style: cbindgen::Style::Tag,
19 | no_includes: true,
20 | sys_includes: vec![String::from("stdint.h")],
21 | ..Default::default()
22 | };
23 | cbindgen::Builder::new()
24 | .with_crate(crate_dir)
25 | .with_config(config)
26 | .generate()
27 | .expect("Unable to generate bindings")
28 | .write_to_file("binding.h");
29 | }
30 |
--------------------------------------------------------------------------------
/native/ffi/def.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package subsocial;
3 |
4 |
5 | message Request {
6 | oneof body {
7 | GetSpaceById space_by_id = 1;
8 | GetSpaceByHandle space_by_handle = 2;
9 | GetPostById post_by_id = 3;
10 | GetPostIdsBySpaceId post_ids_by_space_id = 4;
11 | GetReactionById reaction_by_id = 5;
12 | GetReactionIdsByPostId reaction_ids_by_post_id = 6;
13 | GetReplyIdsByPostId reply_ids_by_post_id = 7;
14 | GetSocialAccountByAccountId social_account_by_account_id = 8;
15 | GetNextSpaceId next_space_id = 9;
16 | GetNextPostId next_post_id = 10;
17 | GetSpaceIdsByOwner space_ids_by_owner = 11;
18 | GetSpaceFollowers space_followers = 12;
19 | GetSpacesFollowedByAccount spaces_followed_by_account = 13;
20 | GetAccountFollowers account_followers = 14;
21 | GetAccountsFollowedByAccount accounts_followed_by_account = 15;
22 | GenerateAccount generate_account = 16;
23 | ImportAccount import_account = 17;
24 | }
25 | }
26 |
27 | message Response {
28 | oneof body {
29 | Error error = 1;
30 | SpaceById space_by_id = 2;
31 | SpaceByHandle space_by_handle = 3;
32 | PostById post_by_id = 4;
33 | PostIdsBySpaceId post_ids_by_space_id = 5;
34 | ReactionById reaction_by_id = 6;
35 | ReactionIdsByPostId reaction_ids_by_post_id = 7;
36 | ReplyIdsByPostId reply_ids_by_post_id = 8;
37 | SocialAccountByAccountId social_account_by_account_id = 9;
38 | NextSpaceId next_space_id = 10;
39 | NextPostId next_post_id = 11;
40 | SpaceIdsByOwner space_ids_by_owner = 12;
41 | SpaceFollowers space_followers = 13;
42 | SpacesFollowedByAccount spaces_followed_by_account = 14;
43 | AccountFollowers account_followers = 15;
44 | AccountsFollowedByAccount accounts_followed_by_account = 16;
45 | GeneratedAccount generated_account = 17;
46 | ImportedAccount imported_account = 18;
47 | }
48 | }
49 |
50 | message Error {
51 | enum Kind {
52 | KIND_UNKNOWN = 0;
53 | KIND_NETWORK = 1;
54 | KIND_INVALID_PROTO = 2;
55 | KIND_INVALID_REQUEST = 3;
56 | KIND_NOT_FOUND = 4;
57 | KIND_SUBXT = 5;
58 | }
59 | Kind kind = 1;
60 | string msg = 2;
61 | }
62 |
63 | // REQUESTS
64 |
65 | message GetSpaceById {
66 | uint64 space_id = 1;
67 | }
68 |
69 | message GetSpaceByHandle {
70 | string handle = 1;
71 | }
72 |
73 | message GetPostById {
74 | uint64 post_id = 1;
75 | }
76 |
77 | message GetReactionById {
78 | uint64 reaction_id = 1;
79 | }
80 |
81 | message GetPostIdsBySpaceId {
82 | uint64 space_id = 1;
83 | }
84 |
85 | message GetReactionIdsByPostId {
86 | uint64 post_id = 1;
87 | }
88 |
89 | message GetReplyIdsByPostId {
90 | uint64 post_id = 1;
91 | }
92 |
93 | message GetSocialAccountByAccountId {
94 | string account_id = 1;
95 | }
96 |
97 | message GetNextSpaceId {}
98 |
99 | message GetNextPostId {}
100 |
101 | message GetSpaceIdsByOwner {
102 | string account_id = 1;
103 | }
104 |
105 | message GetSpaceFollowers {
106 | uint64 space_id = 1;
107 | }
108 |
109 | message GetSpacesFollowedByAccount {
110 | string account_id = 1;
111 | }
112 |
113 | message GetAccountFollowers {
114 | string account_id = 1;
115 | }
116 |
117 | message GetAccountsFollowedByAccount {
118 | string account_id = 1;
119 | }
120 |
121 | message GenerateAccount {
122 | string password = 1;
123 | }
124 |
125 | message ImportAccount {
126 | string password = 1;
127 | string suri = 2;
128 | }
129 |
130 | // DATA
131 |
132 | message WhoAndWhen {
133 | string account = 1;
134 | uint64 block_number = 2;
135 | uint64 time = 3;
136 | }
137 |
138 | message Content {
139 | oneof value {
140 | bytes raw = 1;
141 | string ipfs = 2;
142 | string hyper = 3;
143 | }
144 | }
145 |
146 | message PostExtension {
147 | oneof value {
148 | Comment comment = 1;
149 | SharedPost shared_post = 2;
150 | }
151 | }
152 |
153 | message Space {
154 | uint64 id = 1;
155 | WhoAndWhen created = 2;
156 | WhoAndWhen updated = 3;
157 | string owner = 4;
158 | uint64 parent_id = 5;
159 | string handle = 6;
160 | Content content = 7;
161 | bool hidden = 8;
162 | uint32 posts_count = 9;
163 | uint32 hidden_posts_count = 10;
164 | uint32 followers_count = 11;
165 | int32 score = 12;
166 | }
167 |
168 | message Post {
169 | uint64 id = 1;
170 | WhoAndWhen created = 2;
171 | WhoAndWhen updated = 3;
172 | string owner = 4;
173 | PostExtension extension_value = 5;
174 | uint64 space_id = 6;
175 | Content content = 7;
176 | bool hidden = 8;
177 | uint32 replies_count = 9;
178 | uint32 hidden_replies_count = 10;
179 | uint32 shares_count = 11;
180 | uint32 upvotes_count = 12;
181 | uint32 downvotes_count = 13;
182 | int32 score = 14;
183 | }
184 |
185 | message Comment {
186 | uint64 parent_id = 1;
187 | uint64 root_post_id = 2;
188 | }
189 |
190 | message SharedPost {
191 | uint64 root_post_id = 1;
192 | }
193 |
194 | message Reaction {
195 | enum ReactionKind {
196 | UNKNOWN = 0;
197 | UP_VOTE = 1;
198 | DOWN_VOTE = 2;
199 | }
200 | uint64 id = 1;
201 | WhoAndWhen created = 2;
202 | WhoAndWhen updated = 3;
203 | ReactionKind kind = 4;
204 | }
205 |
206 | message SocialAccount {
207 | uint32 followers_count = 1;
208 | uint32 following_accounts_count = 2;
209 | uint32 following_spaces_count = 3;
210 | uint32 reputation = 4;
211 | Profile profile = 5;
212 | }
213 |
214 | message Profile {
215 | WhoAndWhen created = 1;
216 | WhoAndWhen updated = 2;
217 | Content content = 3;
218 | }
219 |
220 | // RESPONSES
221 |
222 |
223 | message SpaceById {
224 | Space space = 1;
225 | }
226 |
227 | message SpaceByHandle {
228 | Space space = 1;
229 | }
230 |
231 | message PostById {
232 | Post post = 1;
233 | }
234 |
235 | message ReactionById {
236 | Reaction reaction = 1;
237 | }
238 |
239 | message PostIdsBySpaceId {
240 | repeated uint64 post_ids = 1 [packed = true];
241 | }
242 |
243 | message ReactionIdsByPostId {
244 | repeated uint64 reaction_ids = 1 [packed = true];
245 | }
246 |
247 | message ReplyIdsByPostId {
248 | repeated uint64 reply_ids = 1 [packed = true];
249 | }
250 |
251 | message SocialAccountByAccountId {
252 | SocialAccount social_account = 1;
253 | }
254 |
255 | message NextSpaceId {
256 | uint64 id = 1;
257 | }
258 |
259 | message NextPostId {
260 | uint64 id = 1;
261 | }
262 |
263 | message SpaceIdsByOwner {
264 | repeated uint64 space_ids = 1 [packed = true];
265 | }
266 |
267 | message SpaceFollowers {
268 | repeated string account_ids = 1;
269 | }
270 |
271 | message SpacesFollowedByAccount {
272 | repeated uint64 space_ids = 1 [packed = true];
273 | }
274 |
275 | message AccountFollowers {
276 | repeated string account_ids = 1;
277 | }
278 |
279 | message AccountsFollowedByAccount {
280 | repeated string account_ids = 1;
281 | }
282 |
283 | message GeneratedAccount {
284 | string public_key = 1;
285 | string seed_phrase = 2;
286 | }
287 |
288 | message ImportedAccount {
289 | string public_key = 1;
290 | }
291 |
--------------------------------------------------------------------------------
/native/ffi/src/dart_utils.rs:
--------------------------------------------------------------------------------
1 | use std::ptr::NonNull;
2 |
3 | /// Owned version of Dart's [Uint8List] in Rust.
4 | ///
5 | /// **Note**: Automatically frees the underlying memory allocated from Dart.
6 | #[repr(C)]
7 | pub struct Uint8List {
8 | buf: NonNull,
9 | len: usize,
10 | }
11 |
12 | impl AsRef<[u8]> for Uint8List {
13 | fn as_ref(&self) -> &[u8] {
14 | self.as_slice()
15 | }
16 | }
17 |
18 | impl Uint8List {
19 | pub fn as_slice(&self) -> &[u8] {
20 | unsafe { core::slice::from_raw_parts(self.buf.as_ptr(), self.len) }
21 | }
22 | }
23 |
24 | impl Drop for Uint8List {
25 | fn drop(&mut self) {
26 | let owned = unsafe {
27 | Vec::from_raw_parts(self.buf.as_ptr(), self.len, self.len)
28 | };
29 | drop(owned);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/native/ffi/src/handler.rs:
--------------------------------------------------------------------------------
1 | use prost::Message;
2 | use sdk::pallet::*;
3 | use sdk::runtime::SubsocialRuntime;
4 | use sdk::subxt::sp_core::Pair;
5 | use sdk::subxt::sp_runtime::AccountId32;
6 | use sdk::subxt::Client;
7 |
8 | use crate::pb::subsocial::request::Body as RequestBody;
9 | use crate::pb::subsocial::response::Body as ResponseBody;
10 | use crate::pb::subsocial::*;
11 | use crate::transformer::*;
12 |
13 | pub async fn handle(
14 | client: &Client,
15 | req: Request,
16 | ) -> Vec {
17 | let body = match req.body {
18 | Some(v) => v,
19 | None => {
20 | let mut bytes = Vec::new();
21 | let kind = error::Kind::InvalidRequest.into();
22 | Error {
23 | kind,
24 | msg: String::from("Empty body"),
25 | }
26 | .encode(&mut bytes)
27 | .expect("should never fails");
28 | return bytes;
29 | }
30 | };
31 | let result = match body {
32 | RequestBody::NextSpaceId(_) => next_space_id(client).await,
33 | RequestBody::NextPostId(_) => next_post_id(client).await,
34 | RequestBody::SpaceById(args) => {
35 | space_by_id(client, args.space_id).await
36 | }
37 | RequestBody::SpaceByHandle(args) => {
38 | space_by_handle(client, args.handle).await
39 | }
40 | RequestBody::SpaceIdsByOwner(args) => {
41 | space_ids_by_owner(client, args.account_id).await
42 | }
43 | RequestBody::PostIdsBySpaceId(args) => {
44 | posts_ids_by_space_id(client, args.space_id).await
45 | }
46 | RequestBody::PostById(args) => post_by_id(client, args.post_id).await,
47 | RequestBody::ReactionIdsByPostId(args) => {
48 | reactions_ids_by_post_id(client, args.post_id).await
49 | }
50 | RequestBody::ReactionById(args) => {
51 | reaction_by_id(client, args.reaction_id).await
52 | }
53 | RequestBody::ReplyIdsByPostId(args) => {
54 | reply_ids_by_post_id(client, args.post_id).await
55 | }
56 | RequestBody::SocialAccountByAccountId(args) => {
57 | social_account_by_account_id(client, args.account_id).await
58 | }
59 | RequestBody::SpaceFollowers(args) => {
60 | space_followers(client, args.space_id).await
61 | }
62 | RequestBody::SpacesFollowedByAccount(args) => {
63 | spaces_followed_by_account(client, args.account_id).await
64 | }
65 | RequestBody::AccountFollowers(args) => {
66 | account_followers(client, args.account_id).await
67 | }
68 | RequestBody::AccountsFollowedByAccount(args) => {
69 | accounts_followed_by_account(client, args.account_id).await
70 | }
71 | RequestBody::GenerateAccount(args) => generate_account(args),
72 | RequestBody::ImportAccount(args) => import_account(args),
73 | };
74 | let response = match result {
75 | Ok(body) => Response { body: Some(body) },
76 | Err(e) => Response {
77 | body: Some(ResponseBody::Error(e)),
78 | },
79 | };
80 | let mut buffer = Vec::new();
81 | response.encode(&mut buffer).expect("should never fails");
82 | buffer
83 | }
84 |
85 | async fn space_by_id(
86 | client: &Client,
87 | space_id: u64,
88 | ) -> Result {
89 | let store = spaces::SpaceByIdStore::new(space_id);
90 | let maybe_space = client.fetch(&store, None).await?;
91 | match maybe_space {
92 | Some(space) => {
93 | let body = ResponseBody::SpaceById(SpaceById {
94 | space: Some(space.into()),
95 | });
96 | Ok(body)
97 | }
98 | None => Err(Error {
99 | kind: error::Kind::NotFound.into(),
100 | msg: String::from("Space Not Found"),
101 | }),
102 | }
103 | }
104 |
105 | async fn space_by_handle(
106 | client: &Client,
107 | handle: String,
108 | ) -> Result {
109 | let store = spaces::SpaceIdByHandleStore::new(handle);
110 | let maybe_space_id = client.fetch(&store, None).await?;
111 | match maybe_space_id {
112 | Some(space_id) => {
113 | let body = space_by_id(client, space_id).await?;
114 | let space = match body {
115 | ResponseBody::SpaceById(space_by_id) => space_by_id.space,
116 | _ => unreachable!(),
117 | };
118 | let body = ResponseBody::SpaceByHandle(SpaceByHandle { space });
119 | Ok(body)
120 | }
121 | None => Err(Error {
122 | kind: error::Kind::NotFound.into(),
123 | msg: String::from("Space Not Found"),
124 | }),
125 | }
126 | }
127 |
128 | async fn space_ids_by_owner(
129 | client: &Client,
130 | account_id: String,
131 | ) -> Result {
132 | let account_id = AccountId32::convert(account_id)?;
133 | let store = spaces::SpaceIdsByOwnerStore::new(account_id);
134 | let maybe_ids = client.fetch(&store, None).await?;
135 | match maybe_ids {
136 | Some(space_ids) => {
137 | let body =
138 | ResponseBody::SpaceIdsByOwner(SpaceIdsByOwner { space_ids });
139 | Ok(body)
140 | }
141 | None => Err(Error {
142 | kind: error::Kind::NotFound.into(),
143 | msg: String::from("AccountId Not Found"),
144 | }),
145 | }
146 | }
147 |
148 | async fn posts_ids_by_space_id(
149 | client: &Client,
150 | space_id: u64,
151 | ) -> Result {
152 | let store = posts::PostIdsBySpaceIdStore::new(space_id);
153 | let maybe_ids = client.fetch(&store, None).await?;
154 | match maybe_ids {
155 | Some(ids) => {
156 | let body = ResponseBody::PostIdsBySpaceId(PostIdsBySpaceId {
157 | post_ids: ids,
158 | });
159 | Ok(body)
160 | }
161 | None => Err(Error {
162 | kind: error::Kind::NotFound.into(),
163 | msg: String::from("Space Not Found"),
164 | }),
165 | }
166 | }
167 |
168 | async fn post_by_id(
169 | client: &Client,
170 | post_id: u64,
171 | ) -> Result {
172 | let store = posts::PostByIdStore::new(post_id);
173 | let maybe_post = client.fetch(&store, None).await?;
174 | match maybe_post {
175 | Some(post) => {
176 | let body = ResponseBody::PostById(PostById {
177 | post: Some(post.into()),
178 | });
179 | Ok(body)
180 | }
181 | None => Err(Error {
182 | kind: error::Kind::NotFound.into(),
183 | msg: String::from("Post Not Found"),
184 | }),
185 | }
186 | }
187 |
188 | async fn reactions_ids_by_post_id(
189 | client: &Client,
190 | post_id: u64,
191 | ) -> Result {
192 | let store = reactions::ReactionIdsByPostIdStore::new(post_id);
193 | let maybe_ids = client.fetch(&store, None).await?;
194 | match maybe_ids {
195 | Some(ids) => {
196 | let body = ResponseBody::ReactionIdsByPostId(ReactionIdsByPostId {
197 | reaction_ids: ids,
198 | });
199 | Ok(body)
200 | }
201 | None => Err(Error {
202 | kind: error::Kind::NotFound.into(),
203 | msg: String::from("Post Not Found"),
204 | }),
205 | }
206 | }
207 |
208 | async fn reaction_by_id(
209 | client: &Client,
210 | reaction_id: u64,
211 | ) -> Result {
212 | let store = reactions::ReactionByIdStore::new(reaction_id);
213 | let maybe_reaction = client.fetch(&store, None).await?;
214 | match maybe_reaction {
215 | Some(reaction) => {
216 | let body = ResponseBody::ReactionById(ReactionById {
217 | reaction: Some(reaction.into()),
218 | });
219 | Ok(body)
220 | }
221 | None => Err(Error {
222 | kind: error::Kind::NotFound.into(),
223 | msg: String::from("Reaction Not Found"),
224 | }),
225 | }
226 | }
227 |
228 | async fn social_account_by_account_id(
229 | client: &Client,
230 | account_id: String,
231 | ) -> Result {
232 | let account_id = AccountId32::convert(account_id)?;
233 | let store = profiles::SocialAccountByIdStore::new(account_id);
234 | let maybe_account = client.fetch(&store, None).await?;
235 | match maybe_account {
236 | Some(account) => {
237 | let body = ResponseBody::SocialAccountByAccountId(
238 | SocialAccountByAccountId {
239 | social_account: Some(account.into()),
240 | },
241 | );
242 | Ok(body)
243 | }
244 | None => Err(Error {
245 | kind: error::Kind::NotFound.into(),
246 | msg: String::from("Social Account Not Found"),
247 | }),
248 | }
249 | }
250 |
251 | async fn reply_ids_by_post_id(
252 | client: &Client,
253 | post_id: u64,
254 | ) -> Result {
255 | let store = posts::ReplyIdsByPostIdStore::new(post_id);
256 | let maybe_ids = client.fetch(&store, None).await?;
257 | match maybe_ids {
258 | Some(ids) => {
259 | let body = ResponseBody::ReplyIdsByPostId(ReplyIdsByPostId {
260 | reply_ids: ids,
261 | });
262 | Ok(body)
263 | }
264 | None => Err(Error {
265 | kind: error::Kind::NotFound.into(),
266 | msg: String::from("Post Not Found"),
267 | }),
268 | }
269 | }
270 |
271 | async fn next_space_id(
272 | client: &Client,
273 | ) -> Result {
274 | let store = spaces::NextSpaceIdStore::default();
275 | let maybe_id = client.fetch(&store, None).await?;
276 | match maybe_id {
277 | Some(id) => {
278 | let body = ResponseBody::NextSpaceId(NextSpaceId { id });
279 | Ok(body)
280 | }
281 | None => unreachable!(),
282 | }
283 | }
284 |
285 | async fn next_post_id(
286 | client: &Client,
287 | ) -> Result {
288 | let store = posts::NextPostIdStore::default();
289 | let maybe_id = client.fetch(&store, None).await?;
290 | match maybe_id {
291 | Some(id) => {
292 | let body = ResponseBody::NextPostId(NextPostId { id });
293 | Ok(body)
294 | }
295 | None => unreachable!(),
296 | }
297 | }
298 |
299 | async fn space_followers(
300 | client: &Client,
301 | space_id: u64,
302 | ) -> Result {
303 | let store = space_follows::SpaceFollowersStore::new(space_id);
304 | let maybe_account_ids = client.fetch(&store, None).await?;
305 | match maybe_account_ids {
306 | Some(account_ids) => {
307 | let body = ResponseBody::SpaceFollowers(SpaceFollowers {
308 | account_ids: account_ids
309 | .into_iter()
310 | .map(|v| v.to_string())
311 | .collect(),
312 | });
313 | Ok(body)
314 | }
315 | None => Err(Error {
316 | kind: error::Kind::NotFound.into(),
317 | msg: String::from("Space Not Found"),
318 | }),
319 | }
320 | }
321 |
322 | async fn spaces_followed_by_account(
323 | client: &Client,
324 | account_id: String,
325 | ) -> Result {
326 | let account_id = AccountId32::convert(account_id)?;
327 | let store = space_follows::SpacesFollowedByAccountStore::new(account_id);
328 | let maybe_space_ids = client.fetch(&store, None).await?;
329 | match maybe_space_ids {
330 | Some(space_ids) => {
331 | let body = ResponseBody::SpacesFollowedByAccount(
332 | SpacesFollowedByAccount { space_ids },
333 | );
334 | Ok(body)
335 | }
336 | None => Err(Error {
337 | kind: error::Kind::NotFound.into(),
338 | msg: String::from("AccountId Not Found"),
339 | }),
340 | }
341 | }
342 |
343 | async fn account_followers(
344 | client: &Client,
345 | account_id: String,
346 | ) -> Result {
347 | let account_id = AccountId32::convert(account_id)?;
348 | let store = profile_follows::AccountFollowersStore::new(account_id);
349 | let maybe_account_ids = client.fetch(&store, None).await?;
350 | match maybe_account_ids {
351 | Some(account_ids) => {
352 | let body = ResponseBody::AccountFollowers(AccountFollowers {
353 | account_ids: account_ids
354 | .into_iter()
355 | .map(|v| v.to_string())
356 | .collect(),
357 | });
358 | Ok(body)
359 | }
360 | None => Err(Error {
361 | kind: error::Kind::NotFound.into(),
362 | msg: String::from("AccountId Not Found"),
363 | }),
364 | }
365 | }
366 |
367 | async fn accounts_followed_by_account(
368 | client: &Client,
369 | account_id: String,
370 | ) -> Result {
371 | let account_id = AccountId32::convert(account_id)?;
372 | let store =
373 | profile_follows::AccountsFollowedByAccountStore::new(account_id);
374 | let maybe_account_ids = client.fetch(&store, None).await?;
375 | match maybe_account_ids {
376 | Some(account_ids) => {
377 | let body = ResponseBody::AccountsFollowedByAccount(
378 | AccountsFollowedByAccount {
379 | account_ids: account_ids
380 | .into_iter()
381 | .map(|v| v.to_string())
382 | .collect(),
383 | },
384 | );
385 | Ok(body)
386 | }
387 | None => Err(Error {
388 | kind: error::Kind::NotFound.into(),
389 | msg: String::from("AccountId Not Found"),
390 | }),
391 | }
392 | }
393 |
394 | fn generate_account(
395 | GenerateAccount { password }: GenerateAccount,
396 | ) -> Result {
397 | let (pair, seed_phrase, _) =
398 | crate::Sr25519Pair::generate_with_phrase(if password.is_empty() {
399 | None
400 | } else {
401 | Some(password.as_str())
402 | });
403 | let pair = crate::subxt::PairSigner::new(pair);
404 | let public_key = pair.signer().public().to_string();
405 | unsafe {
406 | let _old = crate::SIGNER.take();
407 | crate::SIGNER
408 | .set(pair)
409 | .map_err(|_| ())
410 | .expect("should never happen");
411 | };
412 | let body = ResponseBody::GeneratedAccount(GeneratedAccount {
413 | seed_phrase,
414 | public_key,
415 | });
416 | Ok(body)
417 | }
418 |
419 | fn import_account(
420 | ImportAccount { password, suri }: ImportAccount,
421 | ) -> Result {
422 | let (pair, _) = crate::Sr25519Pair::from_string_with_seed(
423 | &suri,
424 | if password.is_empty() {
425 | None
426 | } else {
427 | Some(password.as_str())
428 | },
429 | )
430 | .map_err(|_| {
431 | // TODO(shekohex): handle each case of this error with proper error message.
432 | crate::subxt::Error::Other(String::from("Invalid Phrase Format"))
433 | })?;
434 | let pair = crate::subxt::PairSigner::new(pair);
435 | let public_key = pair.signer().public().to_string();
436 | unsafe {
437 | let _old = crate::SIGNER.take();
438 | crate::SIGNER
439 | .set(pair)
440 | .map_err(|_| ())
441 | .expect("should never happen");
442 | };
443 | let body = ResponseBody::ImportedAccount(ImportedAccount { public_key });
444 | Ok(body)
445 | }
446 |
--------------------------------------------------------------------------------
/native/ffi/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::CStr;
2 | use std::os::raw::c_char;
3 |
4 | use allo_isolate::Isolate;
5 | use async_std::task;
6 | use once_cell::sync::OnceCell;
7 | use sdk::runtime::SubsocialRuntime;
8 | use sdk::subxt;
9 |
10 | mod dart_utils;
11 | mod handler;
12 | mod pb;
13 | mod transformer;
14 |
15 | use dart_utils::Uint8List;
16 | use pb::subsocial;
17 | use prost::Message;
18 | use sdk::subxt::sp_core::sr25519::Pair as Sr25519Pair;
19 |
20 | /// Global Shared [subxt::Client] between all tasks.
21 | static CLIENT: OnceCell> = OnceCell::new();
22 | /// Global Shared [subxt::PairSigner] between all tasks.
23 | static mut SIGNER: OnceCell> =
24 | OnceCell::new();
25 |
26 | #[derive(Debug, Clone)]
27 | #[repr(C)]
28 | pub struct SubscoialConfig {
29 | url: *const c_char,
30 | }
31 |
32 | #[no_mangle]
33 | pub extern "C" fn subsocial_init_client(
34 | port: i64,
35 | config: Box,
36 | ) -> i32 {
37 | let isolate = Isolate::new(port);
38 | // check if we already have a client
39 | if CLIENT.get().is_some() {
40 | isolate.post(());
41 | return 1; // we are good!
42 | }
43 | let url = unsafe {
44 | CStr::from_ptr(config.url)
45 | .to_str()
46 | .unwrap_or("wss://rpc.subsocial.network")
47 | };
48 | let task = isolate.catch_unwind(async move {
49 | let client = subxt::ClientBuilder::new().set_url(url).build().await?;
50 | CLIENT.set(client).map_err(|_| {
51 | subxt::Error::Other(String::from("Client already initialized"))
52 | })?;
53 | Result::<_, subxt::Error>::Ok(())
54 | });
55 | task::spawn(task);
56 | 1
57 | }
58 |
59 | #[no_mangle]
60 | pub extern "C" fn subsocial_dispatch(port: i64, buffer: Box) -> i32 {
61 | let isolate = Isolate::new(port);
62 | let req = match prost::Message::decode(buffer.as_slice()) {
63 | Ok(v) => v,
64 | Err(e) => {
65 | let mut bytes = Vec::new();
66 | let kind = subsocial::error::Kind::InvalidProto.into();
67 | subsocial::Error {
68 | kind,
69 | msg: e.to_string(),
70 | }
71 | .encode(&mut bytes)
72 | .expect("should never fails");
73 | isolate.post(bytes);
74 | return 0xbadc0de;
75 | }
76 | };
77 | let client = match CLIENT.get() {
78 | Some(v) => v,
79 | None => return 0xdead,
80 | };
81 | let task = isolate.catch_unwind(handler::handle(client, req));
82 | task::spawn(task);
83 | 1
84 | }
85 |
86 | /// a no-op function that forces xcode to link to our lib.
87 | /// ## Safety
88 | /// lol
89 | #[inline(never)]
90 | #[no_mangle]
91 | pub unsafe extern "C" fn subsocial_link_me_plz() {}
92 |
--------------------------------------------------------------------------------
/native/ffi/src/pb/mod.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_camel_case_types, clippy::large_enum_variant)]
2 |
3 | pub mod subsocial;
4 |
--------------------------------------------------------------------------------
/native/ffi/src/pb/subsoical.rs:
--------------------------------------------------------------------------------
1 | #[derive(Clone, PartialEq, ::prost::Message)]
2 | pub struct Request {
3 | #[prost(oneof = "request::Body", tags = "1, 2, 3, 4, 5, 6")]
4 | pub body: ::core::option::Option,
5 | }
6 | /// Nested message and enum types in `Request`.
7 | pub mod request {
8 | #[derive(Clone, PartialEq, ::prost::Oneof)]
9 | pub enum Body {
10 | #[prost(message, tag = "1")]
11 | SpaceById(super::GetSpaceById),
12 | #[prost(message, tag = "2")]
13 | SpaceByHandle(super::GetSpaceByHandle),
14 | #[prost(message, tag = "3")]
15 | PostById(super::GetPostById),
16 | #[prost(message, tag = "4")]
17 | PostIdsBySpaceId(super::GetPostIdsBySpaceId),
18 | #[prost(message, tag = "5")]
19 | ReactionById(super::GetReactionById),
20 | #[prost(message, tag = "6")]
21 | ReactionIdsByPostId(super::GetReactionIdsByPostId),
22 | }
23 | }
24 | #[derive(Clone, PartialEq, ::prost::Message)]
25 | pub struct Response {
26 | #[prost(oneof = "response::Body", tags = "1, 2, 3, 4, 5, 6, 7")]
27 | pub body: ::core::option::Option,
28 | }
29 | /// Nested message and enum types in `Response`.
30 | pub mod response {
31 | #[derive(Clone, PartialEq, ::prost::Oneof)]
32 | pub enum Body {
33 | #[prost(message, tag = "1")]
34 | Error(super::Error),
35 | #[prost(message, tag = "2")]
36 | SpaceById(super::SpaceById),
37 | #[prost(message, tag = "3")]
38 | SpaceByHandle(super::SpaceByByHandle),
39 | #[prost(message, tag = "4")]
40 | PostById(super::PostById),
41 | #[prost(message, tag = "5")]
42 | PostIdsBySpaceId(super::PostIdsBySpaceId),
43 | #[prost(message, tag = "6")]
44 | ReactionById(super::ReactionById),
45 | #[prost(message, tag = "7")]
46 | ReactionIdsByPostId(super::ReactionIdsByPostId),
47 | }
48 | }
49 | #[derive(Clone, PartialEq, ::prost::Message)]
50 | pub struct Error {
51 | #[prost(enumeration = "error::Kind", tag = "1")]
52 | pub kind: i32,
53 | #[prost(string, tag = "2")]
54 | pub msg: ::prost::alloc::string::String,
55 | }
56 | /// Nested message and enum types in `Error`.
57 | pub mod error {
58 | #[derive(
59 | Clone,
60 | Copy,
61 | Debug,
62 | PartialEq,
63 | Eq,
64 | Hash,
65 | PartialOrd,
66 | Ord,
67 | ::prost::Enumeration,
68 | )]
69 | #[repr(i32)]
70 | pub enum Kind {
71 | Unknown = 0,
72 | Network = 1,
73 | InvalidProto = 2,
74 | InvalidRequest = 3,
75 | }
76 | }
77 | #[derive(Clone, PartialEq, ::prost::Message)]
78 | pub struct GetSpaceById {
79 | #[prost(uint64, tag = "1")]
80 | pub space_id: u64,
81 | }
82 | #[derive(Clone, PartialEq, ::prost::Message)]
83 | pub struct GetSpaceByHandle {
84 | #[prost(string, tag = "1")]
85 | pub handle: ::prost::alloc::string::String,
86 | }
87 | #[derive(Clone, PartialEq, ::prost::Message)]
88 | pub struct GetPostById {
89 | #[prost(uint64, tag = "1")]
90 | pub post_id: u64,
91 | }
92 | #[derive(Clone, PartialEq, ::prost::Message)]
93 | pub struct GetReactionById {
94 | #[prost(uint64, tag = "1")]
95 | pub reaction_id: u64,
96 | }
97 | #[derive(Clone, PartialEq, ::prost::Message)]
98 | pub struct GetPostIdsBySpaceId {
99 | #[prost(uint64, tag = "1")]
100 | pub space_id: u64,
101 | }
102 | #[derive(Clone, PartialEq, ::prost::Message)]
103 | pub struct GetReactionIdsByPostId {
104 | #[prost(uint64, tag = "1")]
105 | pub post_id: u64,
106 | }
107 | #[derive(Clone, PartialEq, ::prost::Message)]
108 | pub struct Space {
109 | #[prost(uint64, tag = "1")]
110 | pub id: u64,
111 | }
112 | #[derive(Clone, PartialEq, ::prost::Message)]
113 | pub struct Post {
114 | #[prost(uint64, tag = "1")]
115 | pub id: u64,
116 | }
117 | #[derive(Clone, PartialEq, ::prost::Message)]
118 | pub struct Reaction {
119 | #[prost(uint64, tag = "1")]
120 | pub id: u64,
121 | }
122 | #[derive(Clone, PartialEq, ::prost::Message)]
123 | pub struct SpaceById {
124 | #[prost(message, optional, tag = "1")]
125 | pub space: ::core::option::Option,
126 | }
127 | #[derive(Clone, PartialEq, ::prost::Message)]
128 | pub struct SpaceByByHandle {
129 | #[prost(message, optional, tag = "1")]
130 | pub space: ::core::option::Option,
131 | }
132 | #[derive(Clone, PartialEq, ::prost::Message)]
133 | pub struct PostById {
134 | #[prost(message, optional, tag = "1")]
135 | pub post: ::core::option::Option,
136 | }
137 | #[derive(Clone, PartialEq, ::prost::Message)]
138 | pub struct ReactionById {
139 | #[prost(message, optional, tag = "1")]
140 | pub reaction: ::core::option::Option,
141 | }
142 | #[derive(Clone, PartialEq, ::prost::Message)]
143 | pub struct PostIdsBySpaceId {
144 | #[prost(uint64, repeated, tag = "1")]
145 | pub post_id: ::prost::alloc::vec::Vec,
146 | }
147 | #[derive(Clone, PartialEq, ::prost::Message)]
148 | pub struct ReactionIdsByPostId {
149 | #[prost(uint64, repeated, tag = "1")]
150 | pub reaction_id: ::prost::alloc::vec::Vec,
151 | }
152 |
--------------------------------------------------------------------------------
/native/ffi/src/transformer.rs:
--------------------------------------------------------------------------------
1 | use sdk::pallet::{posts, profiles, reactions, spaces, utils};
2 | use sdk::runtime::SubsocialRuntime;
3 | use sdk::subxt::sp_core::crypto::Ss58Codec;
4 | use sdk::subxt::sp_runtime::AccountId32;
5 |
6 | use crate::pb::subsocial::*;
7 |
8 | pub trait AccountIdFromString: Ss58Codec {
9 | fn convert(val: String) -> Result {
10 | match Self::from_string(&val) {
11 | Ok(val) => Ok(val),
12 | Err(_) => Err(Error {
13 | kind: error::Kind::InvalidRequest.into(),
14 | msg: String::from("Invalid AccountId"),
15 | }),
16 | }
17 | }
18 | }
19 |
20 | impl AccountIdFromString for AccountId32 {}
21 |
22 | impl From for Error {
23 | fn from(e: sdk::subxt::Error) -> Self {
24 | Self {
25 | kind: error::Kind::Subxt.into(),
26 | msg: e.to_string(),
27 | }
28 | }
29 | }
30 |
31 | impl From> for WhoAndWhen {
32 | fn from(v: utils::WhoAndWhen) -> Self {
33 | Self {
34 | account: v.account.to_string(),
35 | block_number: v.block.into(),
36 | time: v.time,
37 | }
38 | }
39 | }
40 |
41 | impl From for Content {
42 | fn from(content: utils::Content) -> Self {
43 | use content::Value;
44 | match content {
45 | utils::Content::None => unimplemented!("Should not be called"),
46 | utils::Content::Raw(value) => Content {
47 | value: Some(Value::Raw(value)),
48 | },
49 | utils::Content::IPFS(cid) => Content {
50 | value: Some(Value::Ipfs(String::from_utf8(cid).unwrap())),
51 | },
52 | utils::Content::Hyper(link) => Content {
53 | value: Some(Value::Hyper(String::from_utf8(link).unwrap())),
54 | },
55 | }
56 | }
57 | }
58 |
59 | impl From for PostExtension {
60 | fn from(extension: posts::PostExtension) -> Self {
61 | use post_extension::Value;
62 | match extension {
63 | posts::PostExtension::Comment(comment) => PostExtension {
64 | value: Some(Value::Comment(Comment {
65 | parent_id: comment.parent_id.unwrap_or_default(),
66 | root_post_id: comment.root_post_id,
67 | })),
68 | },
69 | posts::PostExtension::SharedPost(id) => PostExtension {
70 | value: Some(Value::SharedPost(SharedPost { root_post_id: id })),
71 | },
72 | posts::PostExtension::RegularPost => {
73 | unimplemented!("Should be None!")
74 | }
75 | }
76 | }
77 | }
78 |
79 | impl From> for Space {
80 | fn from(space: spaces::Space) -> Self {
81 | use utils::Content;
82 | Self {
83 | id: space.id,
84 | created: Some(space.created.into()),
85 | updated: space.updated.map(Into::into),
86 | owner: space.owner.to_string(),
87 | parent_id: space.parent_id.unwrap_or_default(),
88 | handle: space
89 | .handle
90 | .map(String::from_utf8)
91 | .transpose()
92 | .unwrap_or_default()
93 | .unwrap_or_default(),
94 | content: if space.content == Content::None {
95 | None
96 | } else {
97 | Some(space.content.into())
98 | },
99 | hidden: space.hidden,
100 | posts_count: space.posts_count,
101 | hidden_posts_count: space.hidden_posts_count,
102 | followers_count: space.followers_count,
103 | score: space.score,
104 | }
105 | }
106 | }
107 |
108 | impl From> for Post {
109 | fn from(post: posts::Post) -> Self {
110 | use posts::PostExtension;
111 | use utils::Content;
112 | Self {
113 | id: post.id,
114 | created: Some(post.created.into()),
115 | updated: post.updated.map(Into::into),
116 | owner: post.owner.to_string(),
117 | space_id: post.space_id.unwrap_or_default(),
118 | content: if post.content == Content::None {
119 | None
120 | } else {
121 | Some(post.content.into())
122 | },
123 | extension_value: if post.extension == PostExtension::RegularPost {
124 | None
125 | } else {
126 | Some(post.extension.into())
127 | },
128 | hidden: post.hidden,
129 | replies_count: post.replies_count.into(),
130 | shares_count: post.shares_count.into(),
131 | upvotes_count: post.upvotes_count.into(),
132 | downvotes_count: post.downvotes_count.into(),
133 | hidden_replies_count: post.hidden_replies_count.into(),
134 | score: post.score,
135 | }
136 | }
137 | }
138 |
139 | impl From for reaction::ReactionKind {
140 | fn from(k: reactions::ReactionKind) -> Self {
141 | match k {
142 | reactions::ReactionKind::Upvote => reaction::ReactionKind::UpVote,
143 | reactions::ReactionKind::Downvote => {
144 | reaction::ReactionKind::DownVote
145 | }
146 | }
147 | }
148 | }
149 |
150 | impl From> for Reaction {
151 | fn from(reaction: reactions::Reaction) -> Self {
152 | use reaction::ReactionKind;
153 | Self {
154 | id: reaction.id,
155 | created: Some(reaction.created.into()),
156 | updated: reaction.updated.map(Into::into),
157 | kind: ReactionKind::from(reaction.kind).into(),
158 | }
159 | }
160 | }
161 |
162 | impl From> for Profile {
163 | fn from(profile: profiles::Profile) -> Self {
164 | Self {
165 | created: Some(profile.created.into()),
166 | updated: profile.updated.map(Into::into),
167 | content: Some(profile.content.into()),
168 | }
169 | }
170 | }
171 |
172 | impl From> for SocialAccount {
173 | fn from(account: profiles::SocialAccount) -> Self {
174 | Self {
175 | profile: account.profile.map(Into::into),
176 | followers_count: account.followers_count,
177 | reputation: account.reputation,
178 | following_spaces_count: account.following_spaces_count.into(),
179 | following_accounts_count: account.following_accounts_count.into(),
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/native/sdk/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "subsocial-sdk"
3 | version = "0.1.0"
4 | authors = ["Shady Khalifa "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | subxt = { version = "0.14", package = "substrate-subxt" }
9 | codec = { package = "parity-scale-codec", version = "1.3.4", features = ["derive", "full"] }
10 |
11 | # Pallets
12 | # Substrate dependencies
13 | frame-support = { default-features = false, version = '2.0.1' }
14 | frame-system = { default-features = false, version = '2.0.1' }
15 | pallet-timestamp = { default-features = false, version = '2.0.1' }
16 | sp-std = { default-features = false, version = '2.0.1' }
17 |
18 | [dev-dependencies]
19 | async-std = { version = "1.8", features = ["attributes"] }
20 |
--------------------------------------------------------------------------------
/native/sdk/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod pallet;
2 | pub mod runtime;
3 |
4 | pub use subxt;
5 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod permissions;
2 | pub mod posts;
3 | pub mod profile_follows;
4 | pub mod profiles;
5 | pub mod reactions;
6 | pub mod space_follows;
7 | pub mod spaces;
8 | pub mod traits;
9 | pub mod utils;
10 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/permissions.rs:
--------------------------------------------------------------------------------
1 | use codec::{Decode, Encode};
2 | use frame_support::traits::Get;
3 | use frame_system as system;
4 | use sp_std::collections::btree_set::BTreeSet;
5 | use sp_std::prelude::*;
6 | use subxt::sp_runtime::RuntimeDebug;
7 |
8 | use super::utils::SpaceId;
9 |
10 | #[derive(
11 | Encode, Decode, Ord, PartialOrd, Clone, Eq, PartialEq, RuntimeDebug,
12 | )]
13 | pub enum SpacePermission {
14 | /// Create, update, delete, grant and revoke roles in this space.
15 | ManageRoles,
16 |
17 | /// Act on behalf of this space within this space.
18 | RepresentSpaceInternally,
19 | /// Act on behalf of this space outside of this space.
20 | RepresentSpaceExternally,
21 |
22 | /// Update this space.
23 | UpdateSpace,
24 |
25 | // Related to subspaces in this space:
26 | CreateSubspaces,
27 | UpdateOwnSubspaces,
28 | DeleteOwnSubspaces,
29 | HideOwnSubspaces,
30 |
31 | UpdateAnySubspace,
32 | DeleteAnySubspace,
33 | HideAnySubspace,
34 |
35 | // Related to posts in this space:
36 | CreatePosts,
37 | UpdateOwnPosts,
38 | DeleteOwnPosts,
39 | HideOwnPosts,
40 |
41 | UpdateAnyPost,
42 | DeleteAnyPost,
43 | HideAnyPost,
44 |
45 | // Related to comments in this space:
46 | CreateComments,
47 | UpdateOwnComments,
48 | DeleteOwnComments,
49 | HideOwnComments,
50 |
51 | // NOTE: It was made on purpose that it's not possible to update or delete
52 | // not own comments. Instead it's possible to allow to hide and block
53 | // comments.
54 | HideAnyComment,
55 |
56 | /// Upvote any post or comment in this space.
57 | Upvote,
58 | /// Downvote any post or comment in this space.
59 | Downvote,
60 | /// Share any post or comment from this space to another outer space.
61 | Share,
62 |
63 | /// Override permissions per subspace in this space.
64 | OverrideSubspacePermissions,
65 | /// Override permissions per post in this space.
66 | OverridePostPermissions,
67 |
68 | // Related to the moderation pallet:
69 | /// Suggest new entity status in space (whether it's blocked or allowed)
70 | SuggestEntityStatus,
71 | /// Update entity status in space
72 | UpdateEntityStatus,
73 |
74 | // Related to space settings:
75 | /// Allows to update space settings across different pallets.
76 | UpdateSpaceSettings,
77 | }
78 |
79 | pub type SpacePermissionSet = BTreeSet;
80 |
81 | #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
82 | pub struct SpacePermissions {
83 | pub none: Option,
84 | pub everyone: Option,
85 | pub follower: Option,
86 | pub space_owner: Option,
87 | }
88 |
89 | impl Default for SpacePermissions {
90 | fn default() -> SpacePermissions {
91 | SpacePermissions {
92 | none: None,
93 | everyone: None,
94 | follower: None,
95 | space_owner: None,
96 | }
97 | }
98 | }
99 |
100 | #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
101 | pub struct SpacePermissionsContext {
102 | pub space_id: SpaceId,
103 | pub is_space_owner: bool,
104 | pub is_space_follower: bool,
105 | pub space_perms: Option,
106 | }
107 |
108 | /// The pallet's configuration trait.
109 | pub trait Trait: system::Trait {
110 | type DefaultSpacePermissions: Get;
111 | }
112 |
113 | impl SpacePermission {
114 | pub fn is_present_in_role(
115 | &self,
116 | perms_opt: Option,
117 | ) -> bool {
118 | if let Some(perms) = perms_opt {
119 | if perms.contains(self) {
120 | return true;
121 | }
122 | }
123 | false
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/posts.rs:
--------------------------------------------------------------------------------
1 | use std::fmt;
2 | use std::marker::PhantomData;
3 |
4 | use codec::{Decode, Encode};
5 | use subxt::system::*;
6 |
7 | use super::utils::{Content, SpaceId, WhoAndWhen};
8 |
9 | #[subxt::module]
10 | pub trait Posts: System {}
11 |
12 | pub type PostId = u64;
13 |
14 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
15 | pub struct Post {
16 | pub id: PostId,
17 | pub created: WhoAndWhen,
18 | pub updated: Option>,
19 | pub owner: T::AccountId,
20 | pub extension: PostExtension,
21 | pub space_id: Option,
22 | pub content: Content,
23 | pub hidden: bool,
24 | pub replies_count: u16,
25 | pub hidden_replies_count: u16,
26 | pub shares_count: u16,
27 | pub upvotes_count: u16,
28 | pub downvotes_count: u16,
29 | pub score: i32,
30 | }
31 |
32 | impl fmt::Display for Post {
33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 | write!(f, "Post #{} on IPFS: {}", self.id, self.content)?;
35 | Ok(())
36 | }
37 | }
38 |
39 | #[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, Debug)]
40 | pub enum PostExtension {
41 | RegularPost,
42 | Comment(Comment),
43 | SharedPost(PostId),
44 | }
45 |
46 | #[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, Debug)]
47 | pub struct Comment {
48 | pub parent_id: Option,
49 | pub root_post_id: PostId,
50 | }
51 |
52 | // Storage ..
53 |
54 | #[derive(Clone, Debug, Eq, Default, Encode, PartialEq, subxt::Store)]
55 | pub struct NextPostIdStore {
56 | #[store(returns = PostId)]
57 | __marker: PhantomData,
58 | }
59 |
60 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
61 | pub struct PostByIdStore {
62 | #[store(returns = Post)]
63 | post_id: PostId,
64 | __marker: PhantomData,
65 | }
66 |
67 | impl PostByIdStore {
68 | pub fn new(post_id: PostId) -> Self {
69 | Self {
70 | post_id,
71 | __marker: Default::default(),
72 | }
73 | }
74 | }
75 |
76 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
77 | pub struct PostIdsBySpaceIdStore {
78 | #[store(returns = Vec)]
79 | space_id: SpaceId,
80 | __marker: PhantomData,
81 | }
82 |
83 | impl PostIdsBySpaceIdStore {
84 | pub fn new(space_id: SpaceId) -> Self {
85 | Self {
86 | space_id,
87 | __marker: Default::default(),
88 | }
89 | }
90 | }
91 |
92 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
93 | pub struct ReplyIdsByPostIdStore {
94 | #[store(returns = Vec)]
95 | post_id: PostId,
96 | __marker: PhantomData,
97 | }
98 |
99 | impl ReplyIdsByPostIdStore {
100 | pub fn new(post_id: PostId) -> Self {
101 | Self {
102 | post_id,
103 | __marker: Default::default(),
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/profile_follows.rs:
--------------------------------------------------------------------------------
1 | use codec::Encode;
2 | use subxt::system::*;
3 |
4 | #[subxt::module]
5 | pub trait ProfileFollows: System {}
6 |
7 | // Storage ..
8 |
9 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
10 | pub struct AccountFollowersStore {
11 | #[store(returns = Vec)]
12 | account_id: T::AccountId,
13 | }
14 |
15 | impl AccountFollowersStore {
16 | pub fn new(account_id: T::AccountId) -> Self {
17 | Self { account_id }
18 | }
19 | }
20 |
21 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
22 | pub struct AccountsFollowedByAccountStore {
23 | #[store(returns = Vec)]
24 | account_id: T::AccountId,
25 | }
26 |
27 | impl AccountsFollowedByAccountStore {
28 | pub fn new(account_id: T::AccountId) -> Self {
29 | Self { account_id }
30 | }
31 | }
32 |
33 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
34 | pub struct AccountFollowedByAccountStore {
35 | #[store(returns = bool)]
36 | account_one_id: T::AccountId,
37 | account_two_id: T::AccountId,
38 | }
39 |
40 | impl AccountFollowedByAccountStore {
41 | pub fn new(
42 | account_one_id: T::AccountId,
43 | account_two_id: T::AccountId,
44 | ) -> Self {
45 | Self {
46 | account_one_id,
47 | account_two_id,
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/profiles.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | use codec::{Decode, Encode};
4 | use subxt::system::*;
5 |
6 | use super::utils::{Content, WhoAndWhen};
7 |
8 | #[subxt::module]
9 | pub trait Profiles: System {}
10 |
11 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
12 | pub struct SocialAccount {
13 | pub followers_count: u32,
14 | pub following_accounts_count: u16,
15 | pub following_spaces_count: u16,
16 | pub reputation: u32,
17 | pub profile: Option>,
18 | }
19 |
20 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
21 | pub struct Profile {
22 | pub created: WhoAndWhen,
23 | pub updated: Option>,
24 | pub content: Content,
25 | }
26 |
27 | // Storage ..
28 |
29 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
30 | pub struct SocialAccountByIdStore {
31 | #[store(returns = SocialAccount)]
32 | account_id: T::AccountId,
33 | __marker: PhantomData,
34 | }
35 |
36 | impl SocialAccountByIdStore {
37 | pub fn new(account_id: T::AccountId) -> Self {
38 | Self {
39 | account_id,
40 | __marker: Default::default(),
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/reactions.rs:
--------------------------------------------------------------------------------
1 | use std::fmt;
2 | use std::marker::PhantomData;
3 |
4 | use codec::{Decode, Encode};
5 | use subxt::system::*;
6 |
7 | use super::posts::PostId;
8 | use super::utils::WhoAndWhen;
9 |
10 | #[subxt::module]
11 | pub trait Reactions: System {}
12 |
13 | pub type ReactionId = u64;
14 |
15 | #[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, Debug)]
16 | pub enum ReactionKind {
17 | Upvote,
18 | Downvote,
19 | }
20 |
21 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
22 | pub struct Reaction {
23 | pub id: ReactionId,
24 | pub created: WhoAndWhen,
25 | pub updated: Option>,
26 | pub kind: ReactionKind,
27 | }
28 |
29 | impl fmt::Display for Reaction {
30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 | let emo = match self.kind {
32 | ReactionKind::Upvote => "👍",
33 | ReactionKind::Downvote => "👎",
34 | };
35 | write!(f, "Reaction #{} {}", self.id, emo)?;
36 | Ok(())
37 | }
38 | }
39 |
40 | // Storage ..
41 |
42 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
43 | pub struct ReactionByIdStore {
44 | #[store(returns = Reaction)]
45 | reaction_id: ReactionId,
46 | __marker: PhantomData,
47 | }
48 |
49 | impl ReactionByIdStore {
50 | pub fn new(reaction_id: ReactionId) -> Self {
51 | Self {
52 | reaction_id,
53 | __marker: Default::default(),
54 | }
55 | }
56 | }
57 |
58 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
59 | pub struct ReactionIdsByPostIdStore {
60 | #[store(returns = Vec)]
61 | post_id: PostId,
62 | __marker: PhantomData,
63 | }
64 |
65 | impl ReactionIdsByPostIdStore {
66 | pub fn new(post_id: PostId) -> Self {
67 | Self {
68 | post_id,
69 | __marker: Default::default(),
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/space_follows.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | use codec::Encode;
4 | use subxt::system::*;
5 |
6 | use super::utils::SpaceId;
7 |
8 | #[subxt::module]
9 | pub trait SpaceFollows: System {}
10 |
11 | // Storage ..
12 |
13 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
14 | pub struct SpaceFollowersStore {
15 | #[store(returns = Vec)]
16 | space_id: SpaceId,
17 | __marker: PhantomData,
18 | }
19 |
20 | impl SpaceFollowersStore {
21 | pub fn new(space_id: SpaceId) -> Self {
22 | Self {
23 | space_id,
24 | __marker: Default::default(),
25 | }
26 | }
27 | }
28 |
29 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
30 | pub struct SpacesFollowedByAccountStore {
31 | #[store(returns = Vec)]
32 | account_id: T::AccountId,
33 | __marker: PhantomData,
34 | }
35 |
36 | impl SpacesFollowedByAccountStore {
37 | pub fn new(account_id: T::AccountId) -> Self {
38 | Self {
39 | account_id,
40 | __marker: Default::default(),
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/native/sdk/src/pallet/spaces.rs:
--------------------------------------------------------------------------------
1 | use std::fmt;
2 | use std::marker::PhantomData;
3 |
4 | use codec::{Decode, Encode};
5 | use subxt::system::*;
6 |
7 | use super::permissions::SpacePermissions;
8 | use super::utils::{Content, SpaceId, WhoAndWhen};
9 |
10 | #[subxt::module]
11 | pub trait Spaces: System {}
12 |
13 | #[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
14 | pub struct Space {
15 | pub id: SpaceId,
16 | pub created: WhoAndWhen,
17 | pub updated: Option>,
18 | pub owner: T::AccountId,
19 | pub parent_id: Option,
20 | pub handle: Option>,
21 | pub content: Content,
22 | pub hidden: bool,
23 | pub posts_count: u32,
24 | pub hidden_posts_count: u32,
25 | pub followers_count: u32,
26 | pub score: i32,
27 | pub permissions: Option,
28 | }
29 |
30 | impl fmt::Display for Space {
31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 | write!(
33 | f,
34 | "Space #{} with #{} Posts on IPFS: {}",
35 | self.id, self.posts_count, self.content
36 | )?;
37 | Ok(())
38 | }
39 | }
40 |
41 | // Storage ..
42 |
43 | #[derive(Clone, Debug, Eq, Default, Encode, PartialEq, subxt::Store)]
44 | pub struct NextSpaceIdStore {
45 | #[store(returns = SpaceId)]
46 | __marker: PhantomData,
47 | }
48 |
49 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
50 | pub struct SpaceByIdStore {
51 | #[store(returns = Space)]
52 | space_id: SpaceId,
53 | __marker: PhantomData,
54 | }
55 |
56 | impl SpaceByIdStore {
57 | pub fn new(space_id: SpaceId) -> Self {
58 | Self {
59 | space_id,
60 | __marker: Default::default(),
61 | }
62 | }
63 | }
64 |
65 | #[derive(Clone, Debug, Eq, Encode, PartialEq, subxt::Store)]
66 | pub struct SpaceIdByHandleStore