Date: Thu, 6 Oct 2022 09:26:35 -0400
Subject: [PATCH 25/44] Update groupContracts in db trigger
---
common/group.ts | 1 +
functions/src/on-update-contract.ts | 55 ++++++++++++++++++++++----
functions/src/scripts/update-groups.ts | 27 +++++++------
web/lib/firebase/groups.ts | 13 +-----
4 files changed, 65 insertions(+), 31 deletions(-)
diff --git a/common/group.ts b/common/group.ts
index 8f5728d3..cb6660e8 100644
--- a/common/group.ts
+++ b/common/group.ts
@@ -39,3 +39,4 @@ export type GroupLink = {
createdTime: number
userId?: string
}
+export type GroupContractDoc = { contractId: string; createdTime: number }
diff --git a/functions/src/on-update-contract.ts b/functions/src/on-update-contract.ts
index 301d6286..1e3418fa 100644
--- a/functions/src/on-update-contract.ts
+++ b/functions/src/on-update-contract.ts
@@ -2,6 +2,8 @@ import * as functions from 'firebase-functions'
import { getUser } from './utils'
import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
import { Contract } from '../../common/contract'
+import { GroupContractDoc } from '../../common/group'
+import * as admin from 'firebase-admin'
export const onUpdateContract = functions.firestore
.document('contracts/{contractId}')
@@ -9,17 +11,14 @@ export const onUpdateContract = functions.firestore
const contract = change.after.data() as Contract
const previousContract = change.before.data() as Contract
const { eventId } = context
- const { openCommentBounties, closeTime, question } = contract
+ const { closeTime, question } = contract
- if (
- !previousContract.isResolved &&
- contract.isResolved &&
- (openCommentBounties ?? 0) > 0
- ) {
+ if (!previousContract.isResolved && contract.isResolved) {
// No need to notify users of resolution, that's handled in resolve-market
return
- }
- if (
+ } else if (previousContract.groupSlugs !== contract.groupSlugs) {
+ await handleContractGroupUpdated(previousContract, contract)
+ } else if (
previousContract.closeTime !== closeTime ||
previousContract.question !== question
) {
@@ -51,3 +50,43 @@ async function handleUpdatedCloseTime(
contract
)
}
+
+async function handleContractGroupUpdated(
+ previousContract: Contract,
+ contract: Contract
+) {
+ const prevLength = previousContract.groupSlugs?.length ?? 0
+ const newLength = contract.groupSlugs?.length ?? 0
+ if (prevLength < newLength) {
+ // Contract was added to a new group
+ const groupId = contract.groupLinks?.find(
+ (link) =>
+ !previousContract.groupLinks
+ ?.map((l) => l.groupId)
+ .includes(link.groupId)
+ )?.groupId
+ if (!groupId) throw new Error('Could not find new group id')
+
+ await firestore
+ .collection(`groups/${groupId}/groupContracts`)
+ .doc(contract.id)
+ .set({
+ contractId: contract.id,
+ createdTime: Date.now(),
+ } as GroupContractDoc)
+ }
+ if (prevLength > newLength) {
+ // Contract was removed from a group
+ const groupId = previousContract.groupLinks?.find(
+ (link) =>
+ !contract.groupLinks?.map((l) => l.groupId).includes(link.groupId)
+ )?.groupId
+ if (!groupId) throw new Error('Could not find old group id')
+
+ await firestore
+ .collection(`groups/${groupId}/groupContracts`)
+ .doc(contract.id)
+ .delete()
+ }
+}
+const firestore = admin.firestore()
diff --git a/functions/src/scripts/update-groups.ts b/functions/src/scripts/update-groups.ts
index fc402292..56a9f399 100644
--- a/functions/src/scripts/update-groups.ts
+++ b/functions/src/scripts/update-groups.ts
@@ -89,17 +89,20 @@ const getGroups = async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function updateTotalContractsAndMembers() {
const groups = await getGroups()
- for (const group of groups) {
- log('updating group total contracts and members', group.slug)
- const groupRef = admin.firestore().collection('groups').doc(group.id)
- const totalMembers = (await groupRef.collection('groupMembers').get()).size
- const totalContracts = (await groupRef.collection('groupContracts').get())
- .size
- await groupRef.update({
- totalMembers,
- totalContracts,
+ await Promise.all(
+ groups.map(async (group) => {
+ log('updating group total contracts and members', group.slug)
+ const groupRef = admin.firestore().collection('groups').doc(group.id)
+ const totalMembers = (await groupRef.collection('groupMembers').get())
+ .size
+ const totalContracts = (await groupRef.collection('groupContracts').get())
+ .size
+ await groupRef.update({
+ totalMembers,
+ totalContracts,
+ })
})
- }
+ )
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function removeUnusedMemberAndContractFields() {
@@ -117,6 +120,6 @@ async function removeUnusedMemberAndContractFields() {
if (require.main === module) {
initAdmin()
// convertGroupFieldsToGroupDocuments()
- // updateTotalContractsAndMembers()
- removeUnusedMemberAndContractFields()
+ updateTotalContractsAndMembers()
+ // removeUnusedMemberAndContractFields()
}
diff --git a/web/lib/firebase/groups.ts b/web/lib/firebase/groups.ts
index 17e41c53..6bfc4e85 100644
--- a/web/lib/firebase/groups.ts
+++ b/web/lib/firebase/groups.ts
@@ -191,6 +191,7 @@ export async function leaveGroup(group: Group, userId: string): Promise
{
return await deleteDoc(memberDoc)
}
+// TODO: This doesn't check if the user has permission to do this
export async function addContractToGroup(
group: Group,
contract: Contract,
@@ -211,15 +212,9 @@ export async function addContractToGroup(
groupSlugs: uniq([...(contract.groupSlugs ?? []), group.slug]),
groupLinks: newGroupLinks,
})
-
- // create new contract document in groupContracts collection
- const contractDoc = doc(groupContracts(group.id), contract.id)
- await setDoc(contractDoc, {
- contractId: contract.id,
- createdTime: Date.now(),
- })
}
+// TODO: This doesn't check if the user has permission to do this
export async function removeContractFromGroup(
group: Group,
contract: Contract
@@ -234,10 +229,6 @@ export async function removeContractFromGroup(
groupLinks: newGroupLinks ?? [],
})
}
-
- // delete the contract document in groupContracts collection
- const contractDoc = doc(groupContracts(group.id), contract.id)
- await deleteDoc(contractDoc)
}
export function getGroupLinkToDisplay(contract: Contract) {
From e127f9646a08b6cf593018c738ecf9242626b075 Mon Sep 17 00:00:00 2001
From: Ian Philips
Date: Thu, 6 Oct 2022 09:53:55 -0400
Subject: [PATCH 26/44] Default sort to best
---
web/components/contract/contract-tabs.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx
index 9639a57a..9ce17396 100644
--- a/web/components/contract/contract-tabs.tsx
+++ b/web/components/contract/contract-tabs.tsx
@@ -80,7 +80,7 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
const { contract } = props
const tips = useTipTxns({ contractId: contract.id })
const comments = useComments(contract.id) ?? props.comments
- const [sort, setSort] = usePersistentState<'Newest' | 'Best'>('Newest', {
+ const [sort, setSort] = usePersistentState<'Newest' | 'Best'>('Best', {
key: `contract-${contract.id}-comments-sort`,
store: storageStore(safeLocalStorage()),
})
From 26f04fb04a2302f907287401e5db6fb2095f46c4 Mon Sep 17 00:00:00 2001
From: Ian Philips
Date: Thu, 6 Oct 2022 10:16:29 -0400
Subject: [PATCH 27/44] Save comment sort per user rather than per contract
---
web/components/contract/contract-tabs.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx
index 9ce17396..fdfc5c3d 100644
--- a/web/components/contract/contract-tabs.tsx
+++ b/web/components/contract/contract-tabs.tsx
@@ -81,7 +81,7 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
const tips = useTipTxns({ contractId: contract.id })
const comments = useComments(contract.id) ?? props.comments
const [sort, setSort] = usePersistentState<'Newest' | 'Best'>('Best', {
- key: `contract-${contract.id}-comments-sort`,
+ key: `contract-comments-sort`,
store: storageStore(safeLocalStorage()),
})
const me = useUser()
From b8d65acc3f75cc29af12bb56176a64bcb68acbdc Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Thu, 6 Oct 2022 10:54:42 -0500
Subject: [PATCH 28/44] Revert "create market: use transaction"
This reverts commit e1f24f24a96fa8d811ebcaa3b10b19d9b67cb282.
---
functions/src/create-market.ts | 457 ++++++++++++++++-----------------
1 file changed, 224 insertions(+), 233 deletions(-)
diff --git a/functions/src/create-market.ts b/functions/src/create-market.ts
index 38852184..d1483ca4 100644
--- a/functions/src/create-market.ts
+++ b/functions/src/create-market.ts
@@ -15,7 +15,7 @@ import {
import { slugify } from '../../common/util/slugify'
import { randomString } from '../../common/util/random'
-import { isProd } from './utils'
+import { chargeUser, getContract, isProd } from './utils'
import { APIError, AuthedUser, newEndpoint, validate, zTimestamp } from './api'
import { FIXED_ANTE, FREE_MARKETS_PER_USER_MAX } from '../../common/economy'
@@ -36,7 +36,7 @@ import { getPseudoProbability } from '../../common/pseudo-numeric'
import { JSONContent } from '@tiptap/core'
import { uniq, zip } from 'lodash'
import { Bet } from '../../common/bet'
-import { FieldValue, Transaction } from 'firebase-admin/firestore'
+import { FieldValue } from 'firebase-admin/firestore'
const descScehma: z.ZodType = z.lazy(() =>
z.intersection(
@@ -107,242 +107,229 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
visibility = 'public',
} = validate(bodySchema, body)
- return await firestore.runTransaction(async (trans) => {
- let min, max, initialProb, isLogScale, answers
+ let min, max, initialProb, isLogScale, answers
- if (outcomeType === 'PSEUDO_NUMERIC' || outcomeType === 'NUMERIC') {
- let initialValue
- ;({ min, max, initialValue, isLogScale } = validate(numericSchema, body))
- if (max - min <= 0.01 || initialValue <= min || initialValue >= max)
- throw new APIError(400, 'Invalid range.')
+ if (outcomeType === 'PSEUDO_NUMERIC' || outcomeType === 'NUMERIC') {
+ let initialValue
+ ;({ min, max, initialValue, isLogScale } = validate(numericSchema, body))
+ if (max - min <= 0.01 || initialValue <= min || initialValue >= max)
+ throw new APIError(400, 'Invalid range.')
- initialProb =
- getPseudoProbability(initialValue, min, max, isLogScale) * 100
+ initialProb = getPseudoProbability(initialValue, min, max, isLogScale) * 100
- if (initialProb < 1 || initialProb > 99)
- if (outcomeType === 'PSEUDO_NUMERIC')
- throw new APIError(
- 400,
- `Initial value is too ${initialProb < 1 ? 'low' : 'high'}`
- )
- else throw new APIError(400, 'Invalid initial probability.')
- }
-
- if (outcomeType === 'BINARY') {
- ;({ initialProb } = validate(binarySchema, body))
- }
-
- if (outcomeType === 'MULTIPLE_CHOICE') {
- ;({ answers } = validate(multipleChoiceSchema, body))
- }
-
- const userDoc = await trans.get(firestore.collection('users').doc(auth.uid))
- if (!userDoc.exists) {
- throw new APIError(400, 'No user exists with the authenticated user ID.')
- }
- const user = userDoc.data() as User
-
- const ante = FIXED_ANTE
- const deservesFreeMarket =
- (user?.freeMarketsCreated ?? 0) < FREE_MARKETS_PER_USER_MAX
- // TODO: this is broken because it's not in a transaction
- if (ante > user.balance && !deservesFreeMarket)
- throw new APIError(400, `Balance must be at least ${ante}.`)
-
- let group: Group | null = null
- if (groupId) {
- const groupDocRef = firestore.collection('groups').doc(groupId)
- const groupDoc = await trans.get(groupDocRef)
- if (!groupDoc.exists) {
- throw new APIError(400, 'No group exists with the given group ID.')
- }
-
- group = groupDoc.data() as Group
- const groupMembersSnap = await trans.get(
- firestore.collection(`groups/${groupId}/groupMembers`)
- )
- const groupMemberDocs = groupMembersSnap.docs.map(
- (doc) => doc.data() as { userId: string; createdTime: number }
- )
- if (
- !groupMemberDocs.map((m) => m.userId).includes(user.id) &&
- !group.anyoneCanJoin &&
- group.creatorId !== user.id
- ) {
+ if (initialProb < 1 || initialProb > 99)
+ if (outcomeType === 'PSEUDO_NUMERIC')
throw new APIError(
400,
- 'User must be a member/creator of the group or group must be open to add markets to it.'
+ `Initial value is too ${initialProb < 1 ? 'low' : 'high'}`
)
- }
- }
- const slug = await getSlug(trans, question)
- const contractRef = firestore.collection('contracts').doc()
+ else throw new APIError(400, 'Invalid initial probability.')
+ }
- console.log(
- 'creating contract for',
- user.username,
- 'on',
- question,
- 'ante:',
- ante || 0
- )
+ if (outcomeType === 'BINARY') {
+ ;({ initialProb } = validate(binarySchema, body))
+ }
- // convert string descriptions into JSONContent
- const newDescription =
- !description || typeof description === 'string'
- ? {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [{ type: 'text', text: description || ' ' }],
- },
- ],
- }
- : description
+ if (outcomeType === 'MULTIPLE_CHOICE') {
+ ;({ answers } = validate(multipleChoiceSchema, body))
+ }
- const contract = getNewContract(
- contractRef.id,
- slug,
- user,
- question,
- outcomeType,
- newDescription,
- initialProb ?? 0,
- ante,
- closeTime.getTime(),
- tags ?? [],
- NUMERIC_BUCKET_COUNT,
- min ?? 0,
- max ?? 0,
- isLogScale ?? false,
- answers ?? [],
- visibility
- )
+ const userDoc = await firestore.collection('users').doc(auth.uid).get()
+ if (!userDoc.exists) {
+ throw new APIError(400, 'No user exists with the authenticated user ID.')
+ }
+ const user = userDoc.data() as User
- const providerId = deservesFreeMarket
- ? isProd()
- ? HOUSE_LIQUIDITY_PROVIDER_ID
- : DEV_HOUSE_LIQUIDITY_PROVIDER_ID
- : user.id
+ const ante = FIXED_ANTE
+ const deservesFreeMarket =
+ (user?.freeMarketsCreated ?? 0) < FREE_MARKETS_PER_USER_MAX
+ // TODO: this is broken because it's not in a transaction
+ if (ante > user.balance && !deservesFreeMarket)
+ throw new APIError(400, `Balance must be at least ${ante}.`)
- if (ante) {
- const delta = FieldValue.increment(-ante)
- const providerDoc = firestore.collection('users').doc(providerId)
- await trans.update(providerDoc, { balance: delta, totalDeposits: delta })
+ let group: Group | null = null
+ if (groupId) {
+ const groupDocRef = firestore.collection('groups').doc(groupId)
+ const groupDoc = await groupDocRef.get()
+ if (!groupDoc.exists) {
+ throw new APIError(400, 'No group exists with the given group ID.')
}
- if (deservesFreeMarket) {
- await trans.update(firestore.collection('users').doc(user.id), {
- freeMarketsCreated: FieldValue.increment(1),
+ group = groupDoc.data() as Group
+ const groupMembersSnap = await firestore
+ .collection(`groups/${groupId}/groupMembers`)
+ .get()
+ const groupMemberDocs = groupMembersSnap.docs.map(
+ (doc) => doc.data() as { userId: string; createdTime: number }
+ )
+ if (
+ !groupMemberDocs.map((m) => m.userId).includes(user.id) &&
+ !group.anyoneCanJoin &&
+ group.creatorId !== user.id
+ ) {
+ throw new APIError(
+ 400,
+ 'User must be a member/creator of the group or group must be open to add markets to it.'
+ )
+ }
+ }
+ const slug = await getSlug(question)
+ const contractRef = firestore.collection('contracts').doc()
+
+ console.log(
+ 'creating contract for',
+ user.username,
+ 'on',
+ question,
+ 'ante:',
+ ante || 0
+ )
+
+ // convert string descriptions into JSONContent
+ const newDescription =
+ !description || typeof description === 'string'
+ ? {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [{ type: 'text', text: description || ' ' }],
+ },
+ ],
+ }
+ : description
+
+ const contract = getNewContract(
+ contractRef.id,
+ slug,
+ user,
+ question,
+ outcomeType,
+ newDescription,
+ initialProb ?? 0,
+ ante,
+ closeTime.getTime(),
+ tags ?? [],
+ NUMERIC_BUCKET_COUNT,
+ min ?? 0,
+ max ?? 0,
+ isLogScale ?? false,
+ answers ?? [],
+ visibility
+ )
+
+ const providerId = deservesFreeMarket
+ ? isProd()
+ ? HOUSE_LIQUIDITY_PROVIDER_ID
+ : DEV_HOUSE_LIQUIDITY_PROVIDER_ID
+ : user.id
+
+ if (ante) await chargeUser(providerId, ante, true)
+ if (deservesFreeMarket)
+ await firestore
+ .collection('users')
+ .doc(user.id)
+ .update({ freeMarketsCreated: FieldValue.increment(1) })
+
+ await contractRef.create(contract)
+
+ if (group != null) {
+ const groupContractsSnap = await firestore
+ .collection(`groups/${groupId}/groupContracts`)
+ .get()
+ const groupContracts = groupContractsSnap.docs.map(
+ (doc) => doc.data() as { contractId: string; createdTime: number }
+ )
+ if (!groupContracts.map((c) => c.contractId).includes(contractRef.id)) {
+ await createGroupLinks(group, [contractRef.id], auth.uid)
+ const groupContractRef = firestore
+ .collection(`groups/${groupId}/groupContracts`)
+ .doc(contract.id)
+ await groupContractRef.set({
+ contractId: contract.id,
+ createdTime: Date.now(),
})
}
+ }
- await contractRef.create(contract)
+ if (outcomeType === 'BINARY' || outcomeType === 'PSEUDO_NUMERIC') {
+ const liquidityDoc = firestore
+ .collection(`contracts/${contract.id}/liquidity`)
+ .doc()
- if (group != null) {
- const groupContractsSnap = await trans.get(
- firestore.collection(`groups/${groupId}/groupContracts`)
- )
- const groupContracts = groupContractsSnap.docs.map(
- (doc) => doc.data() as { contractId: string; createdTime: number }
+ const lp = getCpmmInitialLiquidity(
+ providerId,
+ contract as CPMMBinaryContract,
+ liquidityDoc.id,
+ ante
+ )
+
+ await liquidityDoc.set(lp)
+ } else if (outcomeType === 'MULTIPLE_CHOICE') {
+ const betCol = firestore.collection(`contracts/${contract.id}/bets`)
+ const betDocs = (answers ?? []).map(() => betCol.doc())
+
+ const answerCol = firestore.collection(`contracts/${contract.id}/answers`)
+ const answerDocs = (answers ?? []).map((_, i) =>
+ answerCol.doc(i.toString())
+ )
+
+ const { bets, answerObjects } = getMultipleChoiceAntes(
+ user,
+ contract as MultipleChoiceContract,
+ answers ?? [],
+ betDocs.map((bd) => bd.id)
+ )
+
+ await Promise.all(
+ zip(bets, betDocs).map(([bet, doc]) => doc?.create(bet as Bet))
+ )
+ await Promise.all(
+ zip(answerObjects, answerDocs).map(([answer, doc]) =>
+ doc?.create(answer as Answer)
)
+ )
+ await contractRef.update({ answers: answerObjects })
+ } else if (outcomeType === 'FREE_RESPONSE') {
+ const noneAnswerDoc = firestore
+ .collection(`contracts/${contract.id}/answers`)
+ .doc('0')
- if (!groupContracts.map((c) => c.contractId).includes(contractRef.id)) {
- await createGroupLinks(trans, group, [contractRef.id], auth.uid)
+ const noneAnswer = getNoneAnswer(contract.id, user)
+ await noneAnswerDoc.set(noneAnswer)
- const groupContractRef = firestore
- .collection(`groups/${groupId}/groupContracts`)
- .doc(contract.id)
+ const anteBetDoc = firestore
+ .collection(`contracts/${contract.id}/bets`)
+ .doc()
- await trans.set(groupContractRef, {
- contractId: contract.id,
- createdTime: Date.now(),
- })
- }
- }
+ const anteBet = getFreeAnswerAnte(
+ providerId,
+ contract as FreeResponseContract,
+ anteBetDoc.id
+ )
+ await anteBetDoc.set(anteBet)
+ } else if (outcomeType === 'NUMERIC') {
+ const anteBetDoc = firestore
+ .collection(`contracts/${contract.id}/bets`)
+ .doc()
- if (outcomeType === 'BINARY' || outcomeType === 'PSEUDO_NUMERIC') {
- const liquidityDoc = firestore
- .collection(`contracts/${contract.id}/liquidity`)
- .doc()
+ const anteBet = getNumericAnte(
+ providerId,
+ contract as NumericContract,
+ ante,
+ anteBetDoc.id
+ )
- const lp = getCpmmInitialLiquidity(
- providerId,
- contract as CPMMBinaryContract,
- liquidityDoc.id,
- ante
- )
+ await anteBetDoc.set(anteBet)
+ }
- await trans.set(liquidityDoc, lp)
- } else if (outcomeType === 'MULTIPLE_CHOICE') {
- const betCol = firestore.collection(`contracts/${contract.id}/bets`)
- const betDocs = (answers ?? []).map(() => betCol.doc())
-
- const answerCol = firestore.collection(`contracts/${contract.id}/answers`)
- const answerDocs = (answers ?? []).map((_, i) =>
- answerCol.doc(i.toString())
- )
-
- const { bets, answerObjects } = getMultipleChoiceAntes(
- user,
- contract as MultipleChoiceContract,
- answers ?? [],
- betDocs.map((bd) => bd.id)
- )
-
- await Promise.all(
- zip(bets, betDocs).map(([bet, doc]) =>
- doc ? trans.create(doc, bet as Bet) : undefined
- )
- )
- await Promise.all(
- zip(answerObjects, answerDocs).map(([answer, doc]) =>
- doc ? trans.create(doc, answer as Answer) : undefined
- )
- )
- await trans.update(contractRef, { answers: answerObjects })
- } else if (outcomeType === 'FREE_RESPONSE') {
- const noneAnswerDoc = firestore
- .collection(`contracts/${contract.id}/answers`)
- .doc('0')
-
- const noneAnswer = getNoneAnswer(contract.id, user)
- await trans.set(noneAnswerDoc, noneAnswer)
-
- const anteBetDoc = firestore
- .collection(`contracts/${contract.id}/bets`)
- .doc()
-
- const anteBet = getFreeAnswerAnte(
- providerId,
- contract as FreeResponseContract,
- anteBetDoc.id
- )
- await trans.set(anteBetDoc, anteBet)
- } else if (outcomeType === 'NUMERIC') {
- const anteBetDoc = firestore
- .collection(`contracts/${contract.id}/bets`)
- .doc()
-
- const anteBet = getNumericAnte(
- providerId,
- contract as NumericContract,
- ante,
- anteBetDoc.id
- )
-
- await trans.set(anteBetDoc, anteBet)
- }
-
- return contract
- })
+ return contract
}
-const getSlug = async (trans: Transaction, question: string) => {
+const getSlug = async (question: string) => {
const proposedSlug = slugify(question)
- const preexistingContract = await getContractFromSlug(trans, proposedSlug)
+ const preexistingContract = await getContractFromSlug(proposedSlug)
return preexistingContract
? proposedSlug + '-' + randomString()
@@ -351,42 +338,46 @@ const getSlug = async (trans: Transaction, question: string) => {
const firestore = admin.firestore()
-async function getContractFromSlug(trans: Transaction, slug: string) {
- const snap = await trans.get(
- firestore.collection('contracts').where('slug', '==', slug)
- )
+export async function getContractFromSlug(slug: string) {
+ const snap = await firestore
+ .collection('contracts')
+ .where('slug', '==', slug)
+ .get()
return snap.empty ? undefined : (snap.docs[0].data() as Contract)
}
async function createGroupLinks(
- trans: Transaction,
group: Group,
contractIds: string[],
userId: string
) {
for (const contractId of contractIds) {
- const contractRef = firestore.collection('contracts').doc(contractId)
- const contract = (await trans.get(contractRef)).data() as Contract
-
+ const contract = await getContract(contractId)
if (!contract?.groupSlugs?.includes(group.slug)) {
- await trans.update(contractRef, {
- groupSlugs: uniq([group.slug, ...(contract?.groupSlugs ?? [])]),
- })
+ await firestore
+ .collection('contracts')
+ .doc(contractId)
+ .update({
+ groupSlugs: uniq([group.slug, ...(contract?.groupSlugs ?? [])]),
+ })
}
if (!contract?.groupLinks?.map((gl) => gl.groupId).includes(group.id)) {
- await trans.update(contractRef, {
- groupLinks: [
- {
- groupId: group.id,
- name: group.name,
- slug: group.slug,
- userId,
- createdTime: Date.now(),
- } as GroupLink,
- ...(contract?.groupLinks ?? []),
- ],
- })
+ await firestore
+ .collection('contracts')
+ .doc(contractId)
+ .update({
+ groupLinks: [
+ {
+ groupId: group.id,
+ name: group.name,
+ slug: group.slug,
+ userId,
+ createdTime: Date.now(),
+ } as GroupLink,
+ ...(contract?.groupLinks ?? []),
+ ],
+ })
}
}
}
From 59de9799498a5710fc8d79ae18f8edd224d67af4 Mon Sep 17 00:00:00 2001
From: Pico2x
Date: Thu, 6 Oct 2022 17:04:00 +0100
Subject: [PATCH 29/44] Refactor Pinned Items into a reusable component
---
web/components/groups/group-overview.tsx | 203 +++++++++++++----------
web/components/pinned-select-modal.tsx | 8 +-
2 files changed, 118 insertions(+), 93 deletions(-)
diff --git a/web/components/groups/group-overview.tsx b/web/components/groups/group-overview.tsx
index 080453ca..d5cdaafa 100644
--- a/web/components/groups/group-overview.tsx
+++ b/web/components/groups/group-overview.tsx
@@ -145,8 +145,6 @@ function GroupOverviewPinned(props: {
}) {
const { group, posts, isEditable } = props
const [pinned, setPinned] = useState([])
- const [open, setOpen] = useState(false)
- const [editMode, setEditMode] = useState(false)
useEffect(() => {
async function getPinned() {
@@ -185,100 +183,127 @@ function GroupOverviewPinned(props: {
...(selectedItems as { itemId: string; type: 'contract' | 'post' }[]),
],
})
- setOpen(false)
+ }
+
+ function onDeleteClicked(index: number) {
+ const newPinned = group.pinnedItems.filter((item) => {
+ return item.itemId !== group.pinnedItems[index].itemId
+ })
+ updateGroup(group, { pinnedItems: newPinned })
}
return isEditable || (group.pinnedItems && group.pinnedItems.length > 0) ? (
- pinned.length > 0 || isEditable ? (
-
-
-
- {isEditable && (
-
- )}
-
-
-
- {pinned.length == 0 && !editMode && (
-
-
- No pinned items yet. Click the edit button to add some!
-
-
- )}
- {pinned.map((element, index) => (
-
- {element}
+
+ ) : (
+
+ )
+}
- {editMode && (
-
{
- const newPinned = group.pinnedItems.filter((item) => {
- return item.itemId !== group.pinnedItems[index].itemId
- })
- updateGroup(group, { pinnedItems: newPinned })
- }}
- />
- )}
-
- ))}
- {editMode && group.pinnedItems && pinned.length < 6 && (
-
-
-
-
-
+export function PinnedItems(props: {
+ posts: Post[]
+ isEditable: boolean
+ pinned: JSX.Element[]
+ onDeleteClicked: (index: number) => void
+ onSubmit: (selectedItems: { itemId: string; type: string }[]) => void
+ group?: Group
+ modalMessage: string
+}) {
+ const {
+ isEditable,
+ pinned,
+ onDeleteClicked,
+ onSubmit,
+ posts,
+ group,
+ modalMessage,
+ } = props
+ const [editMode, setEditMode] = useState(false)
+ const [open, setOpen] = useState(false)
+
+ return pinned.length > 0 || isEditable ? (
+
+
+
+ {isEditable && (
+
-
- Pin posts or markets to the overview of this group.
+
+ )}
+
+
+
+ {pinned.length == 0 && !editMode && (
+
+
+ No pinned items yet. Click the edit button to add some!
+
- }
- onSubmit={onSubmit}
- />
+ )}
+ {pinned.map((element, index) => (
+
+ {element}
+
+ {editMode && onDeleteClicked(index)} />}
+
+ ))}
+ {editMode && pinned.length < 6 && (
+
+
+
+
+
+ )}
+
- ) : (
-
- )
+ {modalMessage}
+ }
+ onSubmit={onSubmit}
+ />
+
) : (
<>>
)
diff --git a/web/components/pinned-select-modal.tsx b/web/components/pinned-select-modal.tsx
index e72deee2..c43c7534 100644
--- a/web/components/pinned-select-modal.tsx
+++ b/web/components/pinned-select-modal.tsx
@@ -20,8 +20,8 @@ export function PinnedSelectModal(props: {
selectedItems: { itemId: string; type: string }[]
) => void | Promise
contractSearchOptions?: Partial[0]>
- group: Group
posts: Post[]
+ group?: Group
}) {
const {
title,
@@ -134,8 +134,8 @@ export function PinnedSelectModal(props: {
highlightClassName:
'!bg-indigo-100 outline outline-2 outline-indigo-300',
}}
- additionalFilter={{ groupSlug: group.slug }}
- persistPrefix={`group-${group.slug}`}
+ additionalFilter={group ? { groupSlug: group.slug } : undefined}
+ persistPrefix={group ? `group-${group.slug}` : undefined}
headerClassName="bg-white sticky"
{...contractSearchOptions}
/>
@@ -152,7 +152,7 @@ export function PinnedSelectModal(props: {
'!bg-indigo-100 outline outline-2 outline-indigo-300',
}}
/>
- {posts.length === 0 && (
+ {posts.length == 0 && (
No posts yet
)}
From 853e3e48967ac83173269edf698f45ba91e72fdd Mon Sep 17 00:00:00 2001
From: Austin Chen