@@ -16,6 +16,7 @@ package realmadmin
16
16
17
17
import (
18
18
"context"
19
+ "encoding/csv"
19
20
"net/http"
20
21
"strconv"
21
22
"time"
@@ -27,46 +28,87 @@ import (
27
28
28
29
var cacheTimeout = 5 * time .Minute
29
30
30
- func (c * Controller ) HandleShow () http.Handler {
31
+ // ResultType specfies which type of renderer you want.
32
+ type ResultType int
33
+
34
+ const (
35
+ HTML ResultType = iota
36
+ JSON
37
+ CSV
38
+ )
39
+
40
+ // wantUser returns true if we want per-user requests.
41
+ func wantUser (r * http.Request ) bool {
42
+ _ , has := r .URL .Query ()["user" ]
43
+ return has
44
+ }
45
+
46
+ // getRealmStats returns the realm stats for a given date range.
47
+ func (c * Controller ) getRealmStats (ctx context.Context , realm * database.Realm , now , past time.Time ) ([]* database.RealmStats , error ) {
48
+ var stats []* database.RealmStats
49
+ cacheKey := & cache.Key {
50
+ Namespace : "stats:realm" ,
51
+ Key : strconv .FormatUint (uint64 (realm .ID ), 10 ),
52
+ }
53
+ if err := c .cacher .Fetch (ctx , cacheKey , & stats , cacheTimeout , func () (interface {}, error ) {
54
+ return realm .Stats (c .db , past , now )
55
+ }); err != nil {
56
+ return nil , err
57
+ }
58
+
59
+ return stats , nil
60
+ }
61
+
62
+ // getUserStats gets the per-user realm stats for a given date range.
63
+ func (c * Controller ) getUserStats (ctx context.Context , realm * database.Realm , now , past time.Time ) ([]* database.RealmUserStats , error ) {
64
+ var userStats []* database.RealmUserStats
65
+ cacheKey := & cache.Key {
66
+ Namespace : "stats:realm:per_user" ,
67
+ Key : strconv .FormatUint (uint64 (realm .ID ), 10 ),
68
+ }
69
+ if err := c .cacher .Fetch (ctx , cacheKey , & userStats , cacheTimeout , func () (interface {}, error ) {
70
+ return realm .CodesPerUser (c .db , past , now )
71
+ }); err != nil {
72
+ return nil , err
73
+ }
74
+ return userStats , nil
75
+ }
76
+
77
+ func (c * Controller ) HandleShow (result ResultType ) http.Handler {
31
78
return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
32
79
ctx := r .Context ()
33
80
81
+ now := time .Now ().UTC ()
82
+ past := now .Add (- 30 * 24 * time .Hour )
83
+
34
84
realm := controller .RealmFromContext (ctx )
35
85
if realm == nil {
36
86
controller .MissingRealm (w , r , c .h )
37
- return
38
87
}
39
88
40
- now := time .Now ().UTC ()
41
- past := now .Add (- 30 * 24 * time .Hour )
42
-
43
- // Get and cache the stats for this realm.
44
- var stats []* database.RealmStats
45
- cacheKey := & cache.Key {
46
- Namespace : "stats:realm" ,
47
- Key : strconv .FormatUint (uint64 (realm .ID ), 10 ),
48
- }
49
- if err := c .cacher .Fetch (ctx , cacheKey , & stats , cacheTimeout , func () (interface {}, error ) {
50
- return realm .Stats (c .db , past , now )
51
- }); err != nil {
89
+ // Get the realm stats.
90
+ stats , err := c .getRealmStats (ctx , realm , now , past )
91
+ if err != nil {
52
92
controller .InternalError (w , r , c .h , err )
53
- return
54
93
}
55
94
56
95
// Also get the per-user stats.
57
- var userStats []* database.RealmUserStats
58
- cacheKey = & cache.Key {
59
- Namespace : "stats:realm:per_user" ,
60
- Key : strconv .FormatUint (uint64 (realm .ID ), 10 ),
61
- }
62
- if err := c .cacher .Fetch (ctx , cacheKey , & userStats , cacheTimeout , func () (interface {}, error ) {
63
- return realm .CodesPerUser (c .db , past , now )
64
- }); err != nil {
96
+ userStats , err := c .getUserStats (ctx , realm , now , past )
97
+ if err != nil {
65
98
controller .InternalError (w , r , c .h , err )
66
- return
67
99
}
68
100
69
- c .renderShow (ctx , w , realm , stats , userStats )
101
+ switch result {
102
+ case CSV :
103
+ err = c .renderCSV (r , w , stats , userStats )
104
+ case JSON :
105
+ err = c .renderJSON (r , w , stats , userStats )
106
+ case HTML :
107
+ err = c .renderHTML (ctx , w , realm , stats , userStats )
108
+ }
109
+ if err != nil {
110
+ controller .InternalError (w , r , c .h , err )
111
+ }
70
112
})
71
113
}
72
114
@@ -107,12 +149,59 @@ func formatData(userStats []*database.RealmUserStats) ([]string, [][]interface{}
107
149
return names , data
108
150
}
109
151
110
- func (c * Controller ) renderShow (ctx context.Context , w http.ResponseWriter , realm * database.Realm , stats []* database.RealmStats , userStats []* database.RealmUserStats ) {
152
+ func (c * Controller ) renderHTML (ctx context.Context , w http.ResponseWriter , realm * database.Realm , stats []* database.RealmStats , userStats []* database.RealmUserStats ) error {
111
153
names , format := formatData (userStats )
112
154
m := controller .TemplateMapFromContext (ctx )
113
155
m ["user" ] = realm
114
156
m ["stats" ] = stats
115
157
m ["names" ] = names
116
158
m ["userStats" ] = format
117
159
c .h .RenderHTML (w , "realmadmin/show" , m )
160
+
161
+ return nil
162
+ }
163
+
164
+ // renderCSV renders a CSV response.
165
+ func (c * Controller ) renderCSV (r * http.Request , w http.ResponseWriter , stats []* database.RealmStats , userStats []* database.RealmUserStats ) error {
166
+ wr := csv .NewWriter (w )
167
+ defer wr .Flush ()
168
+
169
+ // Check if we want the realm stats or the per-user stats. We
170
+ // default to realm stats.
171
+ if wantUser (r ) {
172
+ if err := wr .Write (database .RealmUserStatsCSVHeader ); err != nil {
173
+ return err
174
+ }
175
+
176
+ for _ , u := range userStats {
177
+ if err := wr .Write (u .CSV ()); err != nil {
178
+ return err
179
+ }
180
+ }
181
+ } else {
182
+ if err := wr .Write (database .RealmStatsCSVHeader ); err != nil {
183
+ return err
184
+ }
185
+
186
+ for _ , s := range stats {
187
+ if err := wr .Write (s .CSV ()); err != nil {
188
+ return err
189
+ }
190
+ }
191
+ }
192
+
193
+ w .Header ().Set ("Content-Type" , "text/csv" )
194
+ w .Header ().Set ("Content-Disposition" , "attachment;filename=stats.csv" )
195
+ return nil
196
+ }
197
+
198
+ // renderJSON renders a JSON response.
199
+ func (c * Controller ) renderJSON (r * http.Request , w http.ResponseWriter , stats []* database.RealmStats , userStats []* database.RealmUserStats ) error {
200
+ if wantUser (r ) {
201
+ c .h .RenderJSON (w , http .StatusOK , userStats )
202
+ } else {
203
+ c .h .RenderJSON (w , http .StatusOK , stats )
204
+ }
205
+ w .Header ().Set ("Content-Disposition" , "attachment;filename=stats.json" )
206
+ return nil
118
207
}
0 commit comments