rettiwt-api 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +2 -0
- package/.gitattributes +3 -0
- package/Dockerfile +9 -0
- package/README.md +38 -0
- package/dist/config/env.d.ts +5 -0
- package/dist/config/env.js +9 -0
- package/dist/config/env.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/models/graphql/Global.d.ts +4 -0
- package/dist/models/graphql/Global.js +13 -0
- package/dist/models/graphql/Global.js.map +1 -0
- package/dist/models/graphql/TweetTypes.d.ts +6 -0
- package/dist/models/graphql/TweetTypes.js +156 -0
- package/dist/models/graphql/TweetTypes.js.map +1 -0
- package/dist/models/graphql/UserTypes.d.ts +3 -0
- package/dist/models/graphql/UserTypes.js +139 -0
- package/dist/models/graphql/UserTypes.js.map +1 -0
- package/dist/queries/RootQuery.d.ts +4 -0
- package/dist/queries/RootQuery.js +59 -0
- package/dist/queries/RootQuery.js.map +1 -0
- package/dist/resolvers/ResolverBase.d.ts +5 -0
- package/dist/resolvers/ResolverBase.js +11 -0
- package/dist/resolvers/ResolverBase.js.map +1 -0
- package/dist/resolvers/TweetResolver.d.ts +54 -0
- package/dist/resolvers/TweetResolver.js +328 -0
- package/dist/resolvers/TweetResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts +47 -0
- package/dist/resolvers/UserResolver.js +302 -0
- package/dist/resolvers/UserResolver.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +75 -0
- package/dist/server.js.map +1 -0
- package/dist/services/AuthService.d.ts +17 -0
- package/dist/services/AuthService.js +103 -0
- package/dist/services/AuthService.js.map +1 -0
- package/dist/services/CacheService.d.ts +25 -0
- package/dist/services/CacheService.js +141 -0
- package/dist/services/CacheService.js.map +1 -0
- package/dist/services/FetcherService.d.ts +30 -0
- package/dist/services/FetcherService.js +171 -0
- package/dist/services/FetcherService.js.map +1 -0
- package/dist/services/data/TweetService.d.ts +43 -0
- package/dist/services/data/TweetService.js +229 -0
- package/dist/services/data/TweetService.js.map +1 -0
- package/dist/services/data/UserAccountService.d.ts +49 -0
- package/dist/services/data/UserAccountService.js +250 -0
- package/dist/services/data/UserAccountService.js.map +1 -0
- package/dist/services/helper/Deserializers.d.ts +19 -0
- package/dist/services/helper/Deserializers.js +115 -0
- package/dist/services/helper/Deserializers.js.map +1 -0
- package/dist/services/helper/Extractors.d.ts +101 -0
- package/dist/services/helper/Extractors.js +409 -0
- package/dist/services/helper/Extractors.js.map +1 -0
- package/dist/services/helper/Headers.d.ts +9 -0
- package/dist/services/helper/Headers.js +47 -0
- package/dist/services/helper/Headers.js.map +1 -0
- package/dist/services/helper/Parser.d.ts +28 -0
- package/dist/services/helper/Parser.js +104 -0
- package/dist/services/helper/Parser.js.map +1 -0
- package/dist/services/helper/Urls.d.ts +74 -0
- package/dist/services/helper/Urls.js +114 -0
- package/dist/services/helper/Urls.js.map +1 -0
- package/dist/test/Test.js +2 -0
- package/dist/test/Test.js.map +1 -0
- package/dist/types/Authentication.d.ts +15 -0
- package/dist/types/Authentication.js +5 -0
- package/dist/types/Authentication.js.map +1 -0
- package/dist/types/HTTP.d.ts +22 -0
- package/dist/types/HTTP.js +30 -0
- package/dist/types/HTTP.js.map +1 -0
- package/dist/types/Service.d.ts +27 -0
- package/dist/types/Service.js +19 -0
- package/dist/types/Service.js.map +1 -0
- package/dist/types/Tweet.d.ts +40 -0
- package/dist/types/Tweet.js +5 -0
- package/dist/types/Tweet.js.map +1 -0
- package/dist/types/UserAccount.d.ts +19 -0
- package/dist/types/UserAccount.js +4 -0
- package/dist/types/UserAccount.js.map +1 -0
- package/dist/types/graphql/Errors.d.ts +15 -0
- package/dist/types/graphql/Errors.js +23 -0
- package/dist/types/graphql/Errors.js.map +1 -0
- package/dist/types/raw/auth/Cookie.d.ts +16 -0
- package/dist/types/raw/auth/Cookie.js +3 -0
- package/dist/types/raw/auth/Cookie.js.map +1 -0
- package/dist/types/raw/tweet/Favouriters.d.ts +164 -0
- package/dist/types/raw/tweet/Favouriters.js +3 -0
- package/dist/types/raw/tweet/Favouriters.js.map +1 -0
- package/dist/types/raw/tweet/Retweeters.d.ts +171 -0
- package/dist/types/raw/tweet/Retweeters.js +3 -0
- package/dist/types/raw/tweet/Retweeters.js.map +1 -0
- package/dist/types/raw/tweet/Tweet.d.ts +746 -0
- package/dist/types/raw/tweet/Tweet.js +3 -0
- package/dist/types/raw/tweet/Tweet.js.map +1 -0
- package/dist/types/raw/tweet/Tweets.d.ts +386 -0
- package/dist/types/raw/tweet/Tweets.js +3 -0
- package/dist/types/raw/tweet/Tweets.js.map +1 -0
- package/dist/types/raw/user/Followers.d.ts +176 -0
- package/dist/types/raw/user/Followers.js +3 -0
- package/dist/types/raw/user/Followers.js.map +1 -0
- package/dist/types/raw/user/Following.d.ts +176 -0
- package/dist/types/raw/user/Following.js +3 -0
- package/dist/types/raw/user/Following.js.map +1 -0
- package/dist/types/raw/user/Likes.d.ts +1059 -0
- package/dist/types/raw/user/Likes.js +3 -0
- package/dist/types/raw/user/Likes.js.map +1 -0
- package/dist/types/raw/user/Tweets.d.ts +2428 -0
- package/dist/types/raw/user/Tweets.js +3 -0
- package/dist/types/raw/user/Tweets.js.map +1 -0
- package/dist/types/raw/user/User.d.ts +117 -0
- package/dist/types/raw/user/User.js +3 -0
- package/dist/types/raw/user/User.js.map +1 -0
- package/dist/types/raw/user/Users.d.ts +120 -0
- package/dist/types/raw/user/Users.js +3 -0
- package/dist/types/raw/user/Users.js.map +1 -0
- package/environment.d.ts +11 -0
- package/package.json +40 -0
- package/src/config/env.ts +5 -0
- package/src/index.ts +19 -0
- package/src/models/graphql/Global.ts +10 -0
- package/src/models/graphql/TweetTypes.ts +154 -0
- package/src/models/graphql/UserTypes.ts +137 -0
- package/src/queries/RootQuery.ts +56 -0
- package/src/resolvers/ResolverBase.ts +12 -0
- package/src/resolvers/TweetResolver.ts +257 -0
- package/src/resolvers/UserResolver.ts +239 -0
- package/src/server.ts +36 -0
- package/src/services/AuthService.ts +58 -0
- package/src/services/CacheService.ts +70 -0
- package/src/services/FetcherService.ts +84 -0
- package/src/services/data/TweetService.ts +163 -0
- package/src/services/data/UserAccountService.ts +187 -0
- package/src/services/helper/Deserializers.ts +95 -0
- package/src/services/helper/Extractors.ts +455 -0
- package/src/services/helper/Headers.ts +45 -0
- package/src/services/helper/Parser.ts +108 -0
- package/src/services/helper/Urls.ts +109 -0
- package/src/types/Authentication.ts +16 -0
- package/src/types/HTTP.ts +23 -0
- package/src/types/Service.ts +36 -0
- package/src/types/Tweet.ts +44 -0
- package/src/types/UserAccount.ts +21 -0
- package/src/types/graphql/Errors.ts +16 -0
- package/src/types/raw/auth/Cookie.ts +16 -0
- package/src/types/raw/tweet/Favouriters.ts +193 -0
- package/src/types/raw/tweet/Retweeters.ts +201 -0
- package/src/types/raw/tweet/Tweet.ts +882 -0
- package/src/types/raw/tweet/Tweets.ts +444 -0
- package/src/types/raw/user/Followers.ts +208 -0
- package/src/types/raw/user/Following.ts +208 -0
- package/src/types/raw/user/Likes.ts +1247 -0
- package/src/types/raw/user/Tweets.ts +2847 -0
- package/src/types/raw/user/User.ts +135 -0
- package/tsconfig.json +95 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// PACKAGE
|
|
2
|
+
import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql'
|
|
3
|
+
|
|
4
|
+
// TYPES
|
|
5
|
+
import { User } from '../models/graphql/UserTypes';
|
|
6
|
+
import { Tweet, TweetList } from '../models/graphql/TweetTypes';
|
|
7
|
+
import { TweetFilter } from '../types/Tweet';
|
|
8
|
+
|
|
9
|
+
// RESOLVERS
|
|
10
|
+
import UserResolver from '../resolvers/UserResolver';
|
|
11
|
+
import TweetResolver from '../resolvers/TweetResolver';
|
|
12
|
+
|
|
13
|
+
export const rootQuery = new GraphQLObjectType({
|
|
14
|
+
name: 'Root',
|
|
15
|
+
fields: {
|
|
16
|
+
test: {
|
|
17
|
+
type: GraphQLString,
|
|
18
|
+
resolve: () => "GraphQL Works!"
|
|
19
|
+
},
|
|
20
|
+
User: {
|
|
21
|
+
type: User,
|
|
22
|
+
description: "Returns the details of the twitter user with given user name",
|
|
23
|
+
args: {
|
|
24
|
+
userName: { type: GraphQLString },
|
|
25
|
+
id: { type: GraphQLString }
|
|
26
|
+
},
|
|
27
|
+
resolve: (parent, args, context) => new UserResolver(context).resolveUserDetails(args.userName, args.id)
|
|
28
|
+
},
|
|
29
|
+
Tweet: {
|
|
30
|
+
type: Tweet,
|
|
31
|
+
description: "Returns a single tweet given it's id",
|
|
32
|
+
args: {
|
|
33
|
+
id: { type: GraphQLString }
|
|
34
|
+
},
|
|
35
|
+
resolve: (parent, args, context) => new TweetResolver(context).resolveTweet(args.id)
|
|
36
|
+
},
|
|
37
|
+
Tweets: {
|
|
38
|
+
type: TweetList,
|
|
39
|
+
description: "Returns the list of tweets matching the given criteria",
|
|
40
|
+
args: {
|
|
41
|
+
fromUsers: { type: new GraphQLList(GraphQLString) },
|
|
42
|
+
toUsers: { type: new GraphQLList(GraphQLString) },
|
|
43
|
+
mentions: { type: new GraphQLList(GraphQLString) },
|
|
44
|
+
hashtags: { type: new GraphQLList(GraphQLString) },
|
|
45
|
+
words: { type: new GraphQLList(GraphQLString) },
|
|
46
|
+
startDate: { type: GraphQLString },
|
|
47
|
+
endDate: { type: GraphQLString },
|
|
48
|
+
quoted: { type: GraphQLString },
|
|
49
|
+
links: { type: GraphQLBoolean, defaultValue: false },
|
|
50
|
+
count: { type: GraphQLInt, defaultValue: 20 },
|
|
51
|
+
cursor: { type: GraphQLString, defaultValue: '' }
|
|
52
|
+
},
|
|
53
|
+
resolve: (parent, args, context) => new TweetResolver(context).resolveTweets(args as TweetFilter, args.count, args.cursor)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// TYPES
|
|
2
|
+
import { DataContext } from '../types/Service';
|
|
3
|
+
|
|
4
|
+
export default class ResolverBase {
|
|
5
|
+
// MEMBER DATA
|
|
6
|
+
protected context: DataContext; // To store the data context
|
|
7
|
+
|
|
8
|
+
// MEMBER METHODS
|
|
9
|
+
constructor(context: DataContext) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// RESOLVERS
|
|
2
|
+
import ResolverBase from './ResolverBase';
|
|
3
|
+
|
|
4
|
+
// TYPES
|
|
5
|
+
import { TweetFilter } from '../types/Tweet';
|
|
6
|
+
import { Cursor, DataContext } from '../types/Service';
|
|
7
|
+
|
|
8
|
+
// HELPERS
|
|
9
|
+
import { ValidationErrors } from '../types/graphql/Errors';
|
|
10
|
+
|
|
11
|
+
export default class TweetResolver extends ResolverBase {
|
|
12
|
+
// MEMBER METHODS
|
|
13
|
+
constructor(context: DataContext) {
|
|
14
|
+
super(context);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @returns The details of the tweet with the given id
|
|
19
|
+
* @param id The id of the tweet which is to be fetched
|
|
20
|
+
*/
|
|
21
|
+
async resolveTweet(id: string): Promise<any> {
|
|
22
|
+
// Getting the data
|
|
23
|
+
let res = await this.context.tweets.getTweetById(id);
|
|
24
|
+
|
|
25
|
+
// Evaluating response
|
|
26
|
+
return res;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @returns The list of tweets matching the given filter
|
|
31
|
+
* @param filter The filter to be used for fetching matching tweets
|
|
32
|
+
* @param count The number of tweets to fetch
|
|
33
|
+
* @param cursor The cursor to the batch of tweets to fetch
|
|
34
|
+
*/
|
|
35
|
+
async resolveTweets(filter: TweetFilter, count: number, cursor: string): Promise<any[]> {
|
|
36
|
+
let tweets: any[] = []; // To store the list of tweets
|
|
37
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
38
|
+
let total: number = 0; // To store the total number of tweets fetched
|
|
39
|
+
let batchSize: number = 20; // To store the batchsize to use
|
|
40
|
+
|
|
41
|
+
// Checking if the given tweet filter is valid or not
|
|
42
|
+
if (!(filter.fromUsers || filter.toUsers || filter.words || filter.hashtags || filter.mentions || filter.quoted)) {
|
|
43
|
+
throw new Error(ValidationErrors.InvalidTweetFilter);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If required count less than batch size, setting batch size to required count
|
|
47
|
+
batchSize = (count < batchSize) ? count : batchSize;
|
|
48
|
+
|
|
49
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
50
|
+
while (total < count) {
|
|
51
|
+
// If this is the last batch, change batch size to number of remaining tweets
|
|
52
|
+
batchSize = ((count - total) < batchSize) ? (count - total) : batchSize;
|
|
53
|
+
|
|
54
|
+
// Getting the data
|
|
55
|
+
const res = await this.context.tweets.getTweets(filter, count, next.value);
|
|
56
|
+
|
|
57
|
+
// If data is available
|
|
58
|
+
if (res.list.length) {
|
|
59
|
+
// Adding fetched tweets to list of tweets
|
|
60
|
+
tweets = tweets.concat(res.list);
|
|
61
|
+
|
|
62
|
+
// Updating total tweets fetched
|
|
63
|
+
total = tweets.length;
|
|
64
|
+
|
|
65
|
+
// Getting cursor to next batch
|
|
66
|
+
next = res.next;
|
|
67
|
+
}
|
|
68
|
+
// If no more data is available
|
|
69
|
+
else {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Adding the cursor to the end of list of data
|
|
75
|
+
tweets.push(next);
|
|
76
|
+
|
|
77
|
+
return tweets;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @returns The list of quotes of the given tweet
|
|
82
|
+
* @param id The id of the tweet whose quotes are to be fetched
|
|
83
|
+
* @param count The number of quotes to be fetched
|
|
84
|
+
* @param all Whether to fetch all quotes or not
|
|
85
|
+
* @param cursor The cursor to the batch of tweet quotes to fetch
|
|
86
|
+
* @param quoteCount The total number of quotes of the given tweet
|
|
87
|
+
*/
|
|
88
|
+
async resolveTweetQuotes(id: string, count: number, all: boolean, cursor: string, quoteCount: number): Promise<any[]> {
|
|
89
|
+
let quotes: any[] = []; // To store the list of quotes
|
|
90
|
+
|
|
91
|
+
// If all tweets are to be fetched
|
|
92
|
+
count = (all || count > quoteCount) ? quoteCount : count;
|
|
93
|
+
|
|
94
|
+
// Preparing the filter to use
|
|
95
|
+
let filter = {
|
|
96
|
+
words: [],
|
|
97
|
+
hashtags: [],
|
|
98
|
+
fromUsers: [],
|
|
99
|
+
toUsers: [],
|
|
100
|
+
mentions: [],
|
|
101
|
+
startDate: '',
|
|
102
|
+
endDate: '',
|
|
103
|
+
quoted: id
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Fetching the quotes using resolveTweets method
|
|
107
|
+
quotes = await this.resolveTweets(filter, count, cursor);
|
|
108
|
+
|
|
109
|
+
return quotes;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @returns The list of likers of the given tweet
|
|
114
|
+
* @param id The id of the tweet whose likers are to be fetched
|
|
115
|
+
* @param count The total number of likers to fetch
|
|
116
|
+
* @param all Whether to fetch all the likers of the tweet
|
|
117
|
+
* @param cursor The cursor to the batch of likers to fetch
|
|
118
|
+
* @param likesCount The total number of like of the tweet
|
|
119
|
+
*/
|
|
120
|
+
async resolveTweetLikers(id: string, count: number, all: boolean, cursor: string, likesCount: number): Promise<any[]> {
|
|
121
|
+
let likers: any[] = []; // To store the list of likers
|
|
122
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
123
|
+
let total: number = 0; // To store the total number of likers fetched
|
|
124
|
+
let batchSize: number = 20; // To store the batchsize to use
|
|
125
|
+
|
|
126
|
+
// If all likers are to be fetched
|
|
127
|
+
count = (all || count > likesCount) ? likesCount : count;
|
|
128
|
+
|
|
129
|
+
// If required count less than batch size, setting batch size to required count
|
|
130
|
+
batchSize = (count < batchSize) ? count : batchSize;
|
|
131
|
+
|
|
132
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
133
|
+
while (total < count) {
|
|
134
|
+
// If this is the last batch, change batch size to number of remaining likers
|
|
135
|
+
batchSize = ((count - total) < batchSize) ? (count - total) : batchSize;
|
|
136
|
+
|
|
137
|
+
// Getting the data
|
|
138
|
+
const res = await this.context.tweets.getTweetLikers(id, count, next.value);
|
|
139
|
+
|
|
140
|
+
// If data is available
|
|
141
|
+
if (res.list.length) {
|
|
142
|
+
// Adding fetched likers to list of likers
|
|
143
|
+
likers = likers.concat(res.list);
|
|
144
|
+
|
|
145
|
+
// Updating total likers fetched
|
|
146
|
+
total = likers.length;
|
|
147
|
+
|
|
148
|
+
// Getting cursor to next batch
|
|
149
|
+
next = res.next;
|
|
150
|
+
}
|
|
151
|
+
// If no more data is available
|
|
152
|
+
else {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Adding the cursor to the end of list of data
|
|
158
|
+
likers.push(next);
|
|
159
|
+
|
|
160
|
+
return likers;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @returns The list of retweeters of the given tweet
|
|
165
|
+
* @param id The id of the tweet whose retweeters are to be fetched
|
|
166
|
+
* @param count The total number of retweeters to fetch
|
|
167
|
+
* @param all Whether to fetch all retweeters
|
|
168
|
+
* @param cursor The cursor to the batch of retweeters to fetch
|
|
169
|
+
* @param retweetsCount The total number of retweets of the
|
|
170
|
+
*/
|
|
171
|
+
async resolveTweetRetweeters(id: string, count: number, all: boolean, cursor: string, retweetsCount: number): Promise<any[]> {
|
|
172
|
+
let retweeters: any[] = []; // To store the list of retweeters
|
|
173
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
174
|
+
let total: number = 0; // To store the total number of retweeters fetched
|
|
175
|
+
let batchSize: number = 20; // To store the batchsize to use
|
|
176
|
+
|
|
177
|
+
// If all retweeters are to be fetched
|
|
178
|
+
count = (all || count > retweetsCount) ? retweetsCount : count;
|
|
179
|
+
|
|
180
|
+
// If required count less than batch size, setting batch size to required count
|
|
181
|
+
batchSize = (count < batchSize) ? count : batchSize;
|
|
182
|
+
|
|
183
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
184
|
+
while (total < count) {
|
|
185
|
+
// If this is the last batch, change batch size to number of remaining retweeters
|
|
186
|
+
batchSize = ((count - total) < batchSize) ? (count - total) : batchSize;
|
|
187
|
+
|
|
188
|
+
// Getting the data
|
|
189
|
+
const res = await this.context.tweets.getTweetRetweeters(id, count, next.value);
|
|
190
|
+
|
|
191
|
+
// If data is available
|
|
192
|
+
if (res.list.length) {
|
|
193
|
+
// Adding fetched retweeters to list of retweeters
|
|
194
|
+
retweeters = retweeters.concat(res.list);
|
|
195
|
+
|
|
196
|
+
// Updating total retweeters fetched
|
|
197
|
+
total = retweeters.length;
|
|
198
|
+
|
|
199
|
+
// Getting cursor to next batch
|
|
200
|
+
next = res.next;
|
|
201
|
+
}
|
|
202
|
+
// If no more data is available
|
|
203
|
+
else {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Adding the cursor to the end of list of data
|
|
209
|
+
retweeters.push(next);
|
|
210
|
+
|
|
211
|
+
return retweeters;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @returns The list of replies of the given tweet
|
|
216
|
+
* @param id The id of the tweet whose replies are to be fetched
|
|
217
|
+
* @param count The total number of replies to fetch
|
|
218
|
+
* @param all Whether to fetch list of all replies
|
|
219
|
+
* @param cursor The cursor to the batch of replies to fetch
|
|
220
|
+
* @param repliesCount The total number of replies to the target tweet
|
|
221
|
+
*/
|
|
222
|
+
async resolveTweetReplies(id: string, count: number, all: boolean, cursor: string, repliesCount: number): Promise<any[]> {
|
|
223
|
+
let replies: any[] = []; // To store the list of replies
|
|
224
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
225
|
+
let total: number = 0; // To store the total number of replies fetched
|
|
226
|
+
|
|
227
|
+
// If all replies are to be fetched
|
|
228
|
+
count = (all || count > repliesCount) ? repliesCount : count;
|
|
229
|
+
|
|
230
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
231
|
+
while (total < count) {
|
|
232
|
+
// Getting the data
|
|
233
|
+
const res = await this.context.tweets.getTweetReplies(id, next.value);
|
|
234
|
+
|
|
235
|
+
// If data is available
|
|
236
|
+
if (res.list.length) {
|
|
237
|
+
// Adding fetched replies to list of replies
|
|
238
|
+
replies = replies.concat(res.list);
|
|
239
|
+
|
|
240
|
+
// Updating total replies fetched
|
|
241
|
+
total = replies.length;
|
|
242
|
+
|
|
243
|
+
// Getting cursor to next batch
|
|
244
|
+
next = res.next;
|
|
245
|
+
}
|
|
246
|
+
// If no more data is available
|
|
247
|
+
else {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Adding the cursor to the end of list of data
|
|
253
|
+
replies.push(next);
|
|
254
|
+
|
|
255
|
+
return replies;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// RESOLVERS
|
|
2
|
+
import ResolverBase from './ResolverBase';
|
|
3
|
+
|
|
4
|
+
// TYPES
|
|
5
|
+
import { Cursor, DataContext } from '../types/Service';
|
|
6
|
+
|
|
7
|
+
// HELPERS
|
|
8
|
+
import { ValidationErrors } from '../types/graphql/Errors';
|
|
9
|
+
|
|
10
|
+
export default class UserResolver extends ResolverBase {
|
|
11
|
+
// MEMBER METHODS
|
|
12
|
+
constructor(context: DataContext) {
|
|
13
|
+
super(context);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @returns The details of the target twitter user
|
|
18
|
+
* @param userName The user name of the target twitter user
|
|
19
|
+
* @param id The id of the target twitter user
|
|
20
|
+
*/
|
|
21
|
+
async resolveUserDetails(userName: string, id: string): Promise<any> {
|
|
22
|
+
// If user name is supplied
|
|
23
|
+
if (userName) {
|
|
24
|
+
return await this.context.users.getUserAccountDetails(userName);
|
|
25
|
+
}
|
|
26
|
+
// If id is supplied
|
|
27
|
+
else if (id) {
|
|
28
|
+
return await this.context.users.getUserAccountDetailsById(id);
|
|
29
|
+
}
|
|
30
|
+
// If neither userName nor id is supplied
|
|
31
|
+
else {
|
|
32
|
+
throw new Error(ValidationErrors.NoUserIdentification);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @returns The list of tweets liked by the given user
|
|
38
|
+
* @param id The id of the user whose likes are to be fetched
|
|
39
|
+
* @param count The number of likes to fetch
|
|
40
|
+
* @param all Whether to fetch list of all tweets liked by user
|
|
41
|
+
* @param cursor The cursor to the batch of likes to fetch
|
|
42
|
+
* @param favouritesCount The total number of tweets liked by target user
|
|
43
|
+
*/
|
|
44
|
+
async resolveUserLikes(id: string, count: number, all: boolean, cursor: string, favouritesCount: number): Promise<any> {
|
|
45
|
+
let likes: any[] = []; // To store the list of liked tweets
|
|
46
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
47
|
+
let total: number = 0; // To store the total number of liked twets fetched
|
|
48
|
+
let batchSize: number = 20; // To store the batchsize to use
|
|
49
|
+
|
|
50
|
+
// If all liked tweets are to be fetched
|
|
51
|
+
count = all ? favouritesCount : count;
|
|
52
|
+
|
|
53
|
+
// If required count less than batch size, setting batch size to required count
|
|
54
|
+
batchSize = (count < batchSize) ? count : batchSize;
|
|
55
|
+
|
|
56
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
57
|
+
while (total < count) {
|
|
58
|
+
// If this is the last batch, change batch size to number of remaining tweets
|
|
59
|
+
batchSize = ((count - total) < batchSize) ? (count - total) : batchSize;
|
|
60
|
+
|
|
61
|
+
// Getting the data
|
|
62
|
+
const res = await this.context.users.getUserLikes(id, count, next.value);
|
|
63
|
+
|
|
64
|
+
// If data is available
|
|
65
|
+
if (res.list.length) {
|
|
66
|
+
// Adding fetched tweets to list of tweets
|
|
67
|
+
likes = likes.concat(res.list);
|
|
68
|
+
|
|
69
|
+
// Updating total tweets fetched
|
|
70
|
+
total = likes.length;
|
|
71
|
+
|
|
72
|
+
// Getting cursor to next batch
|
|
73
|
+
next = res.next;
|
|
74
|
+
}
|
|
75
|
+
// If no more data is available
|
|
76
|
+
else {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Adding the cursor to the end of list of data
|
|
82
|
+
likes.push(next);
|
|
83
|
+
|
|
84
|
+
return likes;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @returns The list of followers of the given twiiter user
|
|
89
|
+
* @param id The id of the user whose followers are to be fetched
|
|
90
|
+
* @param count The number of followers to fetch
|
|
91
|
+
* @param all Whether to fetch all followers list
|
|
92
|
+
* @param cursor The cursor to the batch of followers to fetch
|
|
93
|
+
* @param followerCount The total number of followers of the target user
|
|
94
|
+
*/
|
|
95
|
+
async resolveUserFollowers(id: string, count: number, all: boolean, cursor: string, followersCount: number): Promise<any> {
|
|
96
|
+
let followers: any[] = []; // To store the list of followers
|
|
97
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
98
|
+
let total: number = 0; // To store the total number of followers fetched
|
|
99
|
+
let batchSize: number = 20; // To store the batchsize to use
|
|
100
|
+
|
|
101
|
+
// If all followers are to be fetched
|
|
102
|
+
count = (all || count > followersCount) ? followersCount : count;
|
|
103
|
+
|
|
104
|
+
// If required count less than batch size, setting batch size to required count
|
|
105
|
+
batchSize = (count < batchSize) ? count : batchSize;
|
|
106
|
+
|
|
107
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
108
|
+
while (total < count) {
|
|
109
|
+
// If this is the last batch, change batch size to number of remaining followers
|
|
110
|
+
batchSize = ((count - total) < batchSize) ? (count - total) : batchSize;
|
|
111
|
+
|
|
112
|
+
// Getting the data
|
|
113
|
+
const res = await this.context.users.getUserFollowers(id, count, next.value);
|
|
114
|
+
|
|
115
|
+
// If data is available
|
|
116
|
+
if (res.list.length) {
|
|
117
|
+
// Adding fetched followers to list of followers
|
|
118
|
+
followers = followers.concat(res.list);
|
|
119
|
+
|
|
120
|
+
// Updating total followers fetched
|
|
121
|
+
total = followers.length;
|
|
122
|
+
|
|
123
|
+
// Getting cursor to next batch
|
|
124
|
+
next = res.next;
|
|
125
|
+
}
|
|
126
|
+
// If no more data is available
|
|
127
|
+
else {
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Adding the cursor to the end of list of data
|
|
133
|
+
followers.push(next);
|
|
134
|
+
|
|
135
|
+
return followers;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @returns The list of following of the given twiiter user
|
|
140
|
+
* @param id The id of the user whose followings are to be fetched
|
|
141
|
+
* @param count The number of following to fetch
|
|
142
|
+
* @param all Whether to fetch list of all followings
|
|
143
|
+
* @param cursor The cursor to the batch of followings to fetch
|
|
144
|
+
* @param followingsCount The total number of followings of the target user
|
|
145
|
+
*/
|
|
146
|
+
async resolveUserFollowing(id: string, count: number, all: boolean, cursor: string, followingsCount: number): Promise<any> {
|
|
147
|
+
let following: any[] = []; // To store the list of following
|
|
148
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
149
|
+
let total: number = 0; // To store the total number of following fetched
|
|
150
|
+
let batchSize: number = 20; // To store the batchsize to use
|
|
151
|
+
|
|
152
|
+
// If all followings are to be fetched
|
|
153
|
+
count = (all || count > followingsCount) ? followingsCount : count;
|
|
154
|
+
|
|
155
|
+
// If required count less than batch size, setting batch size to required count
|
|
156
|
+
batchSize = (count < batchSize) ? count : batchSize;
|
|
157
|
+
|
|
158
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
159
|
+
while (total < count) {
|
|
160
|
+
// If this is the last batch, change batch size to number of remaining following
|
|
161
|
+
batchSize = ((count - total) < batchSize) ? (count - total) : batchSize;
|
|
162
|
+
|
|
163
|
+
// Getting the data
|
|
164
|
+
const res = await this.context.users.getUserFollowing(id, count, next.value);
|
|
165
|
+
|
|
166
|
+
// If data is available
|
|
167
|
+
if (res.list.length) {
|
|
168
|
+
// Adding fetched following to list of following
|
|
169
|
+
following = following.concat(res.list);
|
|
170
|
+
|
|
171
|
+
// Updating total following fetched
|
|
172
|
+
total = following.length;
|
|
173
|
+
|
|
174
|
+
// Getting cursor to next batch
|
|
175
|
+
next = res.next;
|
|
176
|
+
}
|
|
177
|
+
// If no more data is available
|
|
178
|
+
else {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Adding the cursor to the end of list of data
|
|
184
|
+
following.push(next);
|
|
185
|
+
|
|
186
|
+
return following;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @returns The list of tweets made by the given user
|
|
191
|
+
* @param id The id of the user whose tweets are to be fetched
|
|
192
|
+
* @param count The number of tweets to fetch
|
|
193
|
+
* @param all Whether to fetch list of all tweets made by user
|
|
194
|
+
* @param cursor The cursor to the batch of tweets to fetch
|
|
195
|
+
* @param statusesCount The total number of tweets made by target user
|
|
196
|
+
*/
|
|
197
|
+
async resolveUserTweets(id: string, count: number, all: boolean, cursor: string, statusesCount: number): Promise<any> {
|
|
198
|
+
let tweets: any[] = []; // To store the list of tweets
|
|
199
|
+
let next: Cursor = new Cursor(cursor); // To store cursor to next batch
|
|
200
|
+
let total: number = 0; // To store the total number of tweets fetched
|
|
201
|
+
let batchSize: number = 20; // To store the batchsize to use
|
|
202
|
+
|
|
203
|
+
// If all tweets are to be fetched
|
|
204
|
+
count = all ? statusesCount : count;
|
|
205
|
+
|
|
206
|
+
// If required count less than batch size, setting batch size to required count
|
|
207
|
+
batchSize = (count < batchSize) ? count : batchSize;
|
|
208
|
+
|
|
209
|
+
// Repeatedly fetching data as long as total data fetched is less than requried
|
|
210
|
+
while (total < count) {
|
|
211
|
+
// If this is the last batch, change batch size to number of remaining tweets
|
|
212
|
+
batchSize = ((count - total) < batchSize) ? (count - total) : batchSize;
|
|
213
|
+
|
|
214
|
+
// Getting the data
|
|
215
|
+
const res = await this.context.users.getUserTweets(id, count, next.value);
|
|
216
|
+
|
|
217
|
+
// If data is available
|
|
218
|
+
if (res.list.length) {
|
|
219
|
+
// Adding fetched tweets to list of tweets
|
|
220
|
+
tweets = tweets.concat(res.list);
|
|
221
|
+
|
|
222
|
+
// Updating total tweets fetched
|
|
223
|
+
total = tweets.length;
|
|
224
|
+
|
|
225
|
+
// Getting cursor to next batch
|
|
226
|
+
next = res.next;
|
|
227
|
+
}
|
|
228
|
+
// If no more data is available
|
|
229
|
+
else {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Adding the cursor to the end of list of data
|
|
235
|
+
tweets.push(next);
|
|
236
|
+
|
|
237
|
+
return tweets;
|
|
238
|
+
}
|
|
239
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// PACKAGE
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { graphqlHTTP } from 'express-graphql';
|
|
4
|
+
import { GraphQLSchema } from 'graphql';
|
|
5
|
+
|
|
6
|
+
// Services
|
|
7
|
+
import { UserAccountService } from './services/data/UserAccountService';
|
|
8
|
+
import { TweetService } from './services/data/TweetService';
|
|
9
|
+
import { AuthService } from './services/AuthService';
|
|
10
|
+
|
|
11
|
+
// SCHEMA
|
|
12
|
+
import { rootQuery } from './queries/RootQuery';
|
|
13
|
+
|
|
14
|
+
// CONFIGS
|
|
15
|
+
import { config } from './config/env';
|
|
16
|
+
|
|
17
|
+
// Initialising express instance
|
|
18
|
+
const app = express();
|
|
19
|
+
|
|
20
|
+
// Setting up graphql endpoint
|
|
21
|
+
app.use('/graphql', graphqlHTTP(req => ({
|
|
22
|
+
schema: new GraphQLSchema({
|
|
23
|
+
query: rootQuery
|
|
24
|
+
}),
|
|
25
|
+
context: {
|
|
26
|
+
users: new UserAccountService(new AuthService(req.headers.cookie as string)),
|
|
27
|
+
tweets: new TweetService(new AuthService(req.headers.cookie as string))
|
|
28
|
+
},
|
|
29
|
+
// If app is running in development environment, enable graphiql
|
|
30
|
+
graphiql: config.is_development
|
|
31
|
+
})));
|
|
32
|
+
|
|
33
|
+
// Setting up express server
|
|
34
|
+
app.listen(config.port, async () => {
|
|
35
|
+
console.log(`Listening on port ${config.port}`);
|
|
36
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// PACKAGE
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
// URLS
|
|
5
|
+
import { guestTokenUrl } from './helper/Urls';
|
|
6
|
+
|
|
7
|
+
// TYPES
|
|
8
|
+
import { GuestCredentials, AuthCredentials } from '../types/Authentication';
|
|
9
|
+
|
|
10
|
+
// CONFIGS
|
|
11
|
+
import { config } from '../config/env';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @summary Handles authentication of http requests and other authentication related tasks
|
|
15
|
+
*/
|
|
16
|
+
export class AuthService {
|
|
17
|
+
// MEMBER DATA
|
|
18
|
+
private authToken: string; // To store the common auth token
|
|
19
|
+
private credentials: AuthCredentials; // To store the current authentication credentials
|
|
20
|
+
|
|
21
|
+
// MEMBER METHODS
|
|
22
|
+
constructor(cookie: string = '') {
|
|
23
|
+
// Reading the auth token from the config, since it's always the same
|
|
24
|
+
this.authToken = config.twitter_auth_token;
|
|
25
|
+
|
|
26
|
+
// Setting up the authenticated credentials
|
|
27
|
+
/**
|
|
28
|
+
* The following regex pattern is used to extract the csrfToken from the cookie string.
|
|
29
|
+
* This is done by matching any string between the characters 'ct0=' and nearest enclosing ';'.
|
|
30
|
+
* (?<=pattern) starts matching after the given pattern.
|
|
31
|
+
* (?=pattern) stops matching just before the pattern
|
|
32
|
+
*/
|
|
33
|
+
this.credentials = { authToken: this.authToken, csrfToken: cookie.match(/(?<=ct0=).+?(?=;)/) + '', cookie: cookie};
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @returns The current authentication credentials. A different credential is returned each time this is invoked
|
|
39
|
+
*/
|
|
40
|
+
async getAuthCredentials(): Promise<AuthCredentials> {
|
|
41
|
+
return this.credentials;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @returns The guest credentials fetched from twitter
|
|
46
|
+
*/
|
|
47
|
+
async getGuestCredentials(): Promise<GuestCredentials> {
|
|
48
|
+
// Getting the guest credentials from twitter
|
|
49
|
+
return await axios.post<{ guest_token: string }>(guestTokenUrl(), null, {
|
|
50
|
+
headers: {
|
|
51
|
+
'Authorization': this.authToken
|
|
52
|
+
}
|
|
53
|
+
}).then(res => ({
|
|
54
|
+
authToken: this.authToken,
|
|
55
|
+
guestToken: res.data.guest_token
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
}
|