Skip to content

Commit 0e671d2

Browse files
committed
Add nice stats
1 parent 2d6d3c0 commit 0e671d2

6 files changed

Lines changed: 122 additions & 31 deletions

File tree

backend/api/src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import swaggerUi from "swagger-ui-express"
5454
import * as fs from "fs"
5555
import {sendSearchNotifications} from "api/send-search-notifications";
5656
import {sendDiscordMessage} from "common/discord/core";
57+
import {getMessagesCount} from "api/get-messages-count";
5758

5859
const allowCorsUnrestricted: RequestHandler = cors({})
5960

@@ -165,6 +166,7 @@ const handlers: { [k in APIPath]: APIHandler<k> } = {
165166
'get-channel-messages': getChannelMessages,
166167
'get-channel-seen-time': getLastSeenChannelTime,
167168
'set-channel-seen-time': setChannelLastSeenTime,
169+
'get-messages-count': getMessagesCount,
168170
}
169171

170172
Object.entries(handlers).forEach(([path, handler]) => {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {APIHandler} from './helpers/endpoint'
2+
import {createSupabaseDirectClient} from "shared/supabase/init";
3+
4+
export const getMessagesCount: APIHandler<'get-messages-count'> = async (_, auth) => {
5+
const pg = createSupabaseDirectClient()
6+
const result = await pg.one(
7+
`
8+
SELECT COUNT(*) AS count
9+
FROM private_user_messages;
10+
`,
11+
[]
12+
);
13+
const count = Number(result.count);
14+
console.debug('private_user_messages count:', count);
15+
return {
16+
count: count,
17+
}
18+
}

common/src/api/schema.ts

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ import {
44
baseProfilesSchema,
55
arraybeSchema,
66
} from 'common/api/zod-types'
7-
import { PrivateChatMessage } from 'common/chat-message'
8-
import { CompatibilityScore } from 'common/love/compatibility-score'
9-
import { MAX_COMPATIBILITY_QUESTION_LENGTH } from 'common/love/constants'
10-
import { Profile, ProfileRow } from 'common/love/profile'
11-
import { Row } from 'common/supabase/utils'
12-
import { PrivateUser, User } from 'common/user'
13-
import { z } from 'zod'
14-
import { LikeData, ShipData } from './love-types'
15-
import { DisplayUser, FullUser } from './user-types'
16-
import { PrivateMessageChannel } from 'common/supabase/private-messages'
17-
import { Notification } from 'common/notifications'
18-
import { arrify } from 'common/util/array'
19-
import { notification_preference } from 'common/user-notification-preferences'
7+
import {PrivateChatMessage} from 'common/chat-message'
8+
import {CompatibilityScore} from 'common/love/compatibility-score'
9+
import {MAX_COMPATIBILITY_QUESTION_LENGTH} from 'common/love/constants'
10+
import {Profile, ProfileRow} from 'common/love/profile'
11+
import {Row} from 'common/supabase/utils'
12+
import {PrivateUser, User} from 'common/user'
13+
import {z} from 'zod'
14+
import {LikeData, ShipData} from './love-types'
15+
import {DisplayUser, FullUser} from './user-types'
16+
import {PrivateMessageChannel} from 'common/supabase/private-messages'
17+
import {Notification} from 'common/notifications'
18+
import {arrify} from 'common/util/array'
19+
import {notification_preference} from 'common/user-notification-preferences'
2020

2121
// mqp: very unscientific, just balancing our willingness to accept load
2222
// with user willingness to put up with stale data
@@ -59,12 +59,12 @@ export const API = (_apiTypeCheck = {
5959
'user/by-id/:id/block': {
6060
method: 'POST',
6161
authed: true,
62-
props: z.object({ id: z.string() }).strict(),
62+
props: z.object({id: z.string()}).strict(),
6363
},
6464
'user/by-id/:id/unblock': {
6565
method: 'POST',
6666
authed: true,
67-
props: z.object({ id: z.string() }).strict(),
67+
props: z.object({id: z.string()}).strict(),
6868
},
6969
'ban-user': {
7070
method: 'POST',
@@ -176,28 +176,28 @@ export const API = (_apiTypeCheck = {
176176
authed: false,
177177
cache: DEFAULT_CACHE_STRATEGY,
178178
returns: {} as FullUser,
179-
props: z.object({ username: z.string() }).strict(),
179+
props: z.object({username: z.string()}).strict(),
180180
},
181181
'user/:username/lite': {
182182
method: 'GET',
183183
authed: false,
184184
cache: DEFAULT_CACHE_STRATEGY,
185185
returns: {} as DisplayUser,
186-
props: z.object({ username: z.string() }).strict(),
186+
props: z.object({username: z.string()}).strict(),
187187
},
188188
'user/by-id/:id': {
189189
method: 'GET',
190190
authed: false,
191191
cache: DEFAULT_CACHE_STRATEGY,
192192
returns: {} as FullUser,
193-
props: z.object({ id: z.string() }).strict(),
193+
props: z.object({id: z.string()}).strict(),
194194
},
195195
'user/by-id/:id/lite': {
196196
method: 'GET',
197197
authed: false,
198198
cache: DEFAULT_CACHE_STRATEGY,
199199
returns: {} as DisplayUser,
200-
props: z.object({ id: z.string() }).strict(),
200+
props: z.object({id: z.string()}).strict(),
201201
},
202202
'search-users': {
203203
method: 'GET',
@@ -215,7 +215,7 @@ export const API = (_apiTypeCheck = {
215215
'compatible-profiles': {
216216
method: 'GET',
217217
authed: false,
218-
props: z.object({ userId: z.string() }),
218+
props: z.object({userId: z.string()}),
219219
returns: {} as {
220220
profile: Profile
221221
compatibleProfiles: Profile[]
@@ -227,7 +227,7 @@ export const API = (_apiTypeCheck = {
227227
'remove-pinned-photo': {
228228
method: 'POST',
229229
authed: true,
230-
returns: { success: true },
230+
returns: {success: true},
231231
props: z
232232
.object({
233233
userId: z.string(),
@@ -338,7 +338,7 @@ export const API = (_apiTypeCheck = {
338338
'get-profile-answers': {
339339
method: 'GET',
340340
authed: false,
341-
props: z.object({ userId: z.string() }).strict(),
341+
props: z.object({userId: z.string()}).strict(),
342342
returns: {} as {
343343
status: 'success'
344344
answers: Row<'love_compatibility_answers'>[]
@@ -477,6 +477,12 @@ export const API = (_apiTypeCheck = {
477477
radius: z.number().min(1).max(500),
478478
}),
479479
},
480+
'get-messages-count': {
481+
method: 'GET',
482+
authed: false,
483+
props: z.object({}),
484+
returns: {} as { count: number },
485+
},
480486
} as const)
481487

482488
export type APIPath = keyof typeof API
@@ -488,8 +494,8 @@ export type ValidatedAPIParams<N extends APIPath> = z.output<
488494
>
489495

490496
export type APIResponse<N extends APIPath> = APISchema<N> extends {
491-
returns: Record<string, any>
492-
}
497+
returns: Record<string, any>
498+
}
493499
? APISchema<N>['returns']
494500
: void
495501

web/components/widgets/card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const Card = forwardRef(function Card(
99
return (
1010
<div
1111
className={clsx(
12-
'bg-canvas-0 border-ink-300 cursor-pointer rounded-lg border transition-shadow hover:shadow-md focus:shadow-md',
12+
'bg-canvas-0 border-ink-300 rounded-lg border transition-shadow hover:shadow-md focus:shadow-md',
1313
className
1414
)}
1515
ref={ref}

web/lib/supabase/users.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {db} from './db'
22
import {run} from 'common/supabase/utils'
3-
import {APIError, api} from 'web/lib/api'
3+
import {api, APIError} from 'web/lib/api'
44
import {unauthedApi} from 'common/util/api'
55
import type {DisplayUser} from 'common/api/user-types'
66
import {MIN_BIO_LENGTH} from "common/constants";
@@ -75,4 +75,21 @@ export async function getProfilesWithBioCreations() {
7575
.order('created_time')
7676
)
7777
return data
78-
}
78+
}
79+
80+
export async function getCount(table: string) {
81+
if (table == 'private_user_messages') {
82+
const result = await api('get-messages-count')
83+
return result.count
84+
}
85+
const {count} = await run(
86+
db
87+
.from(table)
88+
.select('*', {count: 'exact', head: true})
89+
)
90+
return count;
91+
}
92+
93+
// export async function getNumberProfiles() {
94+
// return await getCount('profiles');
95+
// }

web/pages/charts.tsx

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,61 @@
11
import {LovePage} from "web/components/love-page";
22
import ChartMembers from "web/components/widgets/charts";
3+
import {getCount} from "web/lib/supabase/users";
4+
import {useEffect, useState} from "react";
5+
import StatBox from "web/components/widgets/stat-box";
6+
import clsx from "clsx";
7+
import {Col} from "web/components/layout/col";
38

49
export default function Charts() {
10+
const [data, setData] = useState<Record<string, number | null>>({})
11+
12+
useEffect(() => {
13+
async function load() {
14+
const tables = [
15+
'profiles',
16+
'bookmarked_searches',
17+
'private_user_message_channels',
18+
'private_user_messages',
19+
'profile_comments',
20+
'love_compatibility_answers',
21+
] as const
22+
23+
const settled = await Promise.allSettled(
24+
tables.map((t) => getCount(t))
25+
)
26+
27+
const result: Record<string, number | null> = {}
28+
settled.forEach((res, i) => {
29+
const key = tables[i]
30+
if (res.status === 'fulfilled') result[key] = res.value
31+
else result[key] = null
32+
})
33+
34+
setData(result)
35+
}
36+
37+
void load()
38+
}, [])
39+
540
return (
6-
<LovePage
7-
trackPageView={'charts'}
8-
>
41+
<LovePage trackPageView={'charts'}>
942
<h1 className="text-3xl font-semibold text-center mb-6">Community Growth over Time</h1>
10-
<ChartMembers/>
43+
<Col className={'mx-4 mb-8'}>
44+
<ChartMembers/>
45+
<Col
46+
className={clsx(
47+
'pb-[58px] lg:pb-0', // bottom bar padding
48+
'text-ink-1000 mx-auto w-full grid grid-cols-1 gap-8 max-w-3xl sm:grid-cols-2 lg:min-h-0 lg:pt-4 mt-4',
49+
)}
50+
>
51+
{!!data.profiles && <StatBox value={data.profiles} label={'Members'} />}
52+
{!!data.bookmarked_searches && <StatBox value={data.bookmarked_searches} label={'Searches Bookmarked'} />}
53+
{!!data.private_user_message_channels && <StatBox value={data.private_user_message_channels} label={'Discussions'} />}
54+
{!!data.private_user_messages && <StatBox value={data.private_user_messages} label={'Messages'} />}
55+
{!!data.profile_comments && <StatBox value={data.profile_comments} label={'Endorsements'} />}
56+
{!!data.love_compatibility_answers && <StatBox value={data.love_compatibility_answers} label={'Prompts Answered'} />}
57+
</Col>
58+
</Col>
1159
</LovePage>
1260
);
1361
}

0 commit comments

Comments
 (0)