Skip to content

Commit 5a534ab

Browse files
author
github-actions
committed
chore: add data privacy endpoints
1 parent 520e284 commit 5a534ab

File tree

3 files changed

+141
-1
lines changed

3 files changed

+141
-1
lines changed

src/client.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ import { StreamUser } from './user';
1616
import { JWTScopeToken, JWTUserSessionToken } from './signing';
1717
import { FeedError, StreamApiError, SiteError } from './errors';
1818
import utils from './utils';
19+
import DataPrivacy, { ExportIDsResponse, ActivityToDelete } from './data_privacy';
20+
1921
import BatchOperations, {
2022
AddUsersResponse,
2123
FollowRelation,
2224
GetUsersResponse,
2325
UnfollowRelation,
2426
} from './batch_operations';
27+
2528
import createRedirectUrl from './redirect_url';
2629
import {
2730
StreamFeed,
@@ -196,6 +199,10 @@ export class StreamClient<StreamFeedGenerics extends DefaultGenerics = DefaultGe
196199
addUsers?: (this: StreamClient, users: StreamUser[], overrideExisting: boolean) => Promise<AddUsersResponse>; // eslint-disable-line no-use-before-define
197200
getUsers?: (this: StreamClient, ids: string[]) => Promise<GetUsersResponse>; // eslint-disable-line no-use-before-define
198201
deleteUsers?: (this: StreamClient, ids: string[]) => Promise<string[]>; // eslint-disable-line no-use-before-define
202+
deleteActivities?: (this: StreamClient, activities: ActivityToDelete[]) => Promise<APIResponse>; // eslint-disable-line no-use-before-define
203+
deleteReactions?: (this: StreamClient, ids: string[]) => Promise<APIResponse>; // eslint-disable-line no-use-before-define
204+
exportUserActivitiesAndReactionIDs?: (this: StreamClient, userId: string) => Promise<ExportIDsResponse>; // eslint-disable-line no-use-before-define
205+
199206
/**
200207
* Initialize a client
201208
* @link https://getstream.io/activity-feeds/docs/node/#setup
@@ -288,14 +295,17 @@ export class StreamClient<StreamFeedGenerics extends DefaultGenerics = DefaultGe
288295
this.reactions = new StreamReaction<StreamFeedGenerics>(this, this.getOrCreateToken());
289296

290297
// If we are in a node environment and batchOperations/createRedirectUrl is available add the methods to the prototype of StreamClient
291-
if (BatchOperations && !!createRedirectUrl) {
298+
if (BatchOperations && !!createRedirectUrl && DataPrivacy) {
292299
this.addToMany = BatchOperations.addToMany;
293300
this.followMany = BatchOperations.followMany;
294301
this.unfollowMany = BatchOperations.unfollowMany;
295302
this.createRedirectUrl = createRedirectUrl;
296303
this.addUsers = BatchOperations.addUsers;
297304
this.getUsers = BatchOperations.getUsers;
298305
this.deleteUsers = BatchOperations.deleteUsers;
306+
this.deleteActivities = DataPrivacy.deleteActivities;
307+
this.deleteReactions = DataPrivacy.deleteReactions;
308+
this.exportUserActivitiesAndReactionIDs = DataPrivacy.exportUserActivitiesAndReactionIDs;
299309
}
300310
}
301311

src/data_privacy.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { StreamClient, APIResponse } from './client';
2+
3+
export type ActivityToDelete = {
4+
id: string;
5+
remove_from_feeds: string[];
6+
};
7+
8+
export type ExportIDsResult = {
9+
activity_count: number;
10+
reaction_count: number;
11+
activity_ids?: string[];
12+
reaction_ids?: string[];
13+
user_id?: string;
14+
};
15+
16+
export type ExportIDsResponse = APIResponse & {
17+
export?: ExportIDsResult;
18+
};
19+
20+
export function deleteActivities(this: StreamClient, activities: ActivityToDelete[]): Promise<APIResponse> {
21+
this._throwMissingApiSecret();
22+
23+
return this.post<APIResponse>({
24+
url: 'data_privacy/delete_activities/',
25+
body: { activities },
26+
token: this.getOrCreateToken(),
27+
});
28+
}
29+
30+
export function deleteReactions(this: StreamClient, ids: string[]): Promise<APIResponse> {
31+
this._throwMissingApiSecret();
32+
33+
return this.post<APIResponse>({
34+
url: 'data_privacy/delete_reactions/',
35+
body: { ids },
36+
token: this.getOrCreateToken(),
37+
});
38+
}
39+
40+
export function exportUserActivitiesAndReactionIDs(this: StreamClient, userId: string): Promise<ExportIDsResponse> {
41+
if (!userId) {
42+
throw new Error("User ID can't be null or empty");
43+
}
44+
45+
return this.get<ExportIDsResponse>({
46+
url: `data_privacy/export_ids/${userId}`,
47+
token: this.getOrCreateToken(),
48+
});
49+
}
50+
51+
export default {
52+
deleteActivities,
53+
deleteReactions,
54+
exportUserActivitiesAndReactionIDs,
55+
};

test/integration/node/client_test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,4 +680,79 @@ describe('[INTEGRATION] Stream client (Node)', function () {
680680
});
681681
});
682682
});
683+
684+
it('delete activities', function () {
685+
const activities = [
686+
{
687+
actor: 'user:1',
688+
verb: 'tweet',
689+
object: '1',
690+
foreign_id: 'delete_gdpr_1',
691+
time: new Date(),
692+
},
693+
{
694+
actor: 'user:2',
695+
verb: 'tweet',
696+
object: '2',
697+
foreign_id: 'delete_gdpr_2',
698+
time: new Date(),
699+
},
700+
];
701+
702+
return this.user1
703+
.addActivities(activities)
704+
.then((body) => {
705+
const activitiesToDelete = body.activities.map((activity) => ({
706+
id: activity.id,
707+
remove_from_feeds: [this.user1.id],
708+
}));
709+
return this.client.deleteActivities(activitiesToDelete);
710+
// return this.client.deleteUsers(['sa']);
711+
})
712+
.then(() => this.user1.get({ limit: 2 }))
713+
.then((body) => {
714+
expect(body.results.length).to.be(0);
715+
});
716+
});
717+
718+
it('delete reactions', async function () {
719+
const activity = {
720+
actor: 'user:1',
721+
verb: 'tweet',
722+
object: '1',
723+
};
724+
725+
const activityRes = await this.user1.addActivity(activity);
726+
727+
const reaction1 = await this.client.reactions.add('like1', activityRes.id, { text: 'text' }, { userId: 'user1' });
728+
const reaction2 = await this.client.reactions.add('like2', activityRes.id, { text: 'text' }, { userId: 'user1' });
729+
await this.client.reactions.add('like3', activityRes.id, { text: 'text' }, { userId: 'user1' });
730+
731+
// delete 2 reaction
732+
await this.client.deleteReactions([reaction1.id, reaction2.id]);
733+
const resp = await this.client.reactions.filter({ activity_id: activityRes.id });
734+
expect(resp.results.length).to.be(1);
735+
});
736+
737+
it('export user data', async function () {
738+
const userId = randUserId('export');
739+
const activity = {
740+
actor: userId,
741+
verb: 'tweet',
742+
object: '1',
743+
};
744+
745+
const activityRes = await this.user1.addActivity(activity);
746+
747+
const reaction1 = await this.client.reactions.add('like1', activityRes.id, { text: 'text' }, { userId });
748+
await this.client.reactions.add('like2', activityRes.id, { text: 'text' }, { userId: 'user1' });
749+
const reaction3 = await this.client.reactions.add('like3', activityRes.id, { text: 'text' }, { userId });
750+
751+
const resp = await this.client.exportUserActivitiesAndReactionIDs(userId);
752+
expect(resp.export.activity_count).to.be(1);
753+
expect(resp.export.reaction_count).to.be(2);
754+
expect(resp.export.user_id).to.be(userId);
755+
expect(resp.export.activity_ids).to.eql([activityRes.id]);
756+
expect(resp.export.reaction_ids).to.eql([reaction1.id, reaction3.id]);
757+
});
683758
});

0 commit comments

Comments
 (0)