@@ -14,6 +14,7 @@ type Post = {
14
14
title : string ;
15
15
url : string ;
16
16
body : string ;
17
+ date : number ;
17
18
} ;
18
19
19
20
type User = {
@@ -26,10 +27,16 @@ type Upvote = {
26
27
user_id : number ;
27
28
} ;
28
29
29
- type Upvoted = Post & { upvotes : number ; author : User } ;
30
+ type PostWithUpvoteIds = Post & { upvotes : number [ ] ; author : User } ;
30
31
31
- type ResourceInputs = {
32
- postsWithUpvotes : EagerCollection < number , Upvoted > ;
32
+ type PostWithUpvoteCount = Omit < Post , "author_id" > & {
33
+ upvotes : number ;
34
+ upvoted : boolean ;
35
+ author : User ;
36
+ } ;
37
+
38
+ type Session = User & {
39
+ user_id : number ;
33
40
} ;
34
41
35
42
const postgres = new PostgresExternalService ( {
@@ -41,9 +48,9 @@ const postgres = new PostgresExternalService({
41
48
} ) ;
42
49
43
50
class UpvotesMapper {
44
- mapEntry ( key : number , values : Values < Upvote > ) : Iterable < [ number , number ] > {
45
- const value = values . getUnique ( ) . post_id ;
46
- return [ [ value , key ] ] ;
51
+ mapEntry ( _key : number , values : Values < Upvote > ) : Iterable < [ number , number ] > {
52
+ const upvote : Upvote = values . getUnique ( ) ;
53
+ return [ [ upvote . post_id , upvote . user_id ] ] ;
47
54
}
48
55
}
49
56
@@ -53,47 +60,132 @@ class PostsMapper {
53
60
private upvotes : EagerCollection < number , number > ,
54
61
) { }
55
62
56
- mapEntry ( key : number , values : Values < Post > ) : Iterable < [ number , Upvoted ] > {
63
+ mapEntry (
64
+ key : number ,
65
+ values : Values < Post > ,
66
+ ) : Iterable < [ [ number , number ] , PostWithUpvoteIds ] > {
57
67
const post : Post = values . getUnique ( ) ;
58
- const upvotes = this . upvotes . getArray ( key ) . length ;
68
+ const upvotes : number [ ] = this . upvotes . getArray ( key ) ;
59
69
let author ;
60
70
try {
61
71
author = this . users . getUnique ( post . author_id ) ;
62
72
} catch {
63
73
author = { name : "unknown author" , email : "unknown email" } ;
64
74
}
65
- // Projecting all posts on key 0 so that they can later be sorted.
66
- return [ [ 0 , { ...post , upvotes, author } ] ] ;
75
+ return [ [ [ - upvotes . length , key ] , { ...post , upvotes, author } ] ] ;
67
76
}
68
77
}
69
78
70
- class SortingMapper {
71
- mapEntry ( key : number , values : Values < Upvoted > ) : Iterable < [ number , Upvoted ] > {
72
- const posts = values . toArray ( ) ;
73
- // Sorting in descending order of upvotes.
74
- posts . sort ( ( a , b ) => b . upvotes - a . upvotes ) ;
75
- return posts . map ( ( p ) => [ key , p ] ) ;
79
+ class CleanupMapper {
80
+ constructor ( private readonly session : Session | null ) { }
81
+
82
+ mapEntry (
83
+ key : [ number , number ] ,
84
+ values : Values < PostWithUpvoteIds > ,
85
+ ) : Iterable < [ number , PostWithUpvoteCount ] > {
86
+ const post = values . getUnique ( ) ;
87
+ let upvoted ;
88
+ if ( this . session === null ) upvoted = false ;
89
+ else upvoted = post . upvotes . includes ( this . session . user_id ) ;
90
+ const upvotes = post . upvotes . length ;
91
+ return [
92
+ [
93
+ key [ 1 ] ,
94
+ {
95
+ title : post . title ,
96
+ url : post . url ,
97
+ body : post . body ,
98
+ date : post . date ,
99
+ author : post . author ,
100
+ upvotes,
101
+ upvoted,
102
+ } ,
103
+ ] ,
104
+ ] ;
76
105
}
77
106
}
78
107
79
- class PostsResource implements Resource < ResourceInputs > {
108
+ type PostsResourceInputs = {
109
+ postsWithUpvotes : EagerCollection < [ number , number ] , PostWithUpvoteIds > ;
110
+ sessions : EagerCollection < string , Session > ;
111
+ } ;
112
+
113
+ type PostsResourceParams = { limit ?: number ; session_id ?: string } ;
114
+
115
+ class PostsResource implements Resource < PostsResourceInputs > {
80
116
private limit : number ;
117
+ private session_id : string ;
81
118
82
- constructor ( param : Json ) {
83
- if ( typeof param == "number" ) this . limit = param ;
84
- else this . limit = 25 ;
119
+ constructor ( jsonParams : Json ) {
120
+ const params = jsonParams as PostsResourceParams ;
121
+ if ( params . limit === undefined ) this . limit = 25 ;
122
+ else this . limit = params . limit ;
123
+ if ( params . session_id === undefined )
124
+ throw new Error ( "Missing required session_id." ) ;
125
+ else this . session_id = params . session_id as string ;
85
126
}
86
127
87
- instantiate ( collections : ResourceInputs ) : EagerCollection < number , Upvoted > {
88
- return collections . postsWithUpvotes . take ( this . limit ) . map ( SortingMapper ) ;
128
+ instantiate (
129
+ collections : PostsResourceInputs ,
130
+ ) : EagerCollection < number , PostWithUpvoteCount > {
131
+ let session ;
132
+ try {
133
+ session = collections . sessions . getUnique ( this . session_id ) ;
134
+ } catch {
135
+ session = null ;
136
+ }
137
+ return collections . postsWithUpvotes
138
+ . take ( this . limit )
139
+ . map ( CleanupMapper , session ) ;
89
140
}
90
141
}
91
142
92
- export const service : SkipService < { } , ResourceInputs > = {
93
- initialData : { } ,
94
- resources : { posts : PostsResource } ,
143
+ class FilterSessionMapper {
144
+ constructor ( private session_id : string ) { }
145
+
146
+ mapEntry ( key : string , values : Values < Session > ) : Iterable < [ number , Session ] > {
147
+ if ( key != this . session_id ) return [ ] ;
148
+ const sessions = values . toArray ( ) ;
149
+ if ( sessions . length > 0 ) return [ [ 0 , sessions [ 0 ] as Session ] ] ;
150
+ else return [ ] ;
151
+ }
152
+ }
153
+
154
+ type SessionsResourceInputs = {
155
+ sessions : EagerCollection < string , Session > ;
156
+ } ;
157
+
158
+ class SessionsResource implements Resource < SessionsResourceInputs > {
159
+ private session_id : string ;
160
+
161
+ constructor ( jsonParams : Json ) {
162
+ const params = jsonParams as PostsResourceParams ;
163
+ if ( params . session_id === undefined )
164
+ throw new Error ( "Missing required session_id." ) ;
165
+ else this . session_id = params . session_id as string ;
166
+ }
167
+
168
+ instantiate (
169
+ collections : SessionsResourceInputs ,
170
+ ) : EagerCollection < number , Session > {
171
+ return collections . sessions . map ( FilterSessionMapper , this . session_id ) ;
172
+ }
173
+ }
174
+
175
+ type PostsServiceInputs = {
176
+ sessions : EagerCollection < string , Session > ;
177
+ } ;
178
+
179
+ export const service : SkipService < PostsServiceInputs , PostsResourceInputs > = {
180
+ initialData : {
181
+ sessions : [ ] ,
182
+ } ,
183
+ resources : { posts : PostsResource , sessions : SessionsResource } ,
95
184
externalServices : { postgres } ,
96
- createGraph ( _ : { } , context : Context ) : ResourceInputs {
185
+ createGraph (
186
+ inputs : PostsServiceInputs ,
187
+ context : Context ,
188
+ ) : PostsResourceInputs {
97
189
const serialIDKey = { key : { col : "id" , type : "SERIAL" } } ;
98
190
const posts = context . useExternalResource < number , Post > ( {
99
191
service : "postgres" ,
@@ -116,6 +208,7 @@ export const service: SkipService<{}, ResourceInputs> = {
116
208
users ,
117
209
upvotes . map ( UpvotesMapper ) ,
118
210
) ,
211
+ sessions : inputs . sessions ,
119
212
} ;
120
213
} ,
121
214
} ;
0 commit comments