mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-24 17:41:27 -04:00
Fix get message being called for each convo
This commit is contained in:
@@ -6,6 +6,7 @@ import {createVote} from 'api/create-vote'
|
||||
import {deleteMessage} from 'api/delete-message'
|
||||
import {editMessage} from 'api/edit-message'
|
||||
import {getHiddenProfiles} from 'api/get-hidden-profiles'
|
||||
import {getLastMessages} from 'api/get-last-messages'
|
||||
import {getMessagesCountEndpoint} from 'api/get-messages-count'
|
||||
import {getOptions} from 'api/get-options'
|
||||
import {
|
||||
@@ -597,6 +598,7 @@ const handlers: {[k in APIPath]: APIHandler<k>} = {
|
||||
'get-channel-memberships': getChannelMemberships,
|
||||
'get-channel-messages': getChannelMessagesEndpoint,
|
||||
'get-channel-seen-time': getLastSeenChannelTime,
|
||||
'get-last-messages': getLastMessages,
|
||||
'get-compatibility-questions': getCompatibilityQuestions,
|
||||
'get-likes-and-ships': getLikesAndShips,
|
||||
'get-messages-count': getMessagesCountEndpoint,
|
||||
|
||||
33
backend/api/src/get-last-messages.ts
Normal file
33
backend/api/src/get-last-messages.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {PrivateChatMessage} from 'common/chat-message'
|
||||
import {createSupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {convertPrivateChatMessage} from 'shared/supabase/messages'
|
||||
|
||||
import {APIHandler} from './helpers/endpoint'
|
||||
|
||||
export const getLastMessages: APIHandler<'get-last-messages'> = async (props, auth) => {
|
||||
const pg = createSupabaseDirectClient()
|
||||
const {channelIds} = props
|
||||
|
||||
const messages = await pg.map(
|
||||
`select distinct on (channel_id) channel_id, id, user_id, content, created_time, visibility, ciphertext, iv, tag
|
||||
from private_user_messages
|
||||
where visibility != 'system_status'
|
||||
and channel_id in (
|
||||
select channel_id from private_user_message_channel_members
|
||||
where user_id = $1 and not status = 'left'
|
||||
)
|
||||
${channelIds ? 'and channel_id = any ($2)' : ''}
|
||||
order by channel_id, created_time desc
|
||||
`,
|
||||
[auth.uid, channelIds],
|
||||
convertPrivateChatMessage,
|
||||
)
|
||||
|
||||
return messages.reduce(
|
||||
(acc, msg) => {
|
||||
acc[Number(msg.channelId)] = msg
|
||||
return acc
|
||||
},
|
||||
{} as Record<number, PrivateChatMessage>,
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ProfileRow} from 'common/profiles/profile'
|
||||
import {convertUserToDb} from 'common/supabase/users'
|
||||
import {convertUserToSQL} from 'common/supabase/users'
|
||||
import {User} from 'common/user'
|
||||
import {removeUndefinedProps} from 'common/util/object'
|
||||
import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
|
||||
@@ -20,7 +20,7 @@ export const updateUser = async (id: string, updated: Partial<User>) => {
|
||||
if (!updated) return
|
||||
const fullUpdate = {id, ...updated}
|
||||
const pg = createSupabaseDirectClient()
|
||||
const result = await update(pg, 'users', 'id', convertUserToDb(fullUpdate))
|
||||
const result = await update(pg, 'users', 'id', convertUserToSQL(fullUpdate))
|
||||
broadcastUpdatedUser(fullUpdate)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -871,6 +871,17 @@ export const API = (_apiTypeCheck = {
|
||||
summary: 'Retrieve messages for a private channel',
|
||||
tag: 'Messages',
|
||||
},
|
||||
'get-last-messages': {
|
||||
method: 'GET',
|
||||
authed: true,
|
||||
rateLimited: false,
|
||||
props: z.object({
|
||||
channelIds: z.array(z.coerce.number()).optional(),
|
||||
}),
|
||||
returns: {} as Record<number, PrivateChatMessage>,
|
||||
summary: 'Get last message for each channel',
|
||||
tag: 'Messages',
|
||||
},
|
||||
'get-channel-seen-time': {
|
||||
method: 'GET',
|
||||
authed: true,
|
||||
|
||||
@@ -1,48 +1,45 @@
|
||||
import {PrivateUser, User} from 'common/user'
|
||||
import {removeUndefinedProps} from 'common/util/object'
|
||||
|
||||
import {millisToTs, Row, run, SupabaseClient, tsToMillis} from './utils'
|
||||
import {
|
||||
convertObjectToSQLRow,
|
||||
convertSQLtoTS,
|
||||
millisToTs,
|
||||
Row,
|
||||
run,
|
||||
SupabaseClient,
|
||||
tsToMillis,
|
||||
} from './utils'
|
||||
|
||||
export async function getUserForStaticProps(db: SupabaseClient, username: string) {
|
||||
const {data} = await run(db.from('users').select().ilike('username', username))
|
||||
return convertUser(data[0] ?? null)
|
||||
}
|
||||
|
||||
function toDb(row: any): any {
|
||||
return {
|
||||
...(row.data as any),
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
name: row.name,
|
||||
avatarUrl: row.avatar_url,
|
||||
isBannedFromPosting: row.is_banned_from_posting,
|
||||
createdTime: row.created_time ? tsToMillis(row.created_time) : undefined,
|
||||
}
|
||||
function toTS(row: any): any {
|
||||
return convertSQLtoTS<'users', User>(row, {
|
||||
created_time: tsToMillis as any,
|
||||
})
|
||||
}
|
||||
|
||||
// From DB to typescript
|
||||
export function convertUser(row: Row<'users'>): User
|
||||
export function convertUser(row: Row<'users'> | null): User | null {
|
||||
if (!row) return null
|
||||
return toDb(row)
|
||||
return toTS(row)
|
||||
}
|
||||
|
||||
export function convertPartialUser(row: Partial<Row<'users'>>): Partial<User> {
|
||||
return removeUndefinedProps(toDb(row))
|
||||
return removeUndefinedProps(toTS(row))
|
||||
}
|
||||
|
||||
// Reciprocal of convertUser, from typescript to DB
|
||||
export function convertUserToDb(user: Partial<User>): Partial<Row<'users'>>
|
||||
export function convertUserToDb(user: Partial<User> | null): Partial<Row<'users'>> | null {
|
||||
export function convertUserToSQL(user: Partial<User>): Partial<Row<'users'>>
|
||||
export function convertUserToSQL(user: Partial<User> | null): Partial<Row<'users'>> | null {
|
||||
if (!user) return null
|
||||
|
||||
return removeUndefinedProps({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
avatar_url: user.avatarUrl,
|
||||
is_banned_from_posting: user.isBannedFromPosting,
|
||||
created_time: millisToTs(user.createdTime),
|
||||
return convertObjectToSQLRow<'users', Partial<Row<'users'>>>(user, {
|
||||
created_time: millisToTs as any,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -147,8 +147,9 @@ export const convertSQLtoTS = <R extends Selectable, T extends Record<string, an
|
||||
else return {...newRows} as T
|
||||
}
|
||||
|
||||
export const convertObjectToSQLRow = <T extends Record<string, any>, R extends Selectable>(
|
||||
export const convertObjectToSQLRow = <R extends Selectable, T extends Record<string, any>>(
|
||||
objData: Partial<T>,
|
||||
converters: TypeConverter<any, any>,
|
||||
) => {
|
||||
const entries = Object.entries(objData)
|
||||
|
||||
@@ -156,9 +157,12 @@ export const convertObjectToSQLRow = <T extends Record<string, any>, R extends S
|
||||
.map((entry) => {
|
||||
const [key, val] = entry as [string, T[keyof T]]
|
||||
|
||||
const convert = converters[key]
|
||||
if (convert === false) return null
|
||||
const decamelizeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase()
|
||||
const jsVal = convert != null ? convert(val) : val
|
||||
|
||||
return [decamelizeKey, val]
|
||||
return [decamelizeKey, jsVal]
|
||||
})
|
||||
.filter((x) => x != null)
|
||||
|
||||
|
||||
30
web/hooks/use-last-private-messages.ts
Normal file
30
web/hooks/use-last-private-messages.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {PrivateChatMessage} from 'common/chat-message'
|
||||
import {useEffect} from 'react'
|
||||
import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state'
|
||||
import {api} from 'web/lib/api'
|
||||
|
||||
export function useLastPrivateMessages(
|
||||
userId: string | undefined,
|
||||
channelIds?: number[] | undefined,
|
||||
): Record<number, PrivateChatMessage> {
|
||||
const key = `last-messages-${userId}`
|
||||
const [lastMessages, setLastMessages] = usePersistentLocalState<
|
||||
Record<number, PrivateChatMessage>
|
||||
>({}, key)
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId) {
|
||||
setLastMessages({})
|
||||
return
|
||||
}
|
||||
|
||||
const fetchLastMessages = async () => {
|
||||
const messages = await api('get-last-messages', {channelIds})
|
||||
setLastMessages(messages)
|
||||
}
|
||||
|
||||
fetchLastMessages()
|
||||
}, [userId, channelIds?.join(',')])
|
||||
|
||||
return lastMessages
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import clsx from 'clsx'
|
||||
import {PrivateChatMessage} from 'common/chat-message'
|
||||
import {PrivateMessageChannel} from 'common/supabase/private-messages'
|
||||
import {User} from 'common/user'
|
||||
import {parseJsonContentToText} from 'common/util/parse'
|
||||
@@ -15,8 +16,8 @@ import {Avatar} from 'web/components/widgets/avatar'
|
||||
import {Title} from 'web/components/widgets/title'
|
||||
import {BannedBadge} from 'web/components/widgets/user-link'
|
||||
import {useFirebaseUser} from 'web/hooks/use-firebase-user'
|
||||
import {useLastPrivateMessages} from 'web/hooks/use-last-private-messages'
|
||||
import {
|
||||
usePrivateMessages,
|
||||
useSortedPrivateMessageMemberships,
|
||||
useUnseenPrivateMessageChannels,
|
||||
} from 'web/hooks/use-private-messages'
|
||||
@@ -54,6 +55,7 @@ export function MessagesContent(props: {currentUser: User}) {
|
||||
const t = useT()
|
||||
const {channels, memberIdsByChannelId} = useSortedPrivateMessageMemberships(currentUser.id)
|
||||
const {lastSeenChatTimeByChannelId} = useUnseenPrivateMessageChannels(currentUser.id, true)
|
||||
const lastMessages = useLastPrivateMessages(currentUser.id)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -76,6 +78,7 @@ export function MessagesContent(props: {currentUser: User}) {
|
||||
currentUser={currentUser}
|
||||
channel={channel}
|
||||
lastSeenTime={lastSeenChatTimeByChannelId[channel.channel_id]}
|
||||
lastMessage={lastMessages[channel.channel_id]}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
@@ -89,13 +92,12 @@ export const MessageChannelRow = (props: {
|
||||
currentUser: User
|
||||
channel: PrivateMessageChannel
|
||||
lastSeenTime: string
|
||||
lastMessage?: PrivateChatMessage
|
||||
}) => {
|
||||
const {otherUserIds, lastSeenTime, currentUser, channel} = props
|
||||
const {otherUserIds, lastSeenTime, currentUser, channel, lastMessage} = props
|
||||
const channelId = channel.channel_id
|
||||
const otherUsers = useUsersInStore(otherUserIds, `${channelId}`, 100)
|
||||
const {messages} = usePrivateMessages(channelId, 1, currentUser.id)
|
||||
const unseen = (messages?.[0]?.createdTimeTs ?? '0') > lastSeenTime
|
||||
const chat = messages?.[0]
|
||||
const unseen = (lastMessage?.createdTimeTs ?? '0') > lastSeenTime
|
||||
const numOthers = otherUsers?.length ?? 0
|
||||
const t = useT()
|
||||
|
||||
@@ -146,7 +148,7 @@ export const MessageChannelRow = (props: {
|
||||
{isBanned && <BannedBadge />}
|
||||
</span>
|
||||
<span className={'text-ink-400 dark:text-ink-500 text-xs'}>
|
||||
{chat && <RelativeTimestamp time={chat.createdTime} />}
|
||||
{lastMessage && <RelativeTimestamp time={lastMessage.createdTime} />}
|
||||
</span>
|
||||
</Row>
|
||||
<Row className="items-center justify-between gap-1">
|
||||
@@ -156,10 +158,10 @@ export const MessageChannelRow = (props: {
|
||||
unseen ? '' : 'text-ink-500 dark:text-ink-600',
|
||||
)}
|
||||
>
|
||||
{chat && (
|
||||
{lastMessage && (
|
||||
<>
|
||||
{chat.userId == currentUser.id && t('messages.you_prefix', 'You: ')}
|
||||
{parseJsonContentToText(chat.content)}
|
||||
{lastMessage.userId == currentUser.id && t('messages.you_prefix', 'You: ')}
|
||||
{parseJsonContentToText(lastMessage.content)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user