stream-feed-flutter icon indicating copy to clipboard operation
stream-feed-flutter copied to clipboard

Can Create but not Update Activities

Open ObserverMoment opened this issue 2 years ago • 6 comments

Describe the bug I can create an activity with:

await _feed.addActivity(feed.Activity(
          actor: context.streamFeedClient.currentUser!.ref,
          verb: 'post',
          object: '${describeEnum(_selectedObjectType!)}:$_selectedObjectId',
          extraData: {
            'caption': _captionController.text,
            'tags': _tags.whereType<String>().toList()
          }));

Where [_feed] is class of type FlatFeed extends Feed

created via streamFeedClient.flatFeed(kUserFeedName, userId);

However, when I try and run:

await _feed.updateActivityById(id: _activity.id!, set: {
        'caption': _captionController.text,
        'tags': _tags.whereType<String>().toList()
      });

I get this error message:

/flutter (28710): *** DioError ***:
I/flutter (28710): uri: https://api.stream-io-api.com/api/v1.0/activity?api_key=em6ka2nnezvv&location=unspecified
I/flutter (28710): DioError [DioErrorType.response]: Http status error [403]
I/flutter (28710): #0      DioMixin.assureDioError
package:dio/src/dio_mixin.dart:819
I/flutter (28710): #1      DioMixin._dispatchRequest
package:dio/src/dio_mixin.dart:678
I/flutter (28710): <asynchronous suspension>
I/flutter (28710): #2      DioMixin.fetch.<anonymous closure>.<anonymous closure> (package:dio/src/dio_mixin.dart)
package:dio/src/dio_mixin.dart:1
I/flutter (28710): <asynchronous suspension>
I/flutter (28710): uri: https://api.stream-io-api.com/api/v1.0/activity?api_key=em6ka2nnezvv&location=unspecified
I/flutter (28710): statusCode: 403
I/flutter (28710): headers:
I/flutter (28710):  connection: keep-alive
I/flutter (28710):  x-ratelimit-reset: 1629555900
I/flutter (28710):  date: Sat, 21 Aug 2021 14:24:05 GMT
I/flutter (28710):  transfer-encoding: chunked
I/flutter (28710):  access-control-allow-origin: *
I/flutter (28710):  x-ratelimit-limit: 300
I/flutter (28710):  content-encoding: gzip
I/flutter (28710):  x-ratelimit-remaining: 299
I/flutter (28710):  content-type: text/plain; charset=utf-8
I/flutter (28710):  server: nginx
I/flutter (28710): Response Text:
I/flutter (28710): {"detail":"This endpoint cannot be accessed with user authentication","status_code":403,"code":17,"exception":"NotAllowedException","duration":"0.00ms","more_info":"https://getstream.io/docs/api_error_responses"}
2
I/flutter (28710):
I/flutter (28710): StreamApiException{body: {"detail":"This endpoint cannot be accessed with user authentication","status_code":403,"code":17,"exception":"NotAllowedException","duration":"0.00ms","more_info":"https://getstream.io/docs/api_error_responses"}, jsonData: {detail: This endpoint cannot be accessed with user authentication, status_code: 403, code: 17, exception: NotAllowedException, duration: 0.00ms, more_info: https://getstream.io/docs/api_error_responses}, status: 403, code: 17}

The documentation is a bit confusing as it states: (https://getstream.io/activity-feeds/docs/flutter-dart/adding_activities/?language=dart)

When you update an activity, you must include the following fields both when adding and updating the activity:time & foreign_id

But then in the code it gives examples using updateActivityById method which does not include either of these fields.

Not sure if this is a SDK bug or something else but assistance would be appreciated.

Thanks,

Richard


What version of Flutter do you use?

Latest

What package are you using? What version?

Latest

What platform is it about?

  • [ ] Android

ObserverMoment avatar Aug 21 '21 14:08 ObserverMoment

Hi Richard,

This is indeed a case of some misleading documentation and I will ask the Flutter team to take a look. Our Feeds API actually does not allow updating of activities from client-side authentication. It is a request that must come from the backend/server.

Best, Stephen

shodgetts avatar Aug 23 '21 11:08 shodgetts

Hi Stephen,

Thanks for the update!

So, to clarify then:

You can create and delete from the client when using

StreamFeedClient.connect(
        EnvironmentConfig.getStreamPublicKey,
        appId: EnvironmentConfig.getStreamAppId,
        token: feed.Token(
          streamFeedToken,
        ),
      )

but to update you must go via the server-side client? Something like this (in nodejs).

streamFeedClient = connect(
      GETSTREAM_PUBLIC_KEY,
      GETSTREAM_PRIVATE_KEY,
    )

streamFeedClient?.updateActivity()
or
streamFeedClient?.activityPartialUpdate()

Is this a limitation of the current SDK or implementation, or is this a design choice of the API (for security I assume)? Can we expect a future iteration to add this functionality?

Could you also confirm that there is no valid method where you can update an activity via its id (uid)? (Rather than foreignId + time). I am not storing any foreignId at the moment as it is not needed for anything else so will need to build this in if necessary.

Thanks again Stephen!

Richard

ObserverMoment avatar Aug 23 '21 13:08 ObserverMoment

Hi @ObserverMoment can you try to instantiate flat feed like this streamFeedClient.flatFeed(kUserFeedName);(without userId) and let me know if it worked?

sachaarbonel avatar Aug 23 '21 18:08 sachaarbonel

Hi @sachaarbonel, ok I tried this

_userFeed = _streamFeedClient.flatFeed(kUserFeedName);

Received this error:

Provide a `userId` if you are using it server-side or call `setUser` before creating feeds
'package:stream_feed/src/client/stream_feed_client_impl.dart':
Failed assertion: line 204 pos 7: '_isUserConnected || _userId != null'

So I tried adding a call to setUser when I initialize my feed client like this:

Future<void> _initStreamFeeds() async {
    try {
      final feedClient = await feed.StreamFeedClient.connect(
        EnvironmentConfig.getStreamPublicKey,
        appId: EnvironmentConfig.getStreamAppId,
        token: feed.Token(
          streamFeedToken,
        ),
      );
      await feedClient.setUser({'id': authedUser.id});
      _streamFeedClient = feedClient;
      await _initNotificationFeed();
      setState(() {
        _feedsInitialized = true;
      });
    } catch (e) {
      print(e);
      context.showToast(message: "Oops, couldn't initialize your feeds!");
      throw Exception(e);
    }
  }

I'm not sure what is supposed to go into setUser(data) as I could not find any docs on it, and I don't store any user data on Stream, except for the ID.

This stopped the above error from happening.

However, this didn't work when running the update. Same error as in the original post.

So I tried setting user just before making the update call. Here is my edit post method.

void _editPost() async {
    setState(() {
      _loading = true;
    });
    try {
      await context.streamFeedClient.setUser({'id': _authedUser.id});
      _feed = context.streamFeedClient.flatFeed(kUserFeedName);
      await _feed.updateActivityById(id: _activity.id!, set: {
        'caption': _captionController.text,
        'tags': _tags.whereType<String>().toList()
      });

      context.pop();
    } catch (e) {
      print(e);
      context.showToast(
          message: 'There was a problem editing the post.',
          toastType: ToastType.destructive);
      setState(() {
        _loading = false;
      });
    }
  }

This also returns:

{"detail":"This endpoint cannot be accessed with user authentication","status_code":403,"code":17,"exception":"NotAllowedException","duration":"0.00ms","more_info":"https://getstream.io/docs/api_error_responses"}

Do you have any example of the update method working that I could look at?

Thanks,

Richard

ObserverMoment avatar Aug 24 '21 08:08 ObserverMoment

So I checked some backend logic to see what's up and apparently you don't have enough claims in your JWT token. You need those fields to be not null: user_id, resource, feed_id, action. Also, note that is not official but the Dart SDK can be used serverside or use serverside features on the clientside (warning: very unsecure). For prototyping purposes, you can set the runner flag to Runner.server and the SDK will handle the JWT creation for you. Just keep in mind, don't do this in production because it has some security consequences and that you'll have to handle the JWT creation sooner or later on the backend using one of our backend SDKs.

Can you run with the flag and let me know if it solved your issue?

sachaarbonel avatar Aug 24 '21 15:08 sachaarbonel

ok, so as @shodgetts suggested yesterday, editing activities can only (really) be done on the server side. Thanks for clarifying.

For now I'm going to leave editing activities and perhaps revisit at a later stage. It is not vital functionality to us and isn't worth building server side logic just to handle this one thing when almost everything else can be handled client-side.

Is there a structural reason why you can Create and Delete on the client but you must Edit on the server? It would be good to know if this is something that will be addressed in a future update or if this is just how it has to be.

Thanks.

ObserverMoment avatar Aug 24 '21 15:08 ObserverMoment