@@ -87,6 +87,9 @@ export interface INusApi {
8787type ApiParams = {
8888 [ key : string ] : string ;
8989} ;
90+ type ApiHeaders = {
91+ [ key : string ] : string ;
92+ } ;
9093
9194// Error codes specified by the API. Note that these, like many other things
9295// in the API, are not to be relied upon completely
@@ -95,13 +98,23 @@ const OKAY: STATUS_CODE = '00000';
9598const AUTH_ERROR : STATUS_CODE = '10000' ;
9699const RECORD_NOT_FOUND : STATUS_CODE = '10001' ;
97100
98- // Authentication via tokens sent through headers
99- const headers = {
100- 'X-APP-API' : config . appKey ,
101- 'X-STUDENT-API' : config . studentKey ,
101+ // Shared headers for all API requests
102+ const commonHeaders : ApiHeaders = {
102103 'Content-Type' : 'application/json' ,
103104} ;
104105
106+ // Authentication via tokens sent through headers
107+ const ttHeaders : ApiHeaders = {
108+ 'X-API-KEY' : config . ttApiKey ,
109+ } ;
110+ const courseHeaders : ApiHeaders = {
111+ 'X-API-KEY' : config . courseApiKey ,
112+ } ;
113+ const acadHeaders : ApiHeaders = {
114+ 'X-API-KEY' : config . acadApiKey ,
115+ 'X-APP-KEY' : config . acadAppKey ,
116+ } ;
117+
105118/**
106119 * Map the error code from the API to the correct class
107120 */
@@ -129,21 +142,32 @@ function mapErrorCode(code: string, msg: string) {
129142 * configuration such as authentication which all API calls should have,
130143 * as well as error handling.
131144 */
132- async function callApi < Data > ( endpoint : string , params : ApiParams ) : Promise < Data > {
145+ async function callApi < Data > (
146+ endpoint : string ,
147+ params : ApiParams ,
148+ headers : ApiHeaders ,
149+ ) : Promise < Data > {
133150 // 1. Construct request URL
134151 const url = new URL ( endpoint , config . baseUrl ) ;
152+
153+ // 2. Encode params in the query string for GET requests
154+ Object . entries ( params ) . forEach ( ( [ key , value ] ) => {
155+ url . searchParams . append ( key , value ) ;
156+ } ) ;
157+
135158 let response ;
136159
137160 try {
138- // 2. All API requests use POST HTTP method with params encoded in JSON
139- // in the body
140- response = await axios . post ( url . href , params , {
141- transformRequest : [ ( data ) => JSON . stringify ( data ) ] ,
142- // 3. Apply authentication using header
143- headers,
161+ // 3. All API requests use GET HTTP method with params encoded in the query string.
162+ response = await axios . get ( url . href , {
163+ // 4. Apply authentication using headers
164+ headers : {
165+ ...commonHeaders ,
166+ ...headers ,
167+ } ,
144168 } ) ;
145169 } catch ( e ) {
146- // 4 . Handle network / request level errors, eg. server returning non-200
170+ // 5 . Handle network / request level errors, eg. server returning non-200
147171 // status code
148172 let message ;
149173 if ( e . response ) {
@@ -188,8 +212,8 @@ class NusApi implements INusApi {
188212 /**
189213 * Wrapper around base callApi method that pushes the call into a queue
190214 */
191- callApi = async < T > ( endpoint : string , params : ApiParams ) =>
192- this . queue . add ( ( ) => callApi < T > ( endpoint , params ) ) ;
215+ callApi = async < T > ( endpoint : string , params : ApiParams , headers : ApiHeaders ) =>
216+ this . queue . add ( ( ) => callApi < T > ( endpoint , params , headers ) ) ;
193217
194218 /**
195219 * Calls the modules endpoint
@@ -198,10 +222,14 @@ class NusApi implements INusApi {
198222 try {
199223 // DO NOT remove this await - the promise must settle so the catch
200224 // can handle the NotFoundError from the API
201- return await this . callApi < ModuleInfo [ ] > ( 'module' , {
202- term,
203- ...params ,
204- } ) ;
225+ return await this . callApi < ModuleInfo [ ] > (
226+ 'CourseNUSMods' ,
227+ {
228+ term,
229+ ...params ,
230+ } ,
231+ courseHeaders ,
232+ ) ;
205233 } catch ( e ) {
206234 // The modules endpoint will return NotFound even for valid inputs
207235 // that just happen to have no records, so we ignore this error
@@ -215,18 +243,22 @@ class NusApi implements INusApi {
215243 } ;
216244
217245 getFaculty = async ( ) : Promise < AcademicGrp [ ] > =>
218- this . callApi ( 'config/get-acadgroup' , {
219- eff_status : 'A' ,
220- // % is a wildcard so this function returns everything
221- acad_group : '%' ,
222- } ) ;
246+ this . callApi (
247+ 'edurec/config/v1/get-acadgroup' ,
248+ {
249+ eff_status : 'A' ,
250+ } ,
251+ acadHeaders ,
252+ ) ;
223253
224254 getDepartment = async ( ) : Promise < AcademicOrg [ ] > =>
225- this . callApi ( 'config/get-acadorg' , {
226- eff_status : 'A' ,
227- // % is a wildcard so this function returns everything
228- acad_org : '%' ,
229- } ) ;
255+ this . callApi (
256+ 'edurec/config/v1/get-acadorg' ,
257+ {
258+ eff_status : 'A' ,
259+ } ,
260+ acadHeaders ,
261+ ) ;
230262
231263 getModuleInfo = async ( term : string , moduleCode : ModuleCode ) : Promise < ModuleInfo > => {
232264 // Module info API takes in subject and catalog number separately, so we need
@@ -239,11 +271,15 @@ class NusApi implements INusApi {
239271
240272 // catalognbr = Catalog number
241273 const [ subject , catalognbr ] = parts ;
242- const modules = await this . callApi < ModuleInfo [ ] > ( 'module' , {
243- term,
244- subject,
245- catalognbr,
246- } ) ;
274+ const modules = await this . callApi < ModuleInfo [ ] > (
275+ 'CourseNUSMods' ,
276+ {
277+ term,
278+ subject,
279+ catalognbr,
280+ } ,
281+ courseHeaders ,
282+ ) ;
247283
248284 if ( modules . length === 0 ) throw new NotFoundError ( `Module ${ moduleCode } cannot be found` ) ;
249285 return modules [ 0 ] ;
@@ -256,34 +292,44 @@ class NusApi implements INusApi {
256292 this . callModulesEndpoint ( term , { acadorg : departmentCode } ) ;
257293
258294 getModuleTimetable = async ( term : string , module : ModuleCode ) : Promise < TimetableLesson [ ] > =>
259- this . callApi ( 'classtt/withdate/published' , {
260- term,
261- module,
262- } ) ;
295+ this . callApi (
296+ 'timetable/v1/published/class/withdate' ,
297+ {
298+ term,
299+ module,
300+ } ,
301+ ttHeaders ,
302+ ) ;
263303
264304 getDepartmentTimetables = async (
265305 term : string ,
266306 departmentCode : string ,
267307 ) : Promise < TimetableLesson [ ] > =>
268- this . callApi ( 'classtt/withdate/published' , {
269- term,
270- deptfac : departmentCode ,
271- } ) ;
308+ this . callApi (
309+ 'timetable/v1/published/class/withdate' ,
310+ {
311+ term,
312+ deptfac : departmentCode ,
313+ } ,
314+ ttHeaders ,
315+ ) ;
272316
273317 getSemesterTimetables = async (
274318 term : string ,
275319 lessonConsumer : ( lesson : TimetableLesson ) => void ,
276320 ) : Promise < void > =>
277321 new Promise ( ( resolve , reject ) => {
278- const endpoint = 'classtt/withdate /published' ;
322+ const endpoint = 'timetable/v1 /published/class/withdate ' ;
279323 const url = new URL ( endpoint , config . baseUrl ) ;
280- const body = JSON . stringify ( { term } ) ;
324+ url . searchParams . append ( ' term' , term ) ;
281325
282326 oboe ( {
283327 url : url . href ,
284- headers,
285- body,
286- method : 'POST' ,
328+ headers : {
329+ ...commonHeaders ,
330+ ...ttHeaders ,
331+ } ,
332+ method : 'GET' ,
287333 } )
288334 . node ( 'data[*]' , ( lesson : TimetableLesson ) => {
289335 // Consume and discard each lesson
@@ -298,7 +344,7 @@ class NusApi implements INusApi {
298344 resolve ( ) ;
299345 } else {
300346 const error = mapErrorCode ( code , msg ) ;
301- error . requestConfig = { url : url . href , data : body } ;
347+ error . requestConfig = { url : url . href } ;
302348 reject ( error ) ;
303349 }
304350 } )
@@ -314,18 +360,22 @@ class NusApi implements INusApi {
314360 } ) ;
315361
316362 getModuleExam = async ( term : string , module : ModuleCode ) : Promise < ModuleExam > => {
317- const exams = await this . callApi < ModuleExam [ ] > ( 'examtt/published' , {
318- term,
319- module,
320- } ) ;
363+ const exams = await this . callApi < ModuleExam [ ] > (
364+ 'timetable/v1/published/exam' ,
365+ {
366+ term,
367+ module,
368+ } ,
369+ ttHeaders ,
370+ ) ;
321371
322372 if ( exams . length === 0 )
323373 throw new NotFoundError ( `Exams for ${ module } cannot be found, or the module has no exams` ) ;
324374 return exams [ 0 ] ;
325375 } ;
326376
327377 getTermExams = async ( term : string ) : Promise < ModuleExam [ ] > =>
328- this . callApi ( 'examtt/ published' , { term } ) ;
378+ this . callApi ( 'timetable/v1/ published/exam ' , { term } , ttHeaders ) ;
329379}
330380
331381// Export as default a singleton instance to be used globally
0 commit comments