Proxy URLs soon rather than late
[sitka/overdrive-evergreen-opac.git] / src / od_data.coffee
1 define [
2         'lodash'
3         'moment'
4 ], (
5         _
6         M
7 ) ->
8
9         # A base class defining utilitarian methods
10         class U
11                 constructor: (x) ->
12                         return unless x
13                         t = @
14                         t extends x
15                         return
16
17                 # Mutate an ISO 8601 date string into a Moment object.  If the argument is
18                 # just a date value, then it specifies an absolute date in ISO 8601 format.
19                 # If the argument is a pair, then it specifies a date relative to now.  For
20                 # an ISO 8601 date, we correct for what seems to be an error in time zone,
21                 # Zulu time is really East Coast time.
22                 momentize: (date, unit) ->
23                         switch arguments.length
24                                 when 1
25                                         if date then M(date.replace /Z$/, '-0400') else M()
26                                 when 2
27                                         if date then M().add date, unit else M()
28                                 else M()
29
30                 # The URL endpoint is converted to its reverse proxy version,
31                 # because we are using the Evergreen server as a reverse proxy to
32                 # the Overdrive server.
33                 proxy: (x) ->
34                         return unless x
35                         y = x
36                         y = y.replace 'https://', '//'
37                         y = y.replace 'http://' , '//'
38                         y = y.replace '//oauth-patron.overdrive.com', '/od/oauth-patron'
39                         y = y.replace        '//oauth.overdrive.com', '/od/oauth'
40                         y = y.replace   '//patron.api.overdrive.com', '/od/api-patron'
41                         y = y.replace          '//api.overdrive.com', '/od/api'
42                         y = y.replace  '//images.contentreserve.com', '/od/images'
43                         y = y.replace '//fulfill.contentreserve.com', '/od/fulfill'
44                         #log "proxy #{x} -> #{y}"
45                         y
46                 proxies: (x) ->
47                         (v.href = @proxy l) for n, v of x when l = v.href
48                         return x
49
50
51         class Metadata extends U
52                 constructor: (x) ->
53                         super x
54
55                         # Convert ID to upper case to match same case found in EG catalogue
56                         @id = @id.toUpperCase()
57                         # Provide a simplified notion of author: first name in creators
58                         # list having a role of author
59                         @author = (v.name for v in @creators when v.role is 'Author')[0] or ''
60                         # Convert image links to use reverse proxy
61                         @proxies @images
62
63                         return
64
65
66         class Availability extends U
67                 constructor: (x, email_address) ->
68                         super x
69
70                         @zero()
71                         @hold email_address if @actions?.hold
72
73                         return @
74
75                 # Add zero values
76                 zero: ->
77                         @copiesOwned     = 0 unless @copiesOwned
78                         @copiesAvailable = 0 unless @copiesAvailable
79                         @numberOfHolds   = 0 unless @numberOfHolds
80                         return @
81
82                 hold: (email_address) ->
83                         # The reserve ID is empty in the actions.hold.fields; we have to fill it ourselves.
84                         _.where(@actions.hold.fields, name: 'reserveId')[0].value = @id
85                         # We jam the email address from the prefs page into the fields object from the server
86                         # so that the new form will display it.
87                         if email_address
88                                 _.where(@actions.hold.fields, name: 'emailAddress')[0].value = email_address
89                         return @
90
91
92         class Holds extends U
93                 constructor: (x) ->
94                         super x
95
96                         @add()
97                         .remove()
98                         .proxy_urls()
99                         .moments()
100                         .count()
101                         .sort()
102
103                         return
104
105                 # Ensure there is always a holds list, even if it's empty
106                 add: ->
107                         @holds = [] if @holds is undefined
108                         return @
109
110                 # Delete action to release a suspension if a hold is not
111                 # suspended, because such actions are redundant
112                 remove: ->
113                         delete x.actions.releaseSuspension for x in @holds when not x.holdSuspension
114                         return @
115
116                 proxy_urls: ->
117                         (@proxies v.actions) for v, n in @holds
118                         return @
119                         
120                 # For each hold, convert any ISO 8601 date strings into a
121                 # Moment object (at local time zone)
122                 moments: ->
123                         for x in @holds
124                                 x.holdPlacedDate = @momentize x.holdPlacedDate
125                                 x.holdExpires = @momentize x.holdExpires
126                                 if x.holdSuspension
127                                         x.holdSuspension.numberOfDays = @momentize x.holdSuspension.numberOfDays, 'days'
128                         return @
129
130                 # Count the number of holds that can be checked out now
131                 count: ->
132                         @ready = _.countBy @holds, (x) -> if x.actions.checkout then 'forCheckout' else 'other'
133                         @ready.forCheckout = 0 unless @ready.forCheckout
134                         return @
135
136                 # Sort the holds list by position and placed date
137                 # and sort ready holds first
138                 sort: ->
139                         @holds = _(@holds)
140                                 .sortBy ['holdListPosition', 'holdPlacedDate']
141                                 .sortBy (x) -> x.actions.checkout
142                                 .value()
143                         return @
144
145
146         class Checkouts extends U
147                 constructor: (x) ->
148                         super x
149
150                         @add()
151                         .proxy_urls()
152                         .moments()
153                         .sort()
154
155                         return
156
157                 # Ensure there is always a checkouts list, even if it's empty
158                 add: ->
159                         @checkouts = [] if @checkouts is undefined
160                         return @
161
162                 proxy_urls:->
163                         (@proxies v.actions) for v, n in @checkouts
164                         return @
165
166                 # For each checkout, convert any ISO 8601 date strings into a
167                 # Moment object (at local time zone)
168                 moments: ->
169                         for x in @checkouts
170                                 x.expires = @momentize x.expires
171                         return @
172
173                 # Sort the checkout list by expiration date
174                 sort: ->
175                         @checkouts = _.sortBy @checkouts, 'expires'
176                         return @
177
178
179         class Interests
180                 constructor: (h, c) ->
181                         return {
182                                 nHolds: h.totalItems
183                                 nHoldsReady: h.ready.forCheckout
184                                 nCheckouts: c.totalItems
185                                 nCheckoutsReady: c.totalCheckouts
186                                 ofHolds: h.holds
187                                 ofCheckouts: c.checkouts
188                                 # The following property is a map from product ID to a hold or
189                                 # a checkout object, eg, interests.byID(124)
190                                 byID: do (hs = h.holds, cs = c.checkouts) ->
191                                         byID = {}
192                                         for v, n in hs
193                                                 v.type = 'hold'
194                                                 byID[v.reserveId] = v
195                                         for v, n in cs
196                                                 v.type = 'checkout'
197                                                 byID[v.reserveId] = v
198                                         return byID
199                         }
200         
201         return {
202                 Metadata:     Metadata
203                 Availability: Availability
204                 Holds:        Holds
205                 Checkouts:    Checkouts
206                 Interests:    Interests
207         }