@@ -33,6 +33,72 @@ export async function getSub (parent, { name }, { models, me }) {
3333 } )
3434}
3535
36+ export async function topSubs ( parent , { query, cursor, when, from, to, limit, by = 'revenue' } , { models, me } ) {
37+ const decodedCursor = decodeCursor ( cursor )
38+ const [ fromDate , toDate ] = whenRange ( when , from , to || decodeCursor . time )
39+ const granularity = timeUnitForRange ( [ fromDate , toDate ] ) . toUpperCase ( )
40+
41+ let column
42+ switch ( by ) {
43+ case 'revenue' : column = Prisma . sql `revenue` ; break
44+ case 'spent' : column = Prisma . sql `spent` ; break
45+ case 'stacked' : column = Prisma . sql `stacked` ; break
46+ case 'items' : column = Prisma . sql `nitems` ; break
47+ default : throw new GqlInputError ( 'invalid sort' )
48+ }
49+
50+ const subs = await models . $queryRaw `
51+ WITH user_subs AS (
52+ ${ query }
53+ ),
54+ sub_outgoing AS (
55+ SELECT user_subs.name,
56+ COALESCE(floor(sum("AggPayOut"."sumMtokens") FILTER (WHERE "AggPayOut"."payOutType" = 'TERRITORY_REVENUE') / 1000), 0) as revenue,
57+ COALESCE(floor(sum("AggPayOut"."sumMtokens") FILTER (WHERE "AggPayOut"."payOutType" = 'ZAP') / 1000), 0) as stacked
58+ FROM user_subs
59+ LEFT JOIN "AggPayOut" ON "AggPayOut"."subName" = user_subs.name
60+ WHERE "AggPayOut"."timeBucket" >= ${ fromDate }
61+ AND "AggPayOut"."timeBucket" <= ${ toDate }
62+ AND "AggPayOut"."granularity" = ${ granularity } ::"AggGranularity"
63+ AND "AggPayOut"."slice" = 'SUB_BY_TYPE'
64+ AND "AggPayOut"."payInType" IS NULL
65+ GROUP BY user_subs.name
66+ ),
67+ sub_incoming AS (
68+ SELECT user_subs.name,
69+ floor(COALESCE(sum("AggPayIn"."sumMcost"), 0) / 1000) as spent,
70+ sum("AggPayIn"."countGroup") FILTER (WHERE "AggPayIn"."payInType" = 'ITEM_CREATE') as nitems
71+ FROM user_subs
72+ LEFT JOIN "AggPayIn" ON "AggPayIn"."subName" = user_subs.name
73+ WHERE "AggPayIn"."timeBucket" >= ${ fromDate }
74+ AND "AggPayIn"."timeBucket" <= ${ toDate }
75+ AND "AggPayIn"."granularity" = ${ granularity } ::"AggGranularity"
76+ AND "AggPayIn"."slice" = 'SUB_BY_TYPE'
77+ AND "AggPayIn"."subName" IS NOT NULL
78+ AND "AggPayIn"."payInType" <> 'DEFUNCT_TERRITORY_DAILY_PAYOUT'
79+ GROUP BY user_subs.name
80+ ),
81+ sub_stats AS (
82+ SELECT COALESCE(sub_outgoing.name, sub_incoming.name) as name,
83+ COALESCE(sub_outgoing."revenue", 0) as revenue,
84+ COALESCE(sub_outgoing."stacked", 0) as stacked,
85+ COALESCE(sub_incoming."spent", 0) as spent,
86+ COALESCE(sub_incoming."nitems", 0) as nitems
87+ FROM sub_outgoing
88+ FULL JOIN sub_incoming ON sub_outgoing.name = sub_incoming.name
89+ )
90+ SELECT * FROM sub_stats
91+ JOIN "Sub" ON sub_stats.name = "Sub".name
92+ ORDER BY ${ column } DESC NULLS LAST, "Sub".created_at ASC
93+ OFFSET ${ decodedCursor . offset }
94+ LIMIT ${ limit } `
95+
96+ return {
97+ cursor : subs . length === limit ? nextCursorEncoded ( decodedCursor , limit ) : null ,
98+ subs
99+ }
100+ }
101+
36102export default {
37103 Query : {
38104 sub : getSub ,
@@ -89,147 +155,51 @@ export default {
89155 return latest ?. createdAt
90156 } ,
91157 topSubs : async ( parent , { cursor, when, by = 'stacked' , from, to, limit } , { models, me } ) => {
92- const decodedCursor = decodeCursor ( cursor )
93- const [ fromDate , toDate ] = whenRange ( when , from , to || decodeCursor . time )
94- const granularity = timeUnitForRange ( [ fromDate , toDate ] ) . toUpperCase ( )
95-
96- let column
97- switch ( by ) {
98- case 'revenue' : column = Prisma . sql `revenue` ; break
99- case 'spent' : column = Prisma . sql `spent` ; break
100- case 'stacked' : column = Prisma . sql `stacked` ; break
101- case 'items' : column = Prisma . sql `nitems` ; break
102- default : throw new GqlInputError ( 'invalid sort' )
103- }
104-
105- const subs = await models . $queryRaw `
106- WITH sub_outgoing AS (
107- SELECT "AggPayIn"."subName", floor(coalesce(sum("AggPayIn"."sumMcost"), 0) / 1000) as spent,
108- sum("AggPayIn"."countGroup") FILTER (WHERE "AggPayIn"."payInType" = 'ITEM_CREATE') as nitems
109- FROM "AggPayIn"
110- WHERE "AggPayIn"."timeBucket" >= ${ fromDate }
111- AND "AggPayIn"."timeBucket" <= ${ toDate }
112- AND "AggPayIn"."granularity" = ${ granularity } ::"AggGranularity"
113- AND "AggPayIn"."slice" = 'SUB_BY_TYPE'
114- AND "AggPayIn"."subName" IS NOT NULL
115- AND "AggPayIn"."payInType" <> 'DEFUNCT_TERRITORY_DAILY_PAYOUT'
116- GROUP BY "AggPayIn"."subName"
117- ),
118- sub_stats AS (
119- SELECT "AggPayOut"."subName", COALESCE(sub_outgoing."spent", 0) as spent,
120- COALESCE(sub_outgoing."nitems", 0) as nitems,
121- floor(coalesce(sum("AggPayOut"."sumMtokens") FILTER (WHERE "AggPayOut"."payOutType" = 'ZAP'), 0) / 1000) as stacked,
122- floor(coalesce(sum("AggPayOut"."sumMtokens") FILTER (WHERE "AggPayOut"."payOutType" = 'TERRITORY_REVENUE'), 0) / 1000) as revenue
123- FROM "AggPayOut"
124- LEFT JOIN sub_outgoing ON "AggPayOut"."subName" = sub_outgoing."subName"
125- WHERE "AggPayOut"."timeBucket" >= ${ fromDate }
126- AND "AggPayOut"."timeBucket" <= ${ toDate }
127- AND "AggPayOut"."granularity" = ${ granularity } ::"AggGranularity"
128- AND "AggPayOut"."slice" = 'SUB_BY_TYPE'
129- AND "AggPayOut"."subName" IS NOT NULL
130- AND "AggPayOut"."payInType" IS NULL
131- GROUP BY "AggPayOut"."subName", sub_outgoing."spent", sub_outgoing."nitems"
132- )
133- SELECT * FROM sub_stats
134- JOIN "Sub" ON sub_stats."subName" = "Sub".name
135- ORDER BY ${ column } DESC NULLS LAST, "Sub".created_at ASC
136- OFFSET ${ decodedCursor . offset }
137- LIMIT ${ limit } `
158+ const query = Prisma . sql `
159+ SELECT "Sub".name
160+ FROM "Sub"
161+ WHERE "Sub".status <> 'STOPPED'
162+ GROUP BY "Sub".name
163+ `
138164
139- return {
140- cursor : subs . length === limit ? nextCursorEncoded ( decodedCursor , limit ) : null ,
141- subs
142- }
165+ return await topSubs ( parent , { query, cursor, when, from, to, limit, by } , { models, me } )
143166 } ,
144- userSubs : async ( _parent , { name, cursor, when, by = 'revenue' , from, to, limit } , { models, me } ) => {
167+ userSubs : async ( parent , { name, cursor, when, by = 'revenue' , from, to, limit } , { models, me } ) => {
145168 if ( ! name ) {
146169 throw new GqlInputError ( 'must supply user name' )
147170 }
148171
149- const decodedCursor = decodeCursor ( cursor )
150- const [ fromDate , toDate ] = whenRange ( when , from , to || decodeCursor . time )
151- const granularity = timeUnitForRange ( [ fromDate , toDate ] ) . toUpperCase ( )
152-
153- let column
154- switch ( by ) {
155- case 'revenue' : column = Prisma . sql `revenue` ; break
156- case 'spent' : column = Prisma . sql `spent` ; break
157- case 'stacked' : column = Prisma . sql `stacked` ; break
158- case 'items' : column = Prisma . sql `nitems` ; break
159- default : throw new GqlInputError ( 'invalid sort' )
160- }
161-
162- const subs = await models . $queryRaw `
163- WITH user_subs AS (
164- SELECT "Sub".name
165- FROM "Sub"
166- JOIN users ON users.id = "Sub"."userId"
167- WHERE users.name = ${ name }
168- AND "Sub".status = 'ACTIVE'
169- GROUP BY "Sub".name
170- ),
171- sub_outgoing AS (
172- SELECT user_subs.name,
173- COALESCE(floor(sum("AggPayOut"."sumMtokens") FILTER (WHERE "AggPayOut"."payOutType" = 'TERRITORY_REVENUE') / 1000), 0) as revenue,
174- COALESCE(floor(sum("AggPayOut"."sumMtokens") FILTER (WHERE "AggPayOut"."payOutType" = 'ZAP') / 1000), 0) as stacked
175- FROM user_subs
176- LEFT JOIN "AggPayOut" ON "AggPayOut"."subName" = user_subs.name
177- WHERE "AggPayOut"."timeBucket" >= ${ fromDate }
178- AND "AggPayOut"."timeBucket" <= ${ toDate }
179- AND "AggPayOut"."granularity" = ${ granularity } ::"AggGranularity"
180- AND "AggPayOut"."slice" = 'SUB_BY_TYPE'
181- AND "AggPayOut"."payInType" IS NULL
182- GROUP BY user_subs.name
183- ),
184- sub_stats AS (
185- SELECT sub_outgoing.name,
186- COALESCE(sub_outgoing."revenue", 0) as revenue,
187- COALESCE(sub_outgoing."stacked", 0) as stacked,
188- COALESCE(floor(sum("AggPayIn"."sumMcost") / 1000), 0) as spent,
189- COALESCE(sum("AggPayIn"."countGroup") FILTER (WHERE "AggPayIn"."payInType" = 'ITEM_CREATE'), 0) as nitems
190- FROM sub_outgoing
191- LEFT JOIN "AggPayIn" ON "AggPayIn"."subName" = sub_outgoing.name
192- WHERE "AggPayIn"."timeBucket" >= ${ fromDate }
193- AND "AggPayIn"."timeBucket" <= ${ toDate }
194- AND "AggPayIn"."granularity" = ${ granularity } ::"AggGranularity"
195- AND "AggPayIn"."slice" = 'SUB_BY_TYPE'
196- AND "AggPayIn"."payInType" <> 'DEFUNCT_TERRITORY_DAILY_PAYOUT'
197- GROUP BY sub_outgoing.name, sub_outgoing.revenue, sub_outgoing.stacked
198- )
199- SELECT * FROM sub_stats
200- JOIN "Sub" ON sub_stats.name = "Sub".name
201- ORDER BY ${ column } DESC NULLS LAST, "Sub".created_at ASC
202- OFFSET ${ decodedCursor . offset }
203- LIMIT ${ limit } `
172+ const query = Prisma . sql `
173+ SELECT "Sub".name
174+ FROM "Sub"
175+ JOIN users ON users.id = "Sub"."userId" AND users.name = ${ name }
176+ WHERE "Sub".status <> 'STOPPED'
177+ GROUP BY "Sub".name
178+ `
204179
205- return {
206- cursor : subs . length === limit ? nextCursorEncoded ( decodedCursor , limit ) : null ,
207- subs
208- }
180+ return await topSubs ( parent , { query, cursor, when, from, to, limit, by } , { models, me } )
209181 } ,
210182 mySubscribedSubs : async ( parent , { cursor } , { models, me } ) => {
211183 if ( ! me ) {
212184 throw new GqlAuthenticationError ( )
213185 }
214186
215- const decodedCursor = decodeCursor ( cursor )
216- const subs = await models . $queryRaw `
217- SELECT "Sub".*,
218- "MuteSub"."userId" IS NOT NULL as "meMuteSub",
219- TRUE as "meSubscription"
187+ const query = Prisma . sql `
188+ SELECT "Sub".name
220189 FROM "SubSubscription"
221190 JOIN "Sub" ON "SubSubscription"."subName" = "Sub".name
222- LEFT JOIN "MuteSub" ON "MuteSub"."subName" = "Sub".name AND "MuteSub"."userId" = ${ me . id }
223191 WHERE "SubSubscription"."userId" = ${ me . id }
224- AND "Sub".status <> 'STOPPED'
225- ORDER BY "Sub".name ASC
226- OFFSET ${ decodedCursor . offset }
227- LIMIT ${ LIMIT }
192+ AND "Sub".status <> 'STOPPED'
193+ GROUP BY "Sub".name
228194 `
229195
196+ const { subs, cursor : mySubscribedSubsCursor } = await topSubs ( parent , { query, cursor, when : 'forever' , limit : LIMIT } , { models, me } )
230197 return {
231- cursor : subs . length === LIMIT ? nextCursorEncoded ( decodedCursor , LIMIT ) : null ,
232- subs
198+ cursor : mySubscribedSubsCursor ,
199+ subs : subs . map ( sub => ( {
200+ ...sub ,
201+ meSubscription : true
202+ } ) )
233203 }
234204 }
235205 } ,
0 commit comments