Line data Source code
1 : import 'package:dio/dio.dart';
2 : import 'package:stream_feed_dart/src/core/http/stream_http_client.dart';
3 : import 'package:stream_feed_dart/src/core/http/token.dart';
4 : import 'package:stream_feed_dart/src/core/models/activity.dart';
5 : import 'package:stream_feed_dart/src/core/models/activity_update.dart';
6 : import 'package:stream_feed_dart/src/core/models/feed_id.dart';
7 : import 'package:stream_feed_dart/src/core/models/follow.dart';
8 : import 'package:stream_feed_dart/src/core/util/default.dart';
9 : import 'package:stream_feed_dart/src/core/util/extension.dart';
10 : import 'package:stream_feed_dart/src/core/util/routes.dart';
11 :
12 : class FeedApi {
13 : // TODO: uppercase API?
14 :
15 3 : const FeedApi(this.client);
16 :
17 : final StreamHttpClient client;
18 :
19 1 : Future<List<Activity>> addActivities(
20 : Token token, FeedId feed, Iterable<Activity> activities) async {
21 2 : checkArgument(activities.isNotEmpty, 'No activities to add');
22 3 : final result = await client.post<Map>(
23 1 : Routes.buildFeedUrl(feed),
24 2 : headers: {'Authorization': '$token'},
25 1 : data: {'activities': activities},
26 : );
27 2 : final data = (result.data!['activities'] as List)
28 3 : .map((e) => Activity.fromJson(e))
29 1 : .toList(growable: false);
30 : return data;
31 : }
32 :
33 1 : Future<Activity> addActivity(
34 : Token token, FeedId feed, Activity activity) async {
35 3 : final result = await client.post<Map>(
36 1 : Routes.buildFeedUrl(feed),
37 2 : headers: {'Authorization': '$token'},
38 : data: activity,
39 : );
40 2 : final data = Activity.fromJson(result.data as Map<String, dynamic>?);
41 : return data;
42 : }
43 :
44 1 : Future<Response> follow(Token token, Token targetToken, FeedId sourceFeed,
45 : FeedId targetFeed, int activityCopyLimit) async {
46 2 : checkArgument(sourceFeed != targetFeed, "Feed can't follow itself");
47 2 : checkArgument(activityCopyLimit >= 0,
48 : 'Activity copy limit should be a non-negative number');
49 1 : checkArgument(
50 1 : activityCopyLimit <= Default.maxActivityCopyLimit,
51 1 : 'Activity copy limit should be less then ${Default.maxActivityCopyLimit}',
52 : );
53 2 : return client.post(
54 1 : Routes.buildFeedUrl(sourceFeed, 'following'),
55 2 : headers: {'Authorization': '$token'},
56 1 : data: {
57 1 : 'target': '$targetFeed',
58 : 'activity_copy_limit': activityCopyLimit,
59 1 : 'target_token': '$targetToken',
60 : },
61 : );
62 : }
63 :
64 1 : Future<Response<Map>> getActivities(
65 : Token token, FeedId feed, Map<String, Object?> options) =>
66 2 : client.get<Map>(
67 1 : Routes.buildFeedUrl(feed),
68 2 : headers: {'Authorization': '$token'},
69 : queryParameters: options,
70 : );
71 :
72 1 : Future<Response> getEnrichedActivities(
73 : //TODO; hmm I think we can type this
74 : Token token,
75 : FeedId feed,
76 : Map<String, Object?> options) =>
77 2 : client.get(
78 1 : Routes.buildEnrichedFeedUrl(feed),
79 2 : headers: {'Authorization': '$token'},
80 : queryParameters: options,
81 : );
82 :
83 1 : Future<List<Follow>> getFollowed(Token token, FeedId feed, int limit,
84 : int offset, Iterable<FeedId> feedIds) async {
85 2 : checkArgument(limit >= 0, 'Limit should be a non-negative number');
86 2 : checkArgument(offset >= 0, 'Offset should be a non-negative number');
87 :
88 3 : final result = await client.get<Map>(
89 1 : Routes.buildFeedUrl(feed, 'following'),
90 2 : headers: {'Authorization': '$token'},
91 1 : queryParameters: {
92 1 : 'limit': limit,
93 1 : 'offset': offset,
94 1 : if (feedIds.isNotEmpty)
95 5 : 'filter': feedIds.map((it) => it.toString()).join(',')
96 : },
97 : );
98 2 : final data = (result.data!['results'] as List)
99 3 : .map((e) => Follow.fromJson(e))
100 1 : .toList(growable: false);
101 : return data;
102 : }
103 :
104 1 : Future<List<Follow>> getFollowers(Token token, FeedId feed, int limit,
105 : int offset, Iterable<FeedId> feedIds) async {
106 2 : checkArgument(limit >= 0, 'Limit should be a non-negative number');
107 2 : checkArgument(offset >= 0, 'Offset should be a non-negative number');
108 :
109 3 : final result = await client.get(
110 1 : Routes.buildFeedUrl(feed, 'followers'),
111 2 : headers: {'Authorization': '$token'},
112 1 : queryParameters: {
113 1 : 'limit': limit,
114 1 : 'offset': offset,
115 1 : if (feedIds.isNotEmpty)
116 5 : 'filter': feedIds.map((it) => it.toString()).join(',')
117 : },
118 : );
119 2 : final data = (result.data['results'] as List)
120 3 : .map((e) => Follow.fromJson(e))
121 1 : .toList(growable: false);
122 : return data;
123 : }
124 :
125 1 : Future<Response> removeActivityByForeignId(
126 : Token token, FeedId feed, String foreignId) =>
127 2 : client.delete(
128 1 : Routes.buildFeedUrl(feed, foreignId),
129 2 : headers: {'Authorization': '$token'},
130 1 : queryParameters: {'foreign_id': '1'},
131 : );
132 :
133 1 : Future<Response> removeActivityById(Token token, FeedId feed, String id) =>
134 2 : client.delete(
135 1 : Routes.buildFeedUrl(feed, id),
136 2 : headers: {'Authorization': '$token'},
137 : );
138 :
139 1 : Future<Response> unfollow(
140 : Token token, FeedId source, FeedId target, bool keepHistory) =>
141 2 : client.delete(
142 2 : Routes.buildFeedUrl(source, 'following/$target'),
143 2 : headers: {'Authorization': '$token'},
144 1 : queryParameters: {'keep_history': keepHistory},
145 : );
146 :
147 1 : Future<List<Activity>> updateActivitiesByForeignId(
148 : Token token, Iterable<ActivityUpdate> updates) async {
149 2 : checkArgument(updates.isNotEmpty, 'No updates');
150 3 : checkArgument(updates.length <= 100, 'Maximum length is 100');
151 2 : for (final update in updates) {
152 2 : checkNotNull(update.foreignId, 'No activity to update');
153 2 : checkNotNull(update.time, 'Missing timestamp');
154 3 : checkArgument(update.set.isNotEmpty || update.unset.isNotEmpty,
155 : 'No activity properties to set or unset');
156 : }
157 3 : final result = await client.post<Map>(
158 1 : Routes.activityUpdateUrl,
159 2 : headers: {'Authorization': '$token'},
160 1 : data: {'changes': updates},
161 : );
162 2 : final data = (result.data!['activities'] as List)
163 3 : .map((e) => Activity.fromJson(e))
164 1 : .toList(growable: false);
165 : return data;
166 : }
167 :
168 1 : Future<List<Activity>> updateActivitiesById(
169 : Token token, Iterable<ActivityUpdate> updates) async {
170 2 : checkArgument(updates.isNotEmpty, 'No updates');
171 3 : checkArgument(updates.length <= 100, 'Maximum length is 100');
172 2 : for (final update in updates) {
173 2 : checkNotNull(update.id, 'No activity to update');
174 3 : checkArgument(update.set.isNotEmpty || update.unset.isNotEmpty,
175 : 'No activity properties to set or unset');
176 : }
177 3 : final result = await client.post<Map>(
178 1 : Routes.activityUpdateUrl,
179 2 : headers: {'Authorization': '$token'},
180 1 : data: {'changes': updates},
181 : );
182 2 : final data = (result.data!['activities'] as List)
183 3 : .map((e) => Activity.fromJson(e))
184 1 : .toList(growable: false);
185 : return data;
186 : }
187 :
188 1 : Future<Activity> updateActivityByForeignId(
189 : Token token, ActivityUpdate update) async {
190 3 : final result = await client.post<Map>(
191 1 : Routes.activityUpdateUrl,
192 2 : headers: {'Authorization': '$token'},
193 : data: update,
194 : );
195 2 : final data = Activity.fromJson(result.data as Map<String, dynamic>?);
196 : return data;
197 : }
198 :
199 1 : Future<Activity> updateActivityById(
200 : Token token, ActivityUpdate update) async {
201 2 : checkNotNull(update.foreignId, 'No activity to update');
202 2 : checkNotNull(update.time, 'Missing timestamp');
203 3 : checkArgument(update.set.isNotEmpty || update.unset.isNotEmpty,
204 : 'No activity properties to set or unset');
205 3 : final result = await client.post<Map>(
206 1 : Routes.activityUpdateUrl,
207 2 : headers: {'Authorization': '$token'},
208 : data: update,
209 : );
210 2 : final data = Activity.fromJson(result.data as Map<String, dynamic>?);
211 : return data;
212 : }
213 :
214 1 : Future<Response> updateActivityToTargets(
215 : Token token,
216 : FeedId feed,
217 : ActivityUpdate update, {
218 : Iterable<FeedId> add = const [],
219 : Iterable<FeedId> remove = const [],
220 : Iterable<FeedId> replace = const [],
221 : }) async {
222 2 : checkNotNull(update.id, 'No activity to update');
223 3 : checkArgument(update.set.isNotEmpty || update.unset.isNotEmpty,
224 : 'No activity properties to set or unset');
225 1 : checkNotNull(
226 1 : update.foreignId, 'Activity is required to have foreign ID attribute');
227 2 : checkNotNull(update.time, 'Activity is required to have time attribute');
228 1 : checkNotNull(add, 'No targets to add');
229 1 : checkNotNull(remove, 'No targets to remove');
230 1 : checkNotNull(replace, 'No targets to set');
231 : final modification =
232 2 : replace.isEmpty && (add.isNotEmpty || remove.isNotEmpty);
233 1 : final replacement = replace.isNotEmpty && add.isEmpty && remove.isEmpty;
234 1 : checkArgument(modification || replacement,
235 : "Can't replace and modify activity to targets at the same time");
236 :
237 2 : return client.post(
238 1 : Routes.activityUpdateUrl,
239 2 : headers: {'Authorization': '$token'},
240 1 : data: {
241 1 : 'foreign_id': update.foreignId,
242 2 : 'time': update.time!.toIso8601String(),
243 4 : 'added_targets': add.map((it) => it.toString()).toList(),
244 4 : 'removed_targets': remove.map((it) => it.toString()).toList(),
245 2 : 'new_targets': replace.map((it) => it.toString()).toList(),
246 : },
247 : );
248 : }
249 : }
|