1
1
import { BlogData , RepoData , RepoPage } from "../../../shared/types" ;
2
- import { firestore } from "./firebase" ;
3
2
4
- import firebase from "firebase" ;
3
+ import {
4
+ FirestoreQuery ,
5
+ QueryResult ,
6
+ QueryResultDocument ,
7
+ } from "../../../shared/types/FirestoreQuery" ;
8
+
9
+ // eslint-disable-next-line
10
+ const lodashGet = require ( "lodash.get" ) ;
5
11
6
12
export interface PagedResponse < T > {
7
- q : firebase . firestore . Query < T > ;
13
+ collectionPath : string ;
14
+ q : FirestoreQuery ;
8
15
perPage : number ;
9
16
pages : T [ ] [ ] ;
10
17
currentPage : number ;
11
18
hasNext : boolean ;
12
- lastDoc : firebase . firestore . QueryDocumentSnapshot | null ;
19
+ lastDoc : QueryResultDocument < T > | null ;
13
20
}
14
21
15
- export async function getDocs < T > ( query : firebase . firestore . Query < T > ) {
16
- const snap = await query . get ( ) ;
17
-
18
- const snapshots = snap . docs ;
19
- const data = snap . docs . map ( ( d ) => d . data ( ) ) ;
20
- return { snapshots, data } ;
22
+ function getApiHost ( ) : string {
23
+ // In development the hosting emulator runs at port 5000
24
+ // while the Vue dev server runs elsewhere. In prod this is
25
+ // not an issue
26
+ return window . location . hostname === "localhost"
27
+ ? `http://localhost:5000`
28
+ : "" ;
21
29
}
22
30
23
- export function reposRef ( product : string ) {
24
- return firestore ( )
25
- . collection ( "products" )
26
- . doc ( product )
27
- . collection ( "repos" )
28
- . withConverter ( {
29
- toFirestore : ( obj : RepoData ) => obj ,
30
- fromFirestore : ( snap ) => snap . data ( ) as RepoData ,
31
- } ) ;
32
- }
31
+ async function fetchDoc ( docPath : string ) {
32
+ const params = new URLSearchParams ( {
33
+ path : docPath ,
34
+ } ) ;
33
35
34
- export function reposQuery ( product : string ) {
35
- return reposRef ( product ) as firebase . firestore . Query < RepoData > ;
36
+ const res = await fetch ( ` ${ getApiHost ( ) } /api/docProxy? ${ params . toString ( ) } ` ) ;
37
+ return await res . json ( ) ;
36
38
}
37
39
38
- export function blogsRef ( product : string ) {
39
- return firestore ( )
40
- . collection ( "products" )
41
- . doc ( product )
42
- . collection ( "blogs" )
43
- . withConverter ( {
44
- toFirestore : ( obj : BlogData ) => obj ,
45
- fromFirestore : ( snap ) => snap . data ( ) as BlogData ,
46
- } ) ;
47
- }
40
+ async function fetchQuery ( collectionPath : string , q : FirestoreQuery ) {
41
+ const params = new URLSearchParams ( {
42
+ path : collectionPath ,
43
+ q : btoa ( JSON . stringify ( q ) ) ,
44
+ } ) ;
48
45
49
- export function blogsQuery ( product : string ) {
50
- return blogsRef ( product ) as firebase . firestore . Query < BlogData > ;
46
+ const res = await fetch (
47
+ `${ getApiHost ( ) } /api/queryProxy?${ params . toString ( ) } `
48
+ ) ;
49
+ return await res . json ( ) ;
51
50
}
52
51
53
52
export function emptyPageResponse < T > (
54
- q : firebase . firestore . Query < T > ,
53
+ collectionPath : string ,
54
+ q : FirestoreQuery ,
55
55
perPage : number
56
56
) : PagedResponse < T > {
57
57
return {
58
+ collectionPath,
58
59
q,
59
60
perPage,
60
61
pages : [ ] ,
@@ -69,38 +70,60 @@ export async function prevPage<T>(res: PagedResponse<T>) {
69
70
}
70
71
71
72
export async function nextPage < T > ( res : PagedResponse < T > ) {
72
- // Fetch the next page based on the limit and startAfter
73
- let q = res . q ;
74
- if ( res . lastDoc ) {
75
- q = q . startAfter ( res . lastDoc ) ;
73
+ // TODO: Make a proper deep copy
74
+ const q : FirestoreQuery = JSON . parse ( JSON . stringify ( res . q ) ) ;
75
+
76
+ // Add orderBy
77
+ q . orderBy = q . orderBy || [ ] ;
78
+
79
+ // Add the startAfter clauses
80
+ if ( res . lastDoc != null ) {
81
+ // Add one startAfter per orderBy
82
+ // eslint-disable-next-line
83
+ const startAfter : any [ ] = [ ] ;
84
+ for ( const ob of q . orderBy ) {
85
+ const fieldVal = lodashGet ( res . lastDoc . data , ob . fieldPath ) ;
86
+ startAfter . push ( fieldVal ) ;
87
+ }
88
+
89
+ // Add a secondary order on name
90
+ q . orderBy . push ( {
91
+ fieldPath : "__name__" ,
92
+ direction : "desc" ,
93
+ } ) ;
94
+ startAfter . push ( res . lastDoc . id ) ;
95
+
96
+ q . startAfter = startAfter ;
76
97
}
77
98
78
99
// Load one more than the limit to see if we have anything more
79
100
// beyond the minimum
80
- q = q . limit ( res . perPage + 1 ) ;
101
+ q . limit = res . perPage + 1 ;
81
102
82
- const { data, snapshots } = await getDocs ( q ) ;
103
+ const json = await fetchQuery ( res . collectionPath , q ) ;
104
+ const { docs } = json as QueryResult < T > ;
83
105
84
106
// If we were able to find more than the per-page minimum, there
85
107
// is still more after this
86
- res . hasNext = data . length > res . perPage ;
108
+ res . hasNext = docs . length > res . perPage ;
87
109
88
110
if ( res . hasNext ) {
89
111
// If there are more pages after this then we need to
90
112
// chop off the extra one we loaded (see above) and then add the data
91
- const lastDoc = snapshots [ snapshots . length - 2 ] ;
92
- const pageData = data . slice ( 0 , data . length - 1 ) ;
113
+ const lastDoc = docs [ docs . length - 2 ] ;
114
+ const pageData = docs . slice ( 0 , docs . length - 1 ) . map ( ( d ) => d . data ) ;
93
115
94
116
res . lastDoc = lastDoc ;
95
117
res . pages . push ( pageData ) ;
96
118
} else {
97
119
// If this is the last page, just add the data (if it exists)
98
120
// and accept the last snapshot
99
- if ( data . length > 0 ) {
100
- res . pages . push ( data ) ;
121
+ if ( docs . length > 0 ) {
122
+ const pageData = docs . map ( ( d ) => d . data ) ;
123
+ res . pages . push ( pageData ) ;
101
124
}
102
125
103
- res . lastDoc = snapshots [ snapshots . length - 1 ] ;
126
+ res . lastDoc = docs [ docs . length - 1 ] ;
104
127
}
105
128
106
129
// Finally increment the page
@@ -111,19 +134,39 @@ export async function fetchRepo(
111
134
product : string ,
112
135
id : string
113
136
) : Promise < RepoData > {
114
- const ref = reposRef ( product ) . doc ( id ) ;
137
+ const repoPath = `/products/${ product } /repos/${ id } ` ;
138
+ const json = await fetchDoc ( repoPath ) ;
115
139
116
- const snap = await ref . get ( ) ;
117
- return snap . data ( ) as RepoData ;
140
+ return json as RepoData ;
118
141
}
119
142
120
143
export async function fetchRepoPage (
121
144
product : string ,
122
145
id : string ,
123
146
pageKey : string
124
147
) : Promise < RepoPage > {
125
- const ref = reposRef ( product ) . doc ( id ) . collection ( "pages" ) . doc ( pageKey ) ;
148
+ const pagePath = `/products/${ product } /repos/${ id } /pages/${ pageKey } ` ;
149
+ const json = await fetchDoc ( pagePath ) ;
150
+
151
+ return json as RepoPage ;
152
+ }
153
+
154
+ export async function queryBlogs (
155
+ product : string ,
156
+ q : FirestoreQuery
157
+ ) : Promise < QueryResult < BlogData > > {
158
+ const collectionPath = `/products/${ product } /blogs` ;
159
+ const json = await fetchQuery ( collectionPath , q ) ;
160
+
161
+ return json as QueryResult < BlogData > ;
162
+ }
163
+
164
+ export async function queryRepos (
165
+ product : string ,
166
+ q : FirestoreQuery
167
+ ) : Promise < QueryResult < RepoData > > {
168
+ const collectionPath = `/products/${ product } /repos` ;
169
+ const json = await fetchQuery ( collectionPath , q ) ;
126
170
127
- const snap = await ref . get ( ) ;
128
- return snap . data ( ) as RepoPage ;
171
+ return json as QueryResult < RepoData > ;
129
172
}
0 commit comments