Backfill and forward fill contracts with group info (#686)
* Backfill and forward fill contracts with group info * No nested queries :( * Fix filter * Pass empty arrays instead of undefined
This commit is contained in:
parent
5899c1f3c0
commit
5f074206de
|
@ -1,6 +1,7 @@
|
||||||
import { Answer } from './answer'
|
import { Answer } from './answer'
|
||||||
import { Fees } from './fees'
|
import { Fees } from './fees'
|
||||||
import { JSONContent } from '@tiptap/core'
|
import { JSONContent } from '@tiptap/core'
|
||||||
|
import { GroupLink } from 'common/group'
|
||||||
|
|
||||||
export type AnyMechanism = DPM | CPMM
|
export type AnyMechanism = DPM | CPMM
|
||||||
export type AnyOutcomeType = Binary | PseudoNumeric | FreeResponse | Numeric
|
export type AnyOutcomeType = Binary | PseudoNumeric | FreeResponse | Numeric
|
||||||
|
@ -46,6 +47,7 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
|
||||||
collectedFees: Fees
|
collectedFees: Fees
|
||||||
|
|
||||||
groupSlugs?: string[]
|
groupSlugs?: string[]
|
||||||
|
groupLinks?: GroupLink[]
|
||||||
uniqueBettorIds?: string[]
|
uniqueBettorIds?: string[]
|
||||||
uniqueBettorCount?: number
|
uniqueBettorCount?: number
|
||||||
popularityScore?: number
|
popularityScore?: number
|
||||||
|
|
|
@ -19,3 +19,11 @@ export const MAX_ABOUT_LENGTH = 140
|
||||||
export const MAX_ID_LENGTH = 60
|
export const MAX_ID_LENGTH = 60
|
||||||
export const NEW_USER_GROUP_SLUGS = ['updates', 'bugs', 'welcome']
|
export const NEW_USER_GROUP_SLUGS = ['updates', 'bugs', 'welcome']
|
||||||
export const GROUP_CHAT_SLUG = 'chat'
|
export const GROUP_CHAT_SLUG = 'chat'
|
||||||
|
|
||||||
|
export type GroupLink = {
|
||||||
|
slug: string
|
||||||
|
name: string
|
||||||
|
groupId: string
|
||||||
|
createdTime: number
|
||||||
|
userId?: string
|
||||||
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ service cloud.firestore {
|
||||||
match /contracts/{contractId} {
|
match /contracts/{contractId} {
|
||||||
allow read;
|
allow read;
|
||||||
allow update: if request.resource.data.diff(resource.data).affectedKeys()
|
allow update: if request.resource.data.diff(resource.data).affectedKeys()
|
||||||
.hasOnly(['tags', 'lowercaseTags', 'groupSlugs']);
|
.hasOnly(['tags', 'lowercaseTags', 'groupSlugs', 'groupLinks']);
|
||||||
allow update: if request.resource.data.diff(resource.data).affectedKeys()
|
allow update: if request.resource.data.diff(resource.data).affectedKeys()
|
||||||
.hasOnly(['description', 'closeTime', 'question'])
|
.hasOnly(['description', 'closeTime', 'question'])
|
||||||
&& resource.data.creatorId == request.auth.uid;
|
&& resource.data.creatorId == request.auth.uid;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { Group } from 'common/group'
|
import { Group } from 'common/group'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
export const onDeleteGroup = functions.firestore
|
export const onDeleteGroup = functions.firestore
|
||||||
|
@ -15,17 +16,21 @@ export const onDeleteGroup = functions.firestore
|
||||||
.collection('contracts')
|
.collection('contracts')
|
||||||
.where('groupSlugs', 'array-contains', group.slug)
|
.where('groupSlugs', 'array-contains', group.slug)
|
||||||
.get()
|
.get()
|
||||||
|
console.log("contracts with group's slug:", contracts)
|
||||||
|
|
||||||
for (const doc of contracts.docs) {
|
for (const doc of contracts.docs) {
|
||||||
const contract = doc.data() as Contract
|
const contract = doc.data() as Contract
|
||||||
|
const newGroupLinks = contract.groupLinks?.filter(
|
||||||
|
(link) => link.slug !== group.slug
|
||||||
|
)
|
||||||
|
|
||||||
// remove the group from the contract
|
// remove the group from the contract
|
||||||
await firestore
|
await firestore
|
||||||
.collection('contracts')
|
.collection('contracts')
|
||||||
.doc(contract.id)
|
.doc(contract.id)
|
||||||
.update({
|
.update({
|
||||||
groupSlugs: (contract.groupSlugs ?? []).filter(
|
groupSlugs: contract.groupSlugs?.filter((s) => s !== group.slug),
|
||||||
(groupSlug) => groupSlug !== group.slug
|
groupLinks: newGroupLinks ?? [],
|
||||||
),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as admin from 'firebase-admin'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
import { getValues, isProd } from '../utils'
|
import { getValues, isProd } from '../utils'
|
||||||
import { CATEGORIES_GROUP_SLUG_POSTFIX } from 'common/categories'
|
import { CATEGORIES_GROUP_SLUG_POSTFIX } from 'common/categories'
|
||||||
import { Group } from 'common/group'
|
import { Group, GroupLink } from 'common/group'
|
||||||
import { uniq } from 'lodash'
|
import { uniq } from 'lodash'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
|
@ -17,27 +17,6 @@ initAdmin()
|
||||||
|
|
||||||
const adminFirestore = admin.firestore()
|
const adminFirestore = admin.firestore()
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const addGroupIdToContracts = async () => {
|
|
||||||
const groups = await getValues<Group>(adminFirestore.collection('groups'))
|
|
||||||
const contracts = await getValues<Contract>(
|
|
||||||
adminFirestore.collection('contracts')
|
|
||||||
)
|
|
||||||
for (const group of groups) {
|
|
||||||
const groupContracts = contracts.filter((contract) =>
|
|
||||||
group.contractIds.includes(contract.id)
|
|
||||||
)
|
|
||||||
for (const contract of groupContracts) {
|
|
||||||
await adminFirestore
|
|
||||||
.collection('contracts')
|
|
||||||
.doc(contract.id)
|
|
||||||
.update({
|
|
||||||
groupSlugs: uniq([...(contract.groupSlugs ?? []), group.slug]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const convertCategoriesToGroupsInternal = async (categories: string[]) => {
|
const convertCategoriesToGroupsInternal = async (categories: string[]) => {
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
const markets = await getValues<Contract>(
|
const markets = await getValues<Contract>(
|
||||||
|
@ -93,18 +72,30 @@ const convertCategoriesToGroupsInternal = async (categories: string[]) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const market of markets) {
|
for (const market of markets) {
|
||||||
|
if (market.groupLinks?.map((l) => l.groupId).includes(newGroup.id))
|
||||||
|
continue // already in that group
|
||||||
|
|
||||||
|
const newGroupLinks = [
|
||||||
|
...(market.groupLinks ?? []),
|
||||||
|
{
|
||||||
|
groupId: newGroup.id,
|
||||||
|
createdTime: Date.now(),
|
||||||
|
slug: newGroup.slug,
|
||||||
|
name: newGroup.name,
|
||||||
|
} as GroupLink,
|
||||||
|
]
|
||||||
await adminFirestore
|
await adminFirestore
|
||||||
.collection('contracts')
|
.collection('contracts')
|
||||||
.doc(market.id)
|
.doc(market.id)
|
||||||
.update({
|
.update({
|
||||||
groupSlugs: uniq([...(market?.groupSlugs ?? []), newGroup.slug]),
|
groupSlugs: uniq([...(market.groupSlugs ?? []), newGroup.slug]),
|
||||||
|
groupLinks: newGroupLinks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function convertCategoriesToGroups() {
|
async function convertCategoriesToGroups() {
|
||||||
// await addGroupIdToContracts()
|
|
||||||
// const defaultCategories = Object.values(DEFAULT_CATEGORIES)
|
// const defaultCategories = Object.values(DEFAULT_CATEGORIES)
|
||||||
const moreCategories = ['world', 'culture']
|
const moreCategories = ['world', 'culture']
|
||||||
await convertCategoriesToGroupsInternal(moreCategories)
|
await convertCategoriesToGroupsInternal(moreCategories)
|
||||||
|
|
52
functions/src/scripts/link-contracts-to-groups.ts
Normal file
52
functions/src/scripts/link-contracts-to-groups.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { getValues } from 'functions/src/utils'
|
||||||
|
import { Group } from 'common/group'
|
||||||
|
import { Contract } from 'common/contract'
|
||||||
|
import { initAdmin } from 'functions/src/scripts/script-init'
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
import { filterDefined } from 'common/util/array'
|
||||||
|
import { uniq } from 'lodash'
|
||||||
|
|
||||||
|
initAdmin()
|
||||||
|
|
||||||
|
const adminFirestore = admin.firestore()
|
||||||
|
|
||||||
|
const addGroupIdToContracts = async () => {
|
||||||
|
const groups = await getValues<Group>(adminFirestore.collection('groups'))
|
||||||
|
const contracts = await getValues<Contract>(
|
||||||
|
adminFirestore.collection('contracts')
|
||||||
|
)
|
||||||
|
for (const group of groups) {
|
||||||
|
const groupContracts = contracts.filter((contract) =>
|
||||||
|
group.contractIds.includes(contract.id)
|
||||||
|
)
|
||||||
|
for (const contract of groupContracts) {
|
||||||
|
const oldGroupLinks = contract.groupLinks?.filter(
|
||||||
|
(l) => l.slug != group.slug
|
||||||
|
)
|
||||||
|
const newGroupLinks = filterDefined([
|
||||||
|
...(oldGroupLinks ?? []),
|
||||||
|
group.id
|
||||||
|
? {
|
||||||
|
slug: group.slug,
|
||||||
|
name: group.name,
|
||||||
|
groupId: group.id,
|
||||||
|
createdTime: Date.now(),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
])
|
||||||
|
await adminFirestore
|
||||||
|
.collection('contracts')
|
||||||
|
.doc(contract.id)
|
||||||
|
.update({
|
||||||
|
groupSlugs: uniq([...(contract.groupSlugs ?? []), group.slug]),
|
||||||
|
groupLinks: newGroupLinks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
addGroupIdToContracts()
|
||||||
|
.then(() => process.exit())
|
||||||
|
.catch(console.log)
|
||||||
|
}
|
|
@ -23,11 +23,8 @@ import { useState } from 'react'
|
||||||
import { ContractInfoDialog } from './contract-info-dialog'
|
import { ContractInfoDialog } from './contract-info-dialog'
|
||||||
import { Bet } from 'common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import NewContractBadge from '../new-contract-badge'
|
import NewContractBadge from '../new-contract-badge'
|
||||||
import { CATEGORY_LIST } from 'common/categories'
|
|
||||||
import { TagsList } from '../tags-list'
|
|
||||||
import { UserFollowButton } from '../follow-button'
|
import { UserFollowButton } from '../follow-button'
|
||||||
import { DAY_MS } from 'common/util/time'
|
import { DAY_MS } from 'common/util/time'
|
||||||
import { useGroupsWithContract } from 'web/hooks/use-group'
|
|
||||||
import { ShareIconButton } from 'web/components/share-icon-button'
|
import { ShareIconButton } from 'web/components/share-icon-button'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { Editor } from '@tiptap/react'
|
import { Editor } from '@tiptap/react'
|
||||||
|
@ -37,6 +34,8 @@ import { Button } from 'web/components/button'
|
||||||
import { Modal } from 'web/components/layout/modal'
|
import { Modal } from 'web/components/layout/modal'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { ContractGroupsList } from 'web/components/groups/contract-groups-list'
|
import { ContractGroupsList } from 'web/components/groups/contract-groups-list'
|
||||||
|
import { SiteLink } from 'web/components/site-link'
|
||||||
|
import { groupPath } from 'web/lib/firebase/groups'
|
||||||
|
|
||||||
export type ShowTime = 'resolve-date' | 'close-date'
|
export type ShowTime = 'resolve-date' | 'close-date'
|
||||||
|
|
||||||
|
@ -50,15 +49,16 @@ export function MiscDetails(props: {
|
||||||
volume,
|
volume,
|
||||||
volume24Hours,
|
volume24Hours,
|
||||||
closeTime,
|
closeTime,
|
||||||
tags,
|
|
||||||
isResolved,
|
isResolved,
|
||||||
createdTime,
|
createdTime,
|
||||||
resolutionTime,
|
resolutionTime,
|
||||||
|
groupLinks,
|
||||||
} = contract
|
} = contract
|
||||||
|
|
||||||
// Show at most one category that this contract is tagged by
|
// Show at most one category that this contract is tagged by
|
||||||
const categories = CATEGORY_LIST.filter((category) =>
|
// const categories = CATEGORY_LIST.filter((category) =>
|
||||||
tags.map((t) => t.toLowerCase()).includes(category)
|
// tags.map((t) => t.toLowerCase()).includes(category)
|
||||||
).slice(0, 1)
|
// ).slice(0, 1)
|
||||||
const isNew = createdTime > Date.now() - DAY_MS && !isResolved
|
const isNew = createdTime > Date.now() - DAY_MS && !isResolved
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -80,13 +80,24 @@ export function MiscDetails(props: {
|
||||||
{fromNow(resolutionTime || 0)}
|
{fromNow(resolutionTime || 0)}
|
||||||
</Row>
|
</Row>
|
||||||
) : volume > 0 || !isNew ? (
|
) : volume > 0 || !isNew ? (
|
||||||
<Row>{contractPool(contract)} pool</Row>
|
<Row className={'shrink-0'}>{contractPool(contract)} pool</Row>
|
||||||
) : (
|
) : (
|
||||||
<NewContractBadge />
|
<NewContractBadge />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{categories.length > 0 && (
|
{/*{categories.length > 0 && (*/}
|
||||||
<TagsList className="text-gray-400" tags={categories} noLabel />
|
{/* <TagsList className="text-gray-400" tags={categories} noLabel />*/}
|
||||||
|
{/*)}*/}
|
||||||
|
{groupLinks && groupLinks.length > 0 && (
|
||||||
|
<SiteLink
|
||||||
|
href={groupPath(groupLinks[0].slug)}
|
||||||
|
className="text-sm text-gray-400"
|
||||||
|
>
|
||||||
|
<Row className={'line-clamp-1 flex-wrap items-center '}>
|
||||||
|
<UserGroupIcon className="mx-1 mb-0.5 inline h-4 w-4 shrink-0" />
|
||||||
|
{groupLinks[0].name}
|
||||||
|
</Row>
|
||||||
|
</SiteLink>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
@ -134,11 +145,12 @@ export function ContractDetails(props: {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { contract, bets, isCreator, disabled } = props
|
const { contract, bets, isCreator, disabled } = props
|
||||||
const { closeTime, creatorName, creatorUsername, creatorId } = contract
|
const { closeTime, creatorName, creatorUsername, creatorId, groupLinks } =
|
||||||
|
contract
|
||||||
const { volumeLabel, resolvedDate } = contractMetrics(contract)
|
const { volumeLabel, resolvedDate } = contractMetrics(contract)
|
||||||
|
|
||||||
const groups = useGroupsWithContract(contract)
|
const groupToDisplay =
|
||||||
const groupToDisplay = groups[0] ?? null
|
groupLinks?.sort((a, b) => a.createdTime - b.createdTime)[0] ?? null
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
@ -172,11 +184,7 @@ export function ContractDetails(props: {
|
||||||
<Row>
|
<Row>
|
||||||
<UserGroupIcon className="mx-1 inline h-5 w-5 shrink-0" />
|
<UserGroupIcon className="mx-1 inline h-5 w-5 shrink-0" />
|
||||||
<span className={'line-clamp-1'}>
|
<span className={'line-clamp-1'}>
|
||||||
{contract.groupSlugs && !groupToDisplay
|
{groupToDisplay ? groupToDisplay.name : 'No group'}
|
||||||
? ''
|
|
||||||
: groupToDisplay
|
|
||||||
? groupToDisplay.name
|
|
||||||
: 'No group'}
|
|
||||||
</span>
|
</span>
|
||||||
</Row>
|
</Row>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -187,7 +195,11 @@ export function ContractDetails(props: {
|
||||||
'max-h-[70vh] min-h-[20rem] overflow-auto rounded bg-white p-6'
|
'max-h-[70vh] min-h-[20rem] overflow-auto rounded bg-white p-6'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ContractGroupsList groups={groups} contract={contract} user={user} />
|
<ContractGroupsList
|
||||||
|
groupLinks={groupLinks ?? []}
|
||||||
|
contract={contract}
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Group } from 'common/group'
|
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { GroupLink } from 'web/pages/groups'
|
import { GroupLinkItem } from 'web/pages/groups'
|
||||||
import { XIcon } from '@heroicons/react/outline'
|
import { XIcon } from '@heroicons/react/outline'
|
||||||
import { Button } from 'web/components/button'
|
import { Button } from 'web/components/button'
|
||||||
import { GroupSelector } from 'web/components/groups/group-selector'
|
import { GroupSelector } from 'web/components/groups/group-selector'
|
||||||
|
@ -13,14 +12,16 @@ import {
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
|
import { GroupLink } from 'common/group'
|
||||||
|
import { useGroupsWithContract } from 'web/hooks/use-group'
|
||||||
|
|
||||||
export function ContractGroupsList(props: {
|
export function ContractGroupsList(props: {
|
||||||
groups: Group[]
|
groupLinks: GroupLink[]
|
||||||
contract: Contract
|
contract: Contract
|
||||||
user: User | null | undefined
|
user: User | null | undefined
|
||||||
}) {
|
}) {
|
||||||
const { groups, user, contract } = props
|
const { groupLinks, user, contract } = props
|
||||||
|
const groups = useGroupsWithContract(contract)
|
||||||
return (
|
return (
|
||||||
<Col className={'gap-2'}>
|
<Col className={'gap-2'}>
|
||||||
<span className={'text-xl text-indigo-700'}>
|
<span className={'text-xl text-indigo-700'}>
|
||||||
|
@ -33,10 +34,10 @@ export function ContractGroupsList(props: {
|
||||||
options={{
|
options={{
|
||||||
showSelector: true,
|
showSelector: true,
|
||||||
showLabel: false,
|
showLabel: false,
|
||||||
ignoreGroupIds: groups.map((g) => g.id),
|
ignoreGroupIds: groupLinks.map((g) => g.groupId),
|
||||||
}}
|
}}
|
||||||
setSelectedGroup={(group) =>
|
setSelectedGroup={(group) =>
|
||||||
group && addContractToGroup(group, contract)
|
group && addContractToGroup(group, contract, user.id)
|
||||||
}
|
}
|
||||||
selectedGroup={undefined}
|
selectedGroup={undefined}
|
||||||
creator={user}
|
creator={user}
|
||||||
|
@ -54,7 +55,7 @@ export function ContractGroupsList(props: {
|
||||||
className={clsx('items-center justify-between gap-2 p-2')}
|
className={clsx('items-center justify-between gap-2 p-2')}
|
||||||
>
|
>
|
||||||
<Row className="line-clamp-1 items-center gap-2">
|
<Row className="line-clamp-1 items-center gap-2">
|
||||||
<GroupLink group={group} />
|
<GroupLinkItem group={group} />
|
||||||
</Row>
|
</Row>
|
||||||
{user && group.memberIds.includes(user.id) && (
|
{user && group.memberIds.includes(user.id) && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Modal } from 'web/components/layout/modal'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { joinGroup, leaveGroup } from 'web/lib/firebase/groups'
|
import { joinGroup, leaveGroup } from 'web/lib/firebase/groups'
|
||||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||||
import { GroupLink } from 'web/pages/groups'
|
import { GroupLinkItem } from 'web/pages/groups'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
export function GroupsButton(props: { user: User }) {
|
export function GroupsButton(props: { user: User }) {
|
||||||
|
@ -77,7 +77,7 @@ function GroupItem(props: { group: Group; className?: string }) {
|
||||||
return (
|
return (
|
||||||
<Row className={clsx('items-center justify-between gap-2 p-2', className)}>
|
<Row className={clsx('items-center justify-between gap-2 p-2', className)}>
|
||||||
<Row className="line-clamp-1 items-center gap-2">
|
<Row className="line-clamp-1 items-center gap-2">
|
||||||
<GroupLink group={group} />
|
<GroupLinkItem group={group} />
|
||||||
</Row>
|
</Row>
|
||||||
<JoinOrLeaveGroupButton group={group} />
|
<JoinOrLeaveGroupButton group={group} />
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import {
|
import {
|
||||||
doc,
|
|
||||||
setDoc,
|
|
||||||
deleteDoc,
|
|
||||||
where,
|
|
||||||
collection,
|
collection,
|
||||||
query,
|
deleteDoc,
|
||||||
getDocs,
|
doc,
|
||||||
orderBy,
|
|
||||||
getDoc,
|
getDoc,
|
||||||
updateDoc,
|
getDocs,
|
||||||
limit,
|
limit,
|
||||||
|
orderBy,
|
||||||
|
query,
|
||||||
|
setDoc,
|
||||||
startAfter,
|
startAfter,
|
||||||
|
updateDoc,
|
||||||
|
where,
|
||||||
} from 'firebase/firestore'
|
} from 'firebase/firestore'
|
||||||
import { sortBy, sum } from 'lodash'
|
import { sortBy, sum } from 'lodash'
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ export async function listContractsByGroupSlug(
|
||||||
): Promise<Contract[]> {
|
): Promise<Contract[]> {
|
||||||
const q = query(contracts, where('groupSlugs', 'array-contains', slug))
|
const q = query(contracts, where('groupSlugs', 'array-contains', slug))
|
||||||
const snapshot = await getDocs(q)
|
const snapshot = await getDocs(q)
|
||||||
|
console.log(snapshot.docs.map((doc) => doc.data()))
|
||||||
return snapshot.docs.map((doc) => doc.data())
|
return snapshot.docs.map((doc) => doc.data())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
where,
|
where,
|
||||||
} from 'firebase/firestore'
|
} from 'firebase/firestore'
|
||||||
import { sortBy, uniq } from 'lodash'
|
import { sortBy, uniq } from 'lodash'
|
||||||
import { Group, GROUP_CHAT_SLUG } from 'common/group'
|
import { Group, GROUP_CHAT_SLUG, GroupLink } from 'common/group'
|
||||||
import { updateContract } from './contracts'
|
import { updateContract } from './contracts'
|
||||||
import {
|
import {
|
||||||
coll,
|
coll,
|
||||||
|
@ -124,9 +124,27 @@ export async function leaveGroup(group: Group, userId: string): Promise<void> {
|
||||||
return await updateGroup(group, { memberIds: uniq(newMemberIds) })
|
return await updateGroup(group, { memberIds: uniq(newMemberIds) })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addContractToGroup(group: Group, contract: Contract) {
|
export async function addContractToGroup(
|
||||||
|
group: Group,
|
||||||
|
contract: Contract,
|
||||||
|
userId: string
|
||||||
|
) {
|
||||||
|
if (contract.groupLinks?.map((l) => l.groupId).includes(group.id)) return // already in that group
|
||||||
|
|
||||||
|
const newGroupLinks = [
|
||||||
|
...(contract.groupLinks ?? []),
|
||||||
|
{
|
||||||
|
groupId: group.id,
|
||||||
|
createdTime: Date.now(),
|
||||||
|
slug: group.slug,
|
||||||
|
userId,
|
||||||
|
name: group.name,
|
||||||
|
} as GroupLink,
|
||||||
|
]
|
||||||
|
|
||||||
await updateContract(contract.id, {
|
await updateContract(contract.id, {
|
||||||
groupSlugs: [...(contract.groupSlugs ?? []), group.slug],
|
groupSlugs: uniq([...(contract.groupSlugs ?? []), group.slug]),
|
||||||
|
groupLinks: newGroupLinks,
|
||||||
})
|
})
|
||||||
return await updateGroup(group, {
|
return await updateGroup(group, {
|
||||||
contractIds: uniq([...group.contractIds, contract.id]),
|
contractIds: uniq([...group.contractIds, contract.id]),
|
||||||
|
@ -142,11 +160,15 @@ export async function removeContractFromGroup(
|
||||||
group: Group,
|
group: Group,
|
||||||
contract: Contract
|
contract: Contract
|
||||||
) {
|
) {
|
||||||
const newGroupSlugs = contract.groupSlugs?.filter(
|
if (!contract.groupLinks?.map((l) => l.groupId).includes(group.id)) return // not in that group
|
||||||
(slug) => slug !== group.slug
|
|
||||||
|
const newGroupLinks = contract.groupLinks?.filter(
|
||||||
|
(link) => link.slug !== group.slug
|
||||||
)
|
)
|
||||||
await updateContract(contract.id, {
|
await updateContract(contract.id, {
|
||||||
groupSlugs: uniq(newGroupSlugs ?? []),
|
groupSlugs:
|
||||||
|
contract.groupSlugs?.filter((slug) => slug !== group.slug) ?? [],
|
||||||
|
groupLinks: newGroupLinks ?? [],
|
||||||
})
|
})
|
||||||
const newContractIds = group.contractIds.filter((id) => id !== contract.id)
|
const newContractIds = group.contractIds.filter((id) => id !== contract.id)
|
||||||
return await updateGroup(group, {
|
return await updateGroup(group, {
|
||||||
|
@ -159,8 +181,22 @@ export async function removeContractFromGroup(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setContractGroupSlugs(group: Group, contractId: string) {
|
export async function setContractGroupLinks(
|
||||||
await updateContract(contractId, { groupSlugs: [group.slug] })
|
group: Group,
|
||||||
|
contractId: string,
|
||||||
|
userId: string
|
||||||
|
) {
|
||||||
|
await updateContract(contractId, {
|
||||||
|
groupLinks: [
|
||||||
|
{
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
slug: group.slug,
|
||||||
|
userId,
|
||||||
|
createdTime: Date.now(),
|
||||||
|
} as GroupLink,
|
||||||
|
],
|
||||||
|
})
|
||||||
return await updateGroup(group, {
|
return await updateGroup(group, {
|
||||||
contractIds: uniq([...group.contractIds, contractId]),
|
contractIds: uniq([...group.contractIds, contractId]),
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { removeUndefinedProps } from 'common/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
|
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
|
||||||
import { getGroup, setContractGroupSlugs } from 'web/lib/firebase/groups'
|
import { getGroup, setContractGroupLinks } from 'web/lib/firebase/groups'
|
||||||
import { Group } from 'common/group'
|
import { Group } from 'common/group'
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
import { useWarnUnsavedChanges } from 'web/hooks/use-warn-unsaved-changes'
|
import { useWarnUnsavedChanges } from 'web/hooks/use-warn-unsaved-changes'
|
||||||
|
@ -226,7 +226,7 @@ export function NewContract(props: {
|
||||||
isFree: false,
|
isFree: false,
|
||||||
})
|
})
|
||||||
if (result && selectedGroup) {
|
if (result && selectedGroup) {
|
||||||
await setContractGroupSlugs(selectedGroup, result.id)
|
await setContractGroupLinks(selectedGroup, result.id, creator.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
await router.push(contractPath(result as Contract))
|
await router.push(contractPath(result as Contract))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { take, sortBy, debounce } from 'lodash'
|
import { debounce, sortBy, take } from 'lodash'
|
||||||
import PlusSmIcon from '@heroicons/react/solid/PlusSmIcon'
|
import PlusSmIcon from '@heroicons/react/solid/PlusSmIcon'
|
||||||
|
|
||||||
import { Group, GROUP_CHAT_SLUG } from 'common/group'
|
import { Group, GROUP_CHAT_SLUG } from 'common/group'
|
||||||
|
@ -6,11 +6,11 @@ import { Page } from 'web/components/page'
|
||||||
import { listAllBets } from 'web/lib/firebase/bets'
|
import { listAllBets } from 'web/lib/firebase/bets'
|
||||||
import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts'
|
import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts'
|
||||||
import {
|
import {
|
||||||
groupPath,
|
|
||||||
getGroupBySlug,
|
|
||||||
updateGroup,
|
|
||||||
joinGroup,
|
|
||||||
addContractToGroup,
|
addContractToGroup,
|
||||||
|
getGroupBySlug,
|
||||||
|
groupPath,
|
||||||
|
joinGroup,
|
||||||
|
updateGroup,
|
||||||
} from 'web/lib/firebase/groups'
|
} from 'web/lib/firebase/groups'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { UserLink } from 'web/components/user-page'
|
import { UserLink } from 'web/components/user-page'
|
||||||
|
@ -543,7 +543,7 @@ function AddContractButton(props: { group: Group; user: User }) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
async function addContractToCurrentGroup(contract: Contract) {
|
async function addContractToCurrentGroup(contract: Contract) {
|
||||||
await addContractToGroup(group, contract)
|
await addContractToGroup(group, contract, user.id)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { sortBy, debounce } from 'lodash'
|
import { debounce, sortBy } from 'lodash'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Group } from 'common/group'
|
import { Group } from 'common/group'
|
||||||
|
@ -238,7 +238,7 @@ function GroupMembersList(props: { group: Group }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupLink(props: { group: Group; className?: string }) {
|
export function GroupLinkItem(props: { group: Group; className?: string }) {
|
||||||
const { group, className } = props
|
const { group, className } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in New Issue
Block a user