Skip to content

Commit 5004cbb

Browse files
authored
Merge pull request #1796 from StoDevX/olecard-json-endpoint
Olecard json endpoint
2 parents 1b8e951 + bacebdd commit 5004cbb

File tree

7 files changed

+135
-104
lines changed

7 files changed

+135
-104
lines changed

source/flux/parts/settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export type SettingsState = {
162162
dietaryPreferences: [],
163163
credentials: CredentialsState,
164164
feedbackDisabled: boolean,
165+
unofficiallyAcknowledged: boolean,
165166
}
166167

167168
const initialSettingsState: SettingsState = {

source/flux/parts/sis.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const UPDATE_BALANCES = 'sis/UPDATE_BALANCES'
1414
export const UPDATE_MEALS_DAILY = 'sis/UPDATE_MEALS_DAILY'
1515
export const UPDATE_MEALS_WEEKLY = 'sis/UPDATE_MEALS_WEEKLY'
1616
export const UPDATE_MEALS_REMAINING = 'sis/UPDATE_MEALS_REMAINING'
17+
export const UPDATE_MEAL_PLAN = 'sis/UPDATE_MEAL_PLAN'
1718
export const UPDATE_COURSES = 'sis/UPDATE_COURSES'
1819

1920
export function updateBalances(forceFromServer: boolean = false) {
@@ -40,15 +41,25 @@ export function updateCourses(forceFromServer: boolean = false) {
4041
}
4142
}
4243

44+
export type BalancesState = {
45+
message: ?string,
46+
flex: ?string,
47+
ole: ?string,
48+
print: ?string,
49+
daily: ?string,
50+
weekly: ?string,
51+
plan: ?string,
52+
}
4353
const initialBalancesState = {
4454
message: null,
4555
flex: null,
4656
ole: null,
4757
print: null,
4858
daily: null,
4959
weekly: null,
60+
plan: null,
5061
}
51-
function balances(state = initialBalancesState, action) {
62+
function balances(state: BalancesState = initialBalancesState, action) {
5263
const {type, payload, error} = action
5364

5465
switch (type) {
@@ -62,6 +73,8 @@ function balances(state = initialBalancesState, action) {
6273
return {...state, daily: payload.mealsRemaining}
6374
case UPDATE_MEALS_WEEKLY:
6475
return {...state, weekly: payload.mealsRemaining}
76+
case UPDATE_MEAL_PLAN:
77+
return {...state, plan: payload.mealPlan}
6578

6679
case UPDATE_BALANCES: {
6780
if (error) {

source/lib/cache.js

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,28 +79,28 @@ export function getAllCourses(): CacheResultType<?CoursesByTermType> {
7979

8080
const flexBalanceKey = 'financials:flex'
8181
const flexBalanceCacheTime = [5, 'minutes']
82-
export function setFlexBalance(balance: ?number) {
82+
export function setFlexBalance(balance: ?string) {
8383
return setItem(flexBalanceKey, balance, flexBalanceCacheTime)
8484
}
85-
export function getFlexBalance(): CacheResultType<?number> {
85+
export function getFlexBalance(): CacheResultType<?string> {
8686
return getItem(flexBalanceKey)
8787
}
8888

8989
const oleBalanceKey = 'financials:ole'
9090
const oleBalanceCacheTime = [5, 'minutes']
91-
export function setOleBalance(balance: ?number) {
91+
export function setOleBalance(balance: ?string) {
9292
return setItem(oleBalanceKey, balance, oleBalanceCacheTime)
9393
}
94-
export function getOleBalance(): CacheResultType<?number> {
94+
export function getOleBalance(): CacheResultType<?string> {
9595
return getItem(oleBalanceKey)
9696
}
9797

9898
const printBalanceKey = 'financials:print'
9999
const printBalanceCacheTime = [5, 'minutes']
100-
export function setPrintBalance(balance: ?number) {
100+
export function setPrintBalance(balance: ?string) {
101101
return setItem(printBalanceKey, balance, printBalanceCacheTime)
102102
}
103-
export function getPrintBalance(): CacheResultType<?number> {
103+
export function getPrintBalance(): CacheResultType<?string> {
104104
return getItem(printBalanceKey)
105105
}
106106

@@ -122,59 +122,75 @@ export function getWeeklyMealInfo(): CacheResultType<?string> {
122122
return getItem(weeklyMealsKey)
123123
}
124124

125+
const mealPlanKey = 'meals:plan'
126+
const mealPlanCacheTime = [5, 'minutes']
127+
export function setMealPlanInfo(mealPlanName: ?string) {
128+
return setItem(mealPlanKey, mealPlanName, mealPlanCacheTime)
129+
}
130+
export function getMealPlanInfo(): CacheResultType<?string> {
131+
return getItem(mealPlanKey)
132+
}
133+
125134
type BalancesInputType = {
126-
flex: ?number,
127-
ole: ?number,
128-
print: ?number,
135+
flex: ?string,
136+
ole: ?string,
137+
print: ?string,
129138
daily: ?string,
130139
weekly: ?string,
140+
plan: ?string,
131141
}
132142
export function setBalances({
133143
flex,
134144
ole,
135145
print,
136146
daily,
137147
weekly,
148+
plan,
138149
}: BalancesInputType) {
139150
return Promise.all([
140151
setFlexBalance(flex),
141152
setOleBalance(ole),
142153
setPrintBalance(print),
143154
setDailyMealInfo(daily),
144155
setWeeklyMealInfo(weekly),
156+
setMealPlanInfo(plan),
145157
])
146158
}
147159

148160
type BalancesOutputType = {
149-
flex: BaseCacheResultType<?number>,
150-
ole: BaseCacheResultType<?number>,
151-
print: BaseCacheResultType<?number>,
161+
flex: BaseCacheResultType<?string>,
162+
ole: BaseCacheResultType<?string>,
163+
print: BaseCacheResultType<?string>,
152164
daily: BaseCacheResultType<?string>,
153165
weekly: BaseCacheResultType<?string>,
166+
plan: BaseCacheResultType<?string>,
154167
_isExpired: boolean,
155168
_isCached: boolean,
156169
}
157170
export async function getBalances(): Promise<BalancesOutputType> {
158-
const [flex, ole, print, daily, weekly] = await Promise.all([
171+
const [flex, ole, print, daily, weekly, plan] = await Promise.all([
159172
getFlexBalance(),
160173
getOleBalance(),
161174
getPrintBalance(),
162175
getDailyMealInfo(),
163176
getWeeklyMealInfo(),
177+
getMealPlanInfo(),
164178
])
165179

166180
const _isExpired =
167181
flex.isExpired ||
168182
ole.isExpired ||
169183
print.isExpired ||
170184
daily.isExpired ||
171-
weekly.isExpired
185+
weekly.isExpired ||
186+
plan.isExpired
172187
const _isCached =
173188
flex.isCached ||
174189
ole.isCached ||
175190
print.isCached ||
176191
daily.isCached ||
177-
weekly.isCached
192+
weekly.isCached ||
193+
plan.isCached
178194

179-
return {flex, ole, print, daily, weekly, _isExpired, _isCached}
195+
return {flex, ole, print, daily, weekly, plan, _isExpired, _isCached}
180196
}

source/lib/financials/balances.js

Lines changed: 31 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
// @flow
22
import {loadLoginCredentials} from '../login'
33
import buildFormData from '../formdata'
4-
import {parseHtml, cssSelect, getTrimmedTextWithSpaces} from '../html'
5-
import {OLECARD_AUTH_URL} from './urls'
6-
import type {BalancesShapeType} from './types'
7-
import fromPairs from 'lodash/fromPairs'
4+
import {OLECARD_AUTH_URL, OLECARD_DATA_ENDPOINT} from './urls'
5+
import type {BalancesShapeType, OleCardBalancesType} from './types'
86
import isNil from 'lodash/isNil'
97
import * as cache from '../cache'
108

@@ -22,6 +20,7 @@ export async function getBalances(
2220
print,
2321
daily,
2422
weekly,
23+
plan,
2524
_isExpired,
2625
_isCached,
2726
} = await cache.getBalances()
@@ -46,6 +45,7 @@ export async function getBalances(
4645
print: print.value,
4746
daily: daily.value,
4847
weekly: weekly.value,
48+
plan: plan.value,
4949
},
5050
}
5151
}
@@ -60,70 +60,44 @@ async function fetchBalancesFromServer(): Promise<BalancesOrErrorType> {
6060
username: username,
6161
password: password,
6262
})
63-
const result = await fetch(OLECARD_AUTH_URL, {method: 'POST', body: form})
64-
const page = await result.text()
65-
const dom = parseHtml(page)
63+
await fetch(OLECARD_AUTH_URL, {method: 'POST', body: form})
6664

67-
return parseBalancesFromDom(dom)
65+
const resp: OleCardBalancesType = await fetchJson(OLECARD_DATA_ENDPOINT)
66+
67+
return getBalancesFromData(resp)
6868
}
6969

70-
function parseBalancesFromDom(dom: mixed): BalancesOrErrorType {
71-
// .accountrow is the name of the row, and it's immediate sibling is a cell with id=value
72-
const elements = cssSelect('.accountrow', dom)
73-
.map(el => el.parent)
74-
.map(getTrimmedTextWithSpaces)
75-
.map(rowIntoNamedAmount)
76-
.filter(Boolean)
70+
const accounts = {
71+
flex: 'STO Flex',
72+
ole: 'STO Ole Dollars',
73+
print: 'STO Student Printing',
74+
}
7775

78-
const namedValues = fromPairs(elements)
76+
function getBalancesFromData(resp: OleCardBalancesType): BalancesOrErrorType {
77+
if (resp.error) {
78+
return {
79+
error: true,
80+
value: new Error(resp.error),
81+
}
82+
}
7983

80-
const flex = dollarAmountToInteger(namedValues.flex)
81-
const ole = dollarAmountToInteger(namedValues.ole)
82-
const print = dollarAmountToInteger(namedValues.print)
83-
const daily = namedValues.daily
84-
const weekly = namedValues.weekly
84+
const flex = resp.data.accounts.find(a => a.account === accounts.flex)
85+
const ole = resp.data.accounts.find(a => a.account === accounts.ole)
86+
const print = resp.data.accounts.find(a => a.account === accounts.print)
87+
88+
const daily = resp.data.meals && resp.data.meals.leftDaily
89+
const weekly = resp.data.meals && resp.data.meals.leftWeekly
90+
const plan = resp.data.meals && resp.data.meals.plan
8591

8692
return {
8793
error: false,
8894
value: {
89-
flex: isNil(flex) ? null : flex,
90-
ole: isNil(ole) ? null : ole,
91-
print: isNil(print) ? null : print,
95+
flex: flex || flex === 0 ? flex.formatted : null,
96+
ole: ole || ole === 0 ? ole.formatted : null,
97+
print: print || print === 0 ? print.formatted : null,
9298
daily: isNil(daily) ? null : daily,
9399
weekly: isNil(weekly) ? null : weekly,
100+
plan: isNil(plan) ? null : plan,
94101
},
95102
}
96103
}
97-
98-
const lookupHash: Map<RegExp, string> = new Map([
99-
[/sto flex/i, 'flex'],
100-
[/ole/i, 'ole'],
101-
[/print/i, 'print'],
102-
[/meals.*day/i, 'daily'],
103-
[/meals.*week/i, 'weekly'],
104-
])
105-
106-
function rowIntoNamedAmount(row: string): ?[string, string] {
107-
const chunks = row.split(' ')
108-
const name = chunks.slice(0, -1).join(' ')
109-
const amount = chunks[chunks.length - 1]
110-
111-
// We have a list of regexes that check the row names for keywords.
112-
// Those keywords are associated with the actual key names.
113-
for (const [lookup, key] of lookupHash.entries()) {
114-
if (lookup.test(name)) {
115-
return [key, amount]
116-
}
117-
}
118-
}
119-
120-
function dollarAmountToInteger(amount: ?string): ?number {
121-
const amountString = amount || ''
122-
// remove the /[$.]/, and put the numbers into big strings (eg, $3.14 -> '314')
123-
const nonDenominationalAmount = amountString
124-
.replace('$', '')
125-
.split('.')
126-
.join('')
127-
const num = parseInt(nonDenominationalAmount, 10)
128-
return Number.isNaN(num) ? null : num
129-
}

source/lib/financials/types.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
// @flow
22
export type BalancesShapeType = {
3-
flex: ?number,
4-
ole: ?number,
5-
print: ?number,
3+
flex: ?string,
4+
ole: ?string,
5+
print: ?string,
66
weekly: ?string,
77
daily: ?string,
8+
plan: ?string,
9+
}
10+
11+
export type MealPlanInfoType = {
12+
plan: ?string,
13+
leftDaily: ?string,
14+
leftWeekly: ?string,
15+
}
16+
17+
export type AccountBalanceType = {
18+
account: string,
19+
numeric: number,
20+
formatted: string,
21+
}
22+
23+
export type OleCardBalancesType = {
24+
data: {
25+
accounts: Array<AccountBalanceType>,
26+
meals: ?MealPlanInfoType,
27+
},
28+
error: ?string,
829
}

source/lib/financials/urls.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export const LANDING_URL = 'https://www.stolaf.edu/sis/landing-page.cfm'
33
export const FINANCIALS_URL = 'https://www.stolaf.edu/sis/st-financials.cfm'
44
export const OLECARD_AUTH_URL =
55
'https://www.stolaf.edu/apps/olecard/checkbalance/authenticate.cfm'
6+
export const OLECARD_DATA_ENDPOINT =
7+
'https://www.stolaf.edu/apps/olecard/checkbalance/api.cfm'

0 commit comments

Comments
 (0)