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