From 03a3df261c8b747999ba8a449e41e83bfecef56f Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 17 Dec 2021 17:16:42 -0600 Subject: [PATCH] contract slugs --- functions/src/types/contract.ts | 4 +++- web/components/bets-list.tsx | 10 ++++---- web/hooks/use-contract.ts | 16 ++++++++++--- web/lib/firebase/contracts.ts | 23 ++++++++++++++++--- web/lib/service/create-contract.ts | 23 +++++++++++-------- .../{[contractId].tsx => [slug].tsx} | 14 +++++------ web/pages/create.tsx | 3 ++- 7 files changed, 65 insertions(+), 28 deletions(-) rename web/pages/[username]/{[contractId].tsx => [slug].tsx} (89%) diff --git a/functions/src/types/contract.ts b/functions/src/types/contract.ts index 44cd57df..8278206b 100644 --- a/functions/src/types/contract.ts +++ b/functions/src/types/contract.ts @@ -1,5 +1,7 @@ export type Contract = { - id: string // Chosen by creator; must be unique + id: string + slug: string // auto-generated; must be unique + creatorId: string creatorName: string diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index 5f027081..48c082e6 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -8,7 +8,7 @@ import { User } from '../lib/firebase/users' import { formatMoney, formatPercent } from '../lib/util/format' import { Col } from './layout/col' import { Spacer } from './layout/spacer' -import { Contract, getContract, path } from '../lib/firebase/contracts' +import { Contract, getContractFromId, path } from '../lib/firebase/contracts' import { Row } from './layout/row' import { UserLink } from './user-page' import { @@ -29,9 +29,11 @@ export function BetsList(props: { user: User }) { const contractIds = _.uniq(loadedBets.map((bet) => bet.contractId)) let disposed = false - Promise.all(contractIds.map((id) => getContract(id))).then((contracts) => { - if (!disposed) setContracts(contracts.filter(Boolean) as Contract[]) - }) + Promise.all(contractIds.map((id) => getContractFromId(id))).then( + (contracts) => { + if (!disposed) setContracts(contracts.filter(Boolean) as Contract[]) + } + ) return () => { disposed = true diff --git a/web/hooks/use-contract.ts b/web/hooks/use-contract.ts index 41473c84..d48cd26a 100644 --- a/web/hooks/use-contract.ts +++ b/web/hooks/use-contract.ts @@ -1,5 +1,9 @@ import { useEffect, useState } from 'react' -import { Contract, listenForContract } from '../lib/firebase/contracts' +import { + Contract, + getContractFromSlug, + listenForContract, +} from '../lib/firebase/contracts' export const useContract = (contractId: string) => { const [contract, setContract] = useState( @@ -14,14 +18,20 @@ export const useContract = (contractId: string) => { } export const useContractWithPreload = ( - contractId: string, + slug: string, initial: Contract | null ) => { const [contract, setContract] = useState(initial) + const [contractId, setContractId] = useState( + initial?.id + ) useEffect(() => { if (contractId) return listenForContract(contractId, setContract) - }, [contractId]) + + if (contractId !== null) + getContractFromSlug(slug).then((c) => setContractId(c?.id || null)) + }, [contractId, slug]) return contract } diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index 63147616..3ca08157 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -16,7 +16,9 @@ import { import dayjs from 'dayjs' export type Contract = { - id: string // Chosen by creator; must be unique + id: string + slug: string // auto-generated; must be unique + creatorId: string creatorName: string @@ -42,7 +44,7 @@ export function path(contract: Contract) { // For now, derive username from creatorName // Fix this when users can change their own names const username = contract.creatorName.replace(/\s+/g, '') - return `/${username}/${contract.id}` + return `/${username}/${contract.slug}` } export function compute(contract: Contract) { @@ -66,13 +68,28 @@ export async function setContract(contract: Contract) { await setDoc(docRef, contract) } -export async function getContract(contractId: string) { +export async function pushNewContract(contract: Omit) { + const newContractRef = doc(contractCollection) + const fullContract: Contract = { ...contract, id: newContractRef.id } + + await setDoc(newContractRef, fullContract) + return fullContract +} + +export async function getContractFromId(contractId: string) { const docRef = doc(db, 'contracts', contractId) const result = await getDoc(docRef) return result.exists() ? (result.data() as Contract) : undefined } +export async function getContractFromSlug(slug: string) { + const q = query(contractCollection, where('slug', '==', slug)) + const snapshot = await getDocs(q) + + return snapshot.empty ? undefined : (snapshot.docs[0].data() as Contract) +} + export async function deleteContract(contractId: string) { const docRef = doc(db, 'contracts', contractId) await deleteDoc(docRef) diff --git a/web/lib/service/create-contract.ts b/web/lib/service/create-contract.ts index 2ad5b115..d1158cc1 100644 --- a/web/lib/service/create-contract.ts +++ b/web/lib/service/create-contract.ts @@ -1,4 +1,9 @@ -import { Contract, getContract, setContract } from '../firebase/contracts' +import { + Contract, + getContractFromSlug, + pushNewContract, + setContract, +} from '../firebase/contracts' import { User } from '../firebase/users' import { randomString } from '../util/random-string' import { slugify } from '../util/slugify' @@ -10,16 +15,18 @@ export async function createContract( initialProb: number, creator: User ) { - const slug = slugify(question).substr(0, 35) + const proposedSlug = slugify(question).substring(0, 35) - const preexistingContract = await getContract(slug) + const preexistingContract = await getContractFromSlug(proposedSlug) - const contractId = preexistingContract ? slug + '-' + randomString() : slug + const slug = preexistingContract + ? proposedSlug + '-' + randomString() + : proposedSlug const { startYes, startNo } = calcStartPool(initialProb) - const contract: Contract = { - id: contractId, + const contract: Omit = { + slug, outcomeType: 'BINARY', creatorId: creator.id, @@ -38,9 +45,7 @@ export async function createContract( lastUpdatedTime: Date.now(), } - await setContract(contract) - - return contract + return await pushNewContract(contract) } export function calcStartPool(initialProb: number, initialCapital = 100) { diff --git a/web/pages/[username]/[contractId].tsx b/web/pages/[username]/[slug].tsx similarity index 89% rename from web/pages/[username]/[contractId].tsx rename to web/pages/[username]/[slug].tsx index bd6d67bd..c126eb50 100644 --- a/web/pages/[username]/[contractId].tsx +++ b/web/pages/[username]/[slug].tsx @@ -13,17 +13,17 @@ import { useBets } from '../../hooks/use-bets' import { Title } from '../../components/title' import { Spacer } from '../../components/layout/spacer' import { User } from '../../lib/firebase/users' -import { Contract, getContract } from '../../lib/firebase/contracts' +import { Contract, getContractFromSlug } from '../../lib/firebase/contracts' import { SEO } from '../../components/SEO' export async function getStaticProps(props: { params: any }) { - const { username, contractId } = props.params - const contract = (await getContract(contractId)) || null + const { username, slug } = props.params + const contract = (await getContractFromSlug(slug)) || null return { props: { username, - contractId, + slug, contract, }, @@ -37,12 +37,12 @@ export async function getStaticPaths() { export default function ContractPage(props: { contract: Contract | null - contractId: string + slug: string username: string }) { const user = useUser() - const contract = useContractWithPreload(props.contractId, props.contract) + const contract = useContractWithPreload(props.slug, props.contract) if (!contract) { return
Contract not found...
@@ -56,7 +56,7 @@ export default function ContractPage(props: {
diff --git a/web/pages/create.tsx b/web/pages/create.tsx index a5e24511..fd25f8b8 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -6,6 +6,7 @@ import { Header } from '../components/header' import { Spacer } from '../components/layout/spacer' import { Title } from '../components/title' import { useUser } from '../hooks/use-user' +import { path } from '../lib/firebase/contracts' import { createContract } from '../lib/service/create-contract' // Allow user to create a new contract @@ -33,7 +34,7 @@ export default function NewContract() { initialProb, creator ) - await router.push(`contract/${contract.id}`) + await router.push(path(contract)) } const descriptionPlaceholder = `e.g. This market will resolve to “Yes” if, by June 2, 2021, 11:59:59 PM ET, Paxlovid (also known under PF-07321332)...`