@rooguys/sdk 0.1.0

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.
@@ -0,0 +1,69 @@
1
+ import axios from 'axios';
2
+ import { Rooguys } from './index';
3
+
4
+ jest.mock('axios');
5
+ const mockedAxios = axios as jest.Mocked<typeof axios>;
6
+
7
+ describe('Rooguys SDK', () => {
8
+ let client: Rooguys;
9
+ const apiKey = 'test-api-key';
10
+ const mockAxiosInstance = {
11
+ get: jest.fn(),
12
+ post: jest.fn(),
13
+ defaults: { headers: { common: {} } },
14
+ interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
15
+ };
16
+
17
+ beforeEach(() => {
18
+ mockedAxios.create.mockReturnValue(mockAxiosInstance as any);
19
+ client = new Rooguys(apiKey);
20
+ jest.clearAllMocks();
21
+ });
22
+
23
+ describe('Events', () => {
24
+ it('should track an event', async () => {
25
+ const mockResponse = { data: { status: 'queued', message: 'ok' } };
26
+ mockAxiosInstance.post.mockResolvedValue(mockResponse);
27
+
28
+ const result = await client.events.track('test_event', 'user_1');
29
+
30
+ expect(mockAxiosInstance.post).toHaveBeenCalledWith(
31
+ '/event',
32
+ {
33
+ event_name: 'test_event',
34
+ user_id: 'user_1',
35
+ properties: {},
36
+ },
37
+ { params: { include_profile: undefined } }
38
+ );
39
+ expect(result).toEqual(mockResponse.data);
40
+ });
41
+ });
42
+
43
+ describe('Users', () => {
44
+ it('should get a user profile', async () => {
45
+ const mockResponse = { data: { user_id: 'user_1', points: 100 } };
46
+ mockAxiosInstance.get.mockResolvedValue(mockResponse);
47
+
48
+ const result = await client.users.get('user_1');
49
+
50
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith('/user/user_1');
51
+ expect(result).toEqual(mockResponse.data);
52
+ });
53
+ });
54
+
55
+ describe('Leaderboards', () => {
56
+ it('should get global leaderboard', async () => {
57
+ const mockResponse = { data: { rankings: [] } };
58
+ mockAxiosInstance.get.mockResolvedValue(mockResponse);
59
+
60
+ const result = await client.leaderboards.getGlobal();
61
+
62
+ expect(mockAxiosInstance.get).toHaveBeenCalledWith(
63
+ '/leaderboard',
64
+ { params: { timeframe: 'all-time', page: 1, limit: 50 } }
65
+ );
66
+ expect(result).toEqual(mockResponse.data);
67
+ });
68
+ });
69
+ });
package/src/index.ts ADDED
@@ -0,0 +1,236 @@
1
+ import axios, { AxiosInstance, AxiosError } from 'axios';
2
+ import { RooguysOptions, TrackEventResponse, UserProfile, UserBadge, UserRank, LeaderboardResult, AnswerSubmission, AhaDeclarationResult, AhaScoreResult, BadgeListResult, LevelListResult, Questionnaire, LeaderboardListResult } from './types';
3
+
4
+ export class Rooguys {
5
+ private client: AxiosInstance;
6
+
7
+ constructor(private apiKey: string, options: RooguysOptions = {}) {
8
+ this.client = axios.create({
9
+ baseURL: options.baseUrl || 'https://api.rooguys.com/v1',
10
+ timeout: options.timeout || 10000,
11
+ headers: {
12
+ 'x-api-key': this.apiKey,
13
+ 'Content-Type': 'application/json',
14
+ },
15
+ });
16
+ }
17
+
18
+ public events = {
19
+ track: async (
20
+ eventName: string,
21
+ userId: string,
22
+ properties: Record<string, any> = {},
23
+ options: { includeProfile?: boolean } = {}
24
+ ): Promise<TrackEventResponse> => {
25
+ try {
26
+ const response = await this.client.post('/event', {
27
+ event_name: eventName,
28
+ user_id: userId,
29
+ properties,
30
+ }, {
31
+ params: {
32
+ include_profile: options.includeProfile,
33
+ },
34
+ });
35
+ return response.data;
36
+ } catch (error) {
37
+ throw this.handleError(error);
38
+ }
39
+ },
40
+ };
41
+
42
+ public users = {
43
+ get: async (userId: string): Promise<UserProfile> => {
44
+ try {
45
+ const response = await this.client.get(`/user/${encodeURIComponent(userId)}`);
46
+ return response.data;
47
+ } catch (error) {
48
+ throw this.handleError(error);
49
+ }
50
+ },
51
+
52
+ getBulk: async (userIds: string[]): Promise<{ users: UserProfile[] }> => {
53
+ try {
54
+ const response = await this.client.post('/users/bulk', { user_ids: userIds });
55
+ return response.data;
56
+ } catch (error) {
57
+ throw this.handleError(error);
58
+ }
59
+ },
60
+
61
+ getBadges: async (userId: string): Promise<{ badges: UserBadge[] }> => {
62
+ try {
63
+ const response = await this.client.get(`/user/${encodeURIComponent(userId)}/badges`);
64
+ return response.data;
65
+ } catch (error) {
66
+ throw this.handleError(error);
67
+ }
68
+ },
69
+
70
+ getRank: async (userId: string, timeframe: 'all-time' | 'weekly' | 'monthly' = 'all-time'): Promise<UserRank> => {
71
+ try {
72
+ const response = await this.client.get(`/user/${encodeURIComponent(userId)}/rank`, {
73
+ params: { timeframe },
74
+ });
75
+ return response.data;
76
+ } catch (error) {
77
+ throw this.handleError(error);
78
+ }
79
+ },
80
+
81
+ submitAnswers: async (userId: string, questionnaireId: string, answers: AnswerSubmission[]): Promise<{ status: string; message: string }> => {
82
+ try {
83
+ const response = await this.client.post(`/user/${encodeURIComponent(userId)}/answers`, {
84
+ questionnaire_id: questionnaireId,
85
+ answers,
86
+ });
87
+ return response.data;
88
+ } catch (error) {
89
+ throw this.handleError(error);
90
+ }
91
+ },
92
+ };
93
+
94
+ public leaderboards = {
95
+ getGlobal: async (
96
+ timeframe: 'all-time' | 'weekly' | 'monthly' = 'all-time',
97
+ page: number = 1,
98
+ limit: number = 50
99
+ ): Promise<LeaderboardResult> => {
100
+ try {
101
+ const response = await this.client.get('/leaderboard', {
102
+ params: { timeframe, page, limit },
103
+ });
104
+ return response.data;
105
+ } catch (error) {
106
+ throw this.handleError(error);
107
+ }
108
+ },
109
+
110
+ list: async (page: number = 1, limit: number = 50, search?: string): Promise<LeaderboardListResult> => {
111
+ try {
112
+ const params: any = { page, limit };
113
+ if (search) {
114
+ params.search = search;
115
+ }
116
+ const response = await this.client.get('/leaderboards', { params });
117
+ return response.data;
118
+ } catch (error) {
119
+ throw this.handleError(error);
120
+ }
121
+ },
122
+
123
+ getCustom: async (
124
+ leaderboardId: string,
125
+ page: number = 1,
126
+ limit: number = 50,
127
+ search?: string
128
+ ): Promise<LeaderboardResult> => {
129
+ try {
130
+ const params: any = { page, limit };
131
+ if (search) {
132
+ params.search = search;
133
+ }
134
+ const response = await this.client.get(`/leaderboard/${encodeURIComponent(leaderboardId)}`, { params });
135
+ return response.data;
136
+ } catch (error) {
137
+ throw this.handleError(error);
138
+ }
139
+ },
140
+
141
+ getUserRank: async (leaderboardId: string, userId: string): Promise<UserRank> => {
142
+ try {
143
+ const response = await this.client.get(
144
+ `/leaderboard/${encodeURIComponent(leaderboardId)}/user/${encodeURIComponent(userId)}/rank`
145
+ );
146
+ return response.data;
147
+ } catch (error) {
148
+ throw this.handleError(error);
149
+ }
150
+ },
151
+ };
152
+
153
+ public badges = {
154
+ list: async (page: number = 1, limit: number = 50, activeOnly: boolean = false): Promise<BadgeListResult> => {
155
+ try {
156
+ const response = await this.client.get('/badges', {
157
+ params: { page, limit, active_only: activeOnly },
158
+ });
159
+ return response.data;
160
+ } catch (error) {
161
+ throw this.handleError(error);
162
+ }
163
+ },
164
+ };
165
+
166
+ public levels = {
167
+ list: async (page: number = 1, limit: number = 50): Promise<LevelListResult> => {
168
+ try {
169
+ const response = await this.client.get('/levels', {
170
+ params: { page, limit },
171
+ });
172
+ return response.data;
173
+ } catch (error) {
174
+ throw this.handleError(error);
175
+ }
176
+ },
177
+ };
178
+
179
+ public questionnaires = {
180
+ get: async (slug: string): Promise<Questionnaire> => {
181
+ try {
182
+ const response = await this.client.get(`/questionnaire/${slug}`);
183
+ return response.data;
184
+ } catch (error) {
185
+ throw this.handleError(error);
186
+ }
187
+ },
188
+
189
+ getActive: async (): Promise<Questionnaire> => {
190
+ try {
191
+ const response = await this.client.get('/questionnaire/active');
192
+ return response.data;
193
+ } catch (error) {
194
+ throw this.handleError(error);
195
+ }
196
+ },
197
+ };
198
+
199
+ public aha = {
200
+ declare: async (userId: string, value: number): Promise<AhaDeclarationResult> => {
201
+ // Validate value is between 1 and 5
202
+ if (!Number.isInteger(value) || value < 1 || value > 5) {
203
+ throw new Error('Aha score value must be an integer between 1 and 5');
204
+ }
205
+
206
+ try {
207
+ const response = await this.client.post('/aha/declare', {
208
+ user_id: userId,
209
+ value,
210
+ });
211
+ return response.data;
212
+ } catch (error) {
213
+ throw this.handleError(error);
214
+ }
215
+ },
216
+
217
+ getUserScore: async (userId: string): Promise<AhaScoreResult> => {
218
+ try {
219
+ const response = await this.client.get(`/users/${encodeURIComponent(userId)}/aha`);
220
+ return response.data;
221
+ } catch (error) {
222
+ throw this.handleError(error);
223
+ }
224
+ },
225
+ };
226
+
227
+ private handleError(error: any): Error {
228
+ if (axios.isAxiosError(error)) {
229
+ const message = error.response?.data?.message || error.message;
230
+ return new Error(message);
231
+ }
232
+ return error;
233
+ }
234
+ }
235
+
236
+ export default Rooguys;
package/src/types.ts ADDED
@@ -0,0 +1,165 @@
1
+ export interface RooguysOptions {
2
+ baseUrl?: string;
3
+ timeout?: number;
4
+ }
5
+
6
+ export interface UserProfile {
7
+ user_id: string;
8
+ points: number;
9
+ persona: string | null;
10
+ level: {
11
+ id: string;
12
+ name: string;
13
+ level_number: number;
14
+ points_required: number;
15
+ } | null;
16
+ next_level: {
17
+ id: string;
18
+ name: string;
19
+ level_number: number;
20
+ points_required: number;
21
+ points_needed: number;
22
+ } | null;
23
+ metrics: Record<string, any>;
24
+ badges: UserBadge[];
25
+ }
26
+
27
+ export interface UserBadge {
28
+ id: string;
29
+ name: string;
30
+ description: string;
31
+ icon_url: string;
32
+ earned_at: string;
33
+ }
34
+
35
+ export interface UserRank {
36
+ user_id: string;
37
+ rank: number;
38
+ points: number;
39
+ total_users: number;
40
+ }
41
+
42
+ export interface LeaderboardEntry {
43
+ rank: number;
44
+ user_id: string;
45
+ points: number;
46
+ level: {
47
+ id: string;
48
+ name: string;
49
+ level_number: number;
50
+ } | null;
51
+ }
52
+
53
+ export interface LeaderboardResult {
54
+ timeframe: string;
55
+ page: number;
56
+ limit: number;
57
+ total: number;
58
+ rankings: LeaderboardEntry[];
59
+ }
60
+
61
+ export interface TrackEventResponse {
62
+ status: string;
63
+ message: string;
64
+ profile?: UserProfile;
65
+ }
66
+
67
+ export interface AnswerSubmission {
68
+ question_id: string;
69
+ answer_option_id: string;
70
+ }
71
+
72
+ export interface AhaDeclarationResult {
73
+ success: boolean;
74
+ message: string;
75
+ }
76
+
77
+ export interface AhaScoreResult {
78
+ success: boolean;
79
+ data: {
80
+ user_id: string;
81
+ current_score: number;
82
+ declarative_score: number | null;
83
+ inferred_score: number | null;
84
+ status: 'not_started' | 'progressing' | 'activated';
85
+ history: {
86
+ initial: number | null;
87
+ initial_date: string | null;
88
+ previous: number | null;
89
+ };
90
+ };
91
+ }
92
+
93
+ export interface Badge {
94
+ id: string;
95
+ name: string;
96
+ description: string;
97
+ icon_url: string;
98
+ points_required: number;
99
+ is_active: boolean;
100
+ unlock_criteria: string;
101
+ created_at: string;
102
+ }
103
+
104
+ export interface BadgeListResult {
105
+ badges: Badge[];
106
+ pagination: {
107
+ page: number;
108
+ limit: number;
109
+ total: number;
110
+ };
111
+ }
112
+
113
+ export interface Level {
114
+ id: string;
115
+ name: string;
116
+ level_number: number;
117
+ points_required: number;
118
+ description: string | null;
119
+ icon_url: string | null;
120
+ created_at: string;
121
+ }
122
+
123
+ export interface LevelListResult {
124
+ levels: Level[];
125
+ pagination: {
126
+ page: number;
127
+ limit: number;
128
+ total: number;
129
+ };
130
+ }
131
+
132
+ export interface QuestionOption {
133
+ id: string;
134
+ text: string;
135
+ persona_weight: Record<string, number>;
136
+ }
137
+
138
+ export interface Question {
139
+ id: string;
140
+ text: string;
141
+ order: number;
142
+ answer_options: QuestionOption[];
143
+ }
144
+
145
+ export interface Questionnaire {
146
+ id: string;
147
+ slug: string;
148
+ title: string;
149
+ description: string;
150
+ is_active: boolean;
151
+ questions: Question[];
152
+ created_at: string;
153
+ }
154
+
155
+ export interface LeaderboardListResult {
156
+ page: number;
157
+ limit: number;
158
+ total: number;
159
+ leaderboards: Array<{
160
+ id: string;
161
+ name: string;
162
+ description: string;
163
+ created_at: string;
164
+ }>;
165
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2018",
4
+ "module": "commonjs",
5
+ "lib": ["es2018", "dom"],
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "**/*.test.ts"]
15
+ }