@@ -14,6 +14,7 @@ type Post = {
1414 title : string ;
1515 url : string ;
1616 body : string ;
17+ date : number ;
1718} ;
1819
1920type User = {
@@ -26,10 +27,16 @@ type Upvote = {
2627 user_id : number ;
2728} ;
2829
29- type Upvoted = Post & { upvotes : number ; author : User } ;
30+ type PostWithUpvoteIds = Post & { upvotes : number [ ] ; author : User } ;
3031
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 ;
3340} ;
3441
3542const postgres = new PostgresExternalService ( {
@@ -41,9 +48,9 @@ const postgres = new PostgresExternalService({
4148} ) ;
4249
4350class 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 ] ] ;
4754 }
4855}
4956
@@ -53,47 +60,132 @@ class PostsMapper {
5360 private upvotes : EagerCollection < number , number > ,
5461 ) { }
5562
56- mapEntry ( key : number , values : Values < Post > ) : Iterable < [ number , Upvoted ] > {
63+ mapEntry (
64+ key : number ,
65+ values : Values < Post > ,
66+ ) : Iterable < [ [ number , number ] , PostWithUpvoteIds ] > {
5767 const post : Post = values . getUnique ( ) ;
58- const upvotes = this . upvotes . getArray ( key ) . length ;
68+ const upvotes : number [ ] = this . upvotes . getArray ( key ) ;
5969 let author ;
6070 try {
6171 author = this . users . getUnique ( post . author_id ) ;
6272 } catch {
6373 author = { name : "unknown author" , email : "unknown email" } ;
6474 }
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 } ] ] ;
6776 }
6877}
6978
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+ ] ;
76105 }
77106}
78107
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 > {
80116 private limit : number ;
117+ private session_id : string ;
81118
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 ;
85126 }
86127
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 ) ;
89140 }
90141}
91142
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 } ,
95184 externalServices : { postgres } ,
96- createGraph ( _ : { } , context : Context ) : ResourceInputs {
185+ createGraph (
186+ inputs : PostsServiceInputs ,
187+ context : Context ,
188+ ) : PostsResourceInputs {
97189 const serialIDKey = { key : { col : "id" , type : "SERIAL" } } ;
98190 const posts = context . useExternalResource < number , Post > ( {
99191 service : "postgres" ,
@@ -116,6 +208,7 @@ export const service: SkipService<{}, ResourceInputs> = {
116208 users ,
117209 upvotes . map ( UpvotesMapper ) ,
118210 ) ,
211+ sessions : inputs . sessions ,
119212 } ;
120213 } ,
121214} ;
0 commit comments