From 7a2eac604d3ce333ac73cc3b5611378387315476 Mon Sep 17 00:00:00 2001 From: jahooma Date: Wed, 19 Jan 2022 16:16:08 -0600 Subject: [PATCH] Fold type, fold page, query for fold contracts --- common/fold.ts | 17 +++++++ firestore.rules | 3 ++ web/lib/firebase/comments.ts | 4 +- web/lib/firebase/contracts.ts | 2 +- web/lib/firebase/folds.ts | 57 ++++++++++++++++++++++ web/lib/firebase/utils.ts | 4 +- web/pages/[username]/[contractSlug].tsx | 4 +- web/pages/activity.tsx | 12 +++-- web/pages/fold/[foldSlug].tsx | 63 +++++++++++++++++++++++++ web/pages/index.tsx | 1 + 10 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 common/fold.ts create mode 100644 web/lib/firebase/folds.ts create mode 100644 web/pages/fold/[foldSlug].tsx diff --git a/common/fold.ts b/common/fold.ts new file mode 100644 index 00000000..9ef27fd4 --- /dev/null +++ b/common/fold.ts @@ -0,0 +1,17 @@ +export type Fold = { + id: string + slug: string + name: string + curatorId: string // User id + createdTime: number + + tags: string + + contractIds: string[] + excludedContractIds: string[] + + // Invariant: exactly one of the following is defined. + // Default: creatorIds: undefined, excludedCreatorIds: [] + creatorIds?: string[] + excludedCreatorIds?: string[] +} diff --git a/firestore.rules b/firestore.rules index 4b1d7142..5e5f3637 100644 --- a/firestore.rules +++ b/firestore.rules @@ -35,5 +35,8 @@ service cloud.firestore { allow read; } + match /folds/{foldId} { + allow read; + } } } \ No newline at end of file diff --git a/web/lib/firebase/comments.ts b/web/lib/firebase/comments.ts index e58d8a1c..5b29bdfe 100644 --- a/web/lib/firebase/comments.ts +++ b/web/lib/firebase/comments.ts @@ -75,9 +75,7 @@ const recentCommentsQuery = query( ) export async function getRecentComments() { - const snapshot = await getDocs(recentCommentsQuery) - const comments = snapshot.docs.map((doc) => doc.data() as Comment) - return comments + return getValues(recentCommentsQuery) } export function listenForRecentComments( diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index a4d78b59..326dad22 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -54,7 +54,7 @@ export function contractMetrics(contract: Contract) { } const db = getFirestore(app) -const contractCollection = collection(db, 'contracts') +export const contractCollection = collection(db, 'contracts') // Push contract to Firestore export async function setContract(contract: Contract) { diff --git a/web/lib/firebase/folds.ts b/web/lib/firebase/folds.ts new file mode 100644 index 00000000..fd172a33 --- /dev/null +++ b/web/lib/firebase/folds.ts @@ -0,0 +1,57 @@ +import { collection, query, where } from 'firebase/firestore' +import { Fold } from '../../../common/fold' +import { Contract, contractCollection } from './contracts' +import { db } from './init' +import { getValues } from './utils' + +const foldCollection = collection(db, 'folds') + +export async function getFoldBySlug(slug: string) { + const q = query(foldCollection, where('slug', '==', slug)) + const folds = await getValues(q) + + return folds.length === 0 ? null : folds[0] +} + +export async function getFoldContracts(fold: Fold) { + const { + tags, + contractIds, + excludedContractIds, + creatorIds, + excludedCreatorIds, + } = fold + + const [tagsContracts, includedContracts] = await Promise.all([ + // TODO: if tags.length > 10, execute multiple parallel queries + getValues( + query(contractCollection, where('tags', 'array-contains-any', tags)) + ), + + // TODO: if contractIds.length > 10, execute multiple parallel queries + contractIds.length > 0 + ? getValues( + query(contractCollection, where('id', 'in', contractIds)) + ) + : [], + ]) + + const excludedContractsSet = new Set(excludedContractIds) + + const creatorSet = creatorIds ? new Set(creatorIds) : undefined + const excludedCreatorSet = excludedCreatorIds + ? new Set(excludedCreatorIds) + : undefined + + const approvedContracts = tagsContracts.filter((contract) => { + const { id, creatorId } = contract + + if (excludedContractsSet.has(id)) return false + if (creatorSet && !creatorSet.has(creatorId)) return false + if (excludedCreatorSet && excludedCreatorSet.has(creatorId)) return false + + return true + }) + + return [...approvedContracts, ...includedContracts] +} diff --git a/web/lib/firebase/utils.ts b/web/lib/firebase/utils.ts index 479f0087..fc516253 100644 --- a/web/lib/firebase/utils.ts +++ b/web/lib/firebase/utils.ts @@ -8,8 +8,8 @@ import { DocumentReference, } from 'firebase/firestore' -export const getValue = async (collectionName: string, docName: string) => { - const snap = await getDoc(doc(db, collectionName, docName)) +export const getValue = async (doc: DocumentReference) => { + const snap = await getDoc(doc) return snap.exists() ? (snap.data() as T) : null } diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index 2d192390..87a81bde 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -23,7 +23,9 @@ import { Bet, listAllBets } from '../../lib/firebase/bets' import { Comment, listAllComments } from '../../lib/firebase/comments' import Custom404 from '../404' -export async function getStaticProps(props: { params: any }) { +export async function getStaticProps(props: { + params: { username: string; contractSlug: string } +}) { const { username, contractSlug } = props.params const contract = (await getContractFromSlug(contractSlug)) || null const contractId = contract?.id diff --git a/web/pages/activity.tsx b/web/pages/activity.tsx index 44f33617..0ec3b5ad 100644 --- a/web/pages/activity.tsx +++ b/web/pages/activity.tsx @@ -68,15 +68,17 @@ export function ActivityFeed(props: { contracts: Contract[] contractBets: Bet[][] contractComments: Comment[][] + listenForChanges?: boolean }) { - const { contractBets, contractComments } = props + const { contractBets, contractComments, listenForChanges } = props const contracts = useContracts() ?? props.contracts const recentComments = useRecentComments() - const activeContracts = recentComments - ? findActiveContracts(contracts, recentComments) - : props.contracts + const activeContracts = + listenForChanges && recentComments + ? findActiveContracts(contracts, recentComments) + : props.contracts - return contracts.length > 0 ? ( + return activeContracts.length > 0 ? ( diff --git a/web/pages/fold/[foldSlug].tsx b/web/pages/fold/[foldSlug].tsx new file mode 100644 index 00000000..cd984f47 --- /dev/null +++ b/web/pages/fold/[foldSlug].tsx @@ -0,0 +1,63 @@ +import { Fold } from '../../../common/fold' +import { Comment } from '../../../common/comment' +import { Page } from '../../components/page' +import { Title } from '../../components/title' +import { Bet, listAllBets } from '../../lib/firebase/bets' +import { getRecentComments, listAllComments } from '../../lib/firebase/comments' +import { Contract } from '../../lib/firebase/contracts' +import { getFoldBySlug, getFoldContracts } from '../../lib/firebase/folds' +import { ActivityFeed, findActiveContracts } from '../activity' + +export async function getStaticProps(props: { params: { foldSlug: string } }) { + const { foldSlug } = props.params + + const recentCommentsPromise = getRecentComments().catch(() => []) + + const fold = await getFoldBySlug(foldSlug) + const contracts = fold ? await getFoldContracts(fold) : [] + + const recentComments = await recentCommentsPromise + const activeContracts = findActiveContracts(contracts, recentComments) + const activeContractBets = await Promise.all( + activeContracts.map((contract) => listAllBets(contract.id)) + ) + const activeContractComments = await Promise.all( + activeContracts.map((contract) => listAllComments(contract.id)) + ) + + return { + props: { + fold, + activeContracts, + activeContractBets, + activeContractComments, + }, + + revalidate: 60, // regenerate after a minute + } +} + +export async function getStaticPaths() { + return { paths: [], fallback: 'blocking' } +} + +export default function FoldPage(props: { + fold: Fold + activeContracts: Contract[] + activeContractBets: Bet[][] + activeContractComments: Comment[][] +}) { + const { fold, activeContracts, activeContractBets, activeContractComments } = + props + + return ( + <Page> + <Title text={fold.name} /> + <ActivityFeed + contracts={activeContracts} + contractBets={activeContractBets} + contractComments={activeContractComments} + /> + </Page> + ) +} diff --git a/web/pages/index.tsx b/web/pages/index.tsx index acf1eb2a..77cba33f 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -73,6 +73,7 @@ const Home = (props: { contracts={activeContracts} contractBets={activeContractBets} contractComments={activeContractComments} + listenForChanges /> </Page> )