From 6c8c0683279820503f3bbe1b6b8fd2f5f69b959f Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Sat, 23 Jul 2022 13:48:28 -0700 Subject: [PATCH 01/91] Write script to fix old comments without IDs and user IDs (#680) --- functions/src/scripts/backfill-comment-ids.ts | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 functions/src/scripts/backfill-comment-ids.ts diff --git a/functions/src/scripts/backfill-comment-ids.ts b/functions/src/scripts/backfill-comment-ids.ts new file mode 100644 index 00000000..e6bb6902 --- /dev/null +++ b/functions/src/scripts/backfill-comment-ids.ts @@ -0,0 +1,55 @@ +// We have some old comments without IDs and user IDs. Let's fill them in. +// Luckily, this was back when all comments had associated bets, so it's possible +// to retrieve the user IDs through the bets. + +import * as admin from 'firebase-admin' +import { QueryDocumentSnapshot } from 'firebase-admin/firestore' +import { initAdmin } from './script-init' +import { log, writeAsync } from '../utils' +import { Bet } from '../../../common/bet' + +initAdmin() +const firestore = admin.firestore() + +const getUserIdsByCommentId = async (comments: QueryDocumentSnapshot[]) => { + const bets = await firestore.collectionGroup('bets').get() + log(`Loaded ${bets.size} bets.`) + const betsById = Object.fromEntries( + bets.docs.map((b) => [b.id, b.data() as Bet]) + ) + return Object.fromEntries( + comments.map((c) => [c.id, betsById[c.data().betId].userId]) + ) +} + +if (require.main === module) { + const commentsQuery = firestore.collectionGroup('comments') + commentsQuery.get().then(async (commentSnaps) => { + log(`Loaded ${commentSnaps.size} comments.`) + const needsFilling = commentSnaps.docs.filter((ct) => { + return !('id' in ct.data()) || !('userId' in ct.data()) + }) + log(`${needsFilling.length} comments need IDs.`) + const userIdNeedsFilling = needsFilling.filter((ct) => { + return !('userId' in ct.data()) + }) + log(`${userIdNeedsFilling.length} comments need user IDs.`) + const userIdsByCommentId = + userIdNeedsFilling.length > 0 + ? await getUserIdsByCommentId(userIdNeedsFilling) + : {} + const updates = needsFilling.map((ct) => { + const fields: { [k: string]: unknown } = {} + if (!ct.data().id) { + fields.id = ct.id + } + if (!ct.data().userId && userIdsByCommentId[ct.id]) { + fields.userId = userIdsByCommentId[ct.id] + } + return { doc: ct.ref, fields } + }) + log(`Updating ${updates.length} comments.`) + await writeAsync(firestore, updates) + log(`Updated all comments.`) + }) +} From f4e45829137c681867a44bd6846f4eff715fc76b Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Sat, 23 Jul 2022 15:04:11 -0600 Subject: [PATCH 02/91] Add group slug during create --- web/lib/firebase/groups.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/lib/firebase/groups.ts b/web/lib/firebase/groups.ts index 151e7fa1..f782f6a8 100644 --- a/web/lib/firebase/groups.ts +++ b/web/lib/firebase/groups.ts @@ -187,6 +187,7 @@ export async function setContractGroupLinks( userId: string ) { await updateContract(contractId, { + groupSlugs: [group.slug], groupLinks: [ { groupId: group.id, From 6c89e5f18fd6105e07a3f3efb393e371f0fec3f6 Mon Sep 17 00:00:00 2001 From: Sinclair Chen Date: Sat, 23 Jul 2022 20:37:34 -0700 Subject: [PATCH 03/91] Add @ mentions to editor (#670) * Add @ mentions to editor * Fix mention list not loading * Sort mention list by prefix, follow count * Render at mention with Linkify component - mentions are now Next rather than - fix bug where editor.getText() returns [object Object] for mentions - fix mention rendering for posted markets --- common/package.json | 1 + common/util/parse.ts | 3 +- functions/package.json | 1 + web/components/editor.tsx | 62 +++++++++++------- web/components/editor/mention-list.tsx | 62 ++++++++++++++++++ web/components/editor/mention-suggestion.ts | 72 +++++++++++++++++++++ web/components/editor/mention.tsx | 29 +++++++++ web/package.json | 4 +- yarn.lock | 20 +++++- 9 files changed, 228 insertions(+), 26 deletions(-) create mode 100644 web/components/editor/mention-list.tsx create mode 100644 web/components/editor/mention-suggestion.ts create mode 100644 web/components/editor/mention.tsx diff --git a/common/package.json b/common/package.json index 6f0f5b29..c324379f 100644 --- a/common/package.json +++ b/common/package.json @@ -10,6 +10,7 @@ "dependencies": { "@tiptap/extension-image": "2.0.0-beta.30", "@tiptap/extension-link": "2.0.0-beta.43", + "@tiptap/extension-mention": "2.0.0-beta.102", "@tiptap/starter-kit": "2.0.0-beta.190", "lodash": "4.17.21" }, diff --git a/common/util/parse.ts b/common/util/parse.ts index cdaa6a6c..cacd0862 100644 --- a/common/util/parse.ts +++ b/common/util/parse.ts @@ -20,6 +20,7 @@ import { Text } from '@tiptap/extension-text' // other tiptap extensions import { Image } from '@tiptap/extension-image' import { Link } from '@tiptap/extension-link' +import { Mention } from '@tiptap/extension-mention' import Iframe from './tiptap-iframe' export function parseTags(text: string) { @@ -81,9 +82,9 @@ export const exhibitExts = [ Image, Link, + Mention, Iframe, ] -// export const exhibitExts = [StarterKit as unknown as Extension, Image] export function richTextToString(text?: JSONContent) { return !text ? '' : generateText(text, exhibitExts) diff --git a/functions/package.json b/functions/package.json index f8657516..fe63dc4e 100644 --- a/functions/package.json +++ b/functions/package.json @@ -27,6 +27,7 @@ "@tiptap/core": "2.0.0-beta.181", "@tiptap/extension-image": "2.0.0-beta.30", "@tiptap/extension-link": "2.0.0-beta.43", + "@tiptap/extension-mention": "2.0.0-beta.102", "@tiptap/starter-kit": "2.0.0-beta.190", "firebase-admin": "10.0.0", "firebase-functions": "3.21.2", diff --git a/web/components/editor.tsx b/web/components/editor.tsx index 4dfddac9..963cea7e 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -11,6 +11,7 @@ import { import StarterKit from '@tiptap/starter-kit' import { Image } from '@tiptap/extension-image' import { Link } from '@tiptap/extension-link' +import { Mention } from '@tiptap/extension-mention' import clsx from 'clsx' import { useEffect, useState } from 'react' import { Linkify } from './linkify' @@ -19,6 +20,9 @@ import { useMutation } from 'react-query' import { exhibitExts } from 'common/util/parse' import { FileUploadButton } from './file-upload-button' import { linkClass } from './site-link' +import { useUsers } from 'web/hooks/use-users' +import { mentionSuggestion } from './editor/mention-suggestion' +import { DisplayMention } from './editor/mention' import Iframe from 'common/util/tiptap-iframe' import { CodeIcon, PhotographIcon } from '@heroicons/react/solid' import { Modal } from './layout/modal' @@ -40,33 +44,41 @@ export function useTextEditor(props: { }) { const { placeholder, max, defaultValue = '', disabled } = props + const users = useUsers() + const editorClass = clsx( proseClass, 'min-h-[6em] resize-none outline-none border-none pt-3 px-4 focus:ring-0' ) - const editor = useEditor({ - editorProps: { attributes: { class: editorClass } }, - extensions: [ - StarterKit.configure({ - heading: { levels: [1, 2, 3] }, - }), - Placeholder.configure({ - placeholder, - emptyEditorClass: - 'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0', - }), - CharacterCount.configure({ limit: max }), - Image, - Link.configure({ - HTMLAttributes: { - class: clsx('no-underline !text-indigo-700', linkClass), - }, - }), - Iframe, - ], - content: defaultValue, - }) + const editor = useEditor( + { + editorProps: { attributes: { class: editorClass } }, + extensions: [ + StarterKit.configure({ + heading: { levels: [1, 2, 3] }, + }), + Placeholder.configure({ + placeholder, + emptyEditorClass: + 'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0', + }), + CharacterCount.configure({ limit: max }), + Image, + Link.configure({ + HTMLAttributes: { + class: clsx('no-underline !text-indigo-700', linkClass), + }, + }), + DisplayMention.configure({ + suggestion: mentionSuggestion(users), + }), + Iframe, + ], + content: defaultValue, + }, + [!users.length] // passed as useEffect dependency. (re-render editor when users load, to update mention menu) + ) const upload = useUploadMutation(editor) @@ -261,7 +273,11 @@ function RichContent(props: { content: JSONContent | string }) { const { content } = props const editor = useEditor({ editorProps: { attributes: { class: proseClass } }, - extensions: exhibitExts, + extensions: [ + // replace tiptap's Mention with ours, to add style and link + ...exhibitExts.filter((ex) => ex.name !== Mention.name), + DisplayMention, + ], content, editable: false, }) diff --git a/web/components/editor/mention-list.tsx b/web/components/editor/mention-list.tsx new file mode 100644 index 00000000..f9e67daf --- /dev/null +++ b/web/components/editor/mention-list.tsx @@ -0,0 +1,62 @@ +import { SuggestionProps } from '@tiptap/suggestion' +import clsx from 'clsx' +import { User } from 'common/user' +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' +import { Avatar } from '../avatar' + +// copied from https://tiptap.dev/api/nodes/mention#usage +export const MentionList = forwardRef((props: SuggestionProps, ref) => { + const { items: users, command } = props + + const [selectedIndex, setSelectedIndex] = useState(0) + useEffect(() => setSelectedIndex(0), [users]) + + const submitUser = (index: number) => { + const user = users[index] + if (user) command({ id: user.id, label: user.username } as any) + } + + const onUp = () => + setSelectedIndex((i) => (i + users.length - 1) % users.length) + const onDown = () => setSelectedIndex((i) => (i + 1) % users.length) + const onEnter = () => submitUser(selectedIndex) + + useImperativeHandle(ref, () => ({ + onKeyDown: ({ event }: any) => { + if (event.key === 'ArrowUp') { + onUp() + return true + } + if (event.key === 'ArrowDown') { + onDown() + return true + } + if (event.key === 'Enter') { + onEnter() + return true + } + return false + }, + })) + + return ( +
+ {!users.length ? ( + No results... + ) : ( + users.map((user, i) => ( + + )) + )} +
+ ) +}) diff --git a/web/components/editor/mention-suggestion.ts b/web/components/editor/mention-suggestion.ts new file mode 100644 index 00000000..e21789c9 --- /dev/null +++ b/web/components/editor/mention-suggestion.ts @@ -0,0 +1,72 @@ +import type { MentionOptions } from '@tiptap/extension-mention' +import { ReactRenderer } from '@tiptap/react' +import { User } from 'common/user' +import { searchInAny } from 'common/util/parse' +import { orderBy } from 'lodash' +import tippy from 'tippy.js' +import { MentionList } from './mention-list' + +type Suggestion = MentionOptions['suggestion'] + +const beginsWith = (text: string, query: string) => + text.toLocaleLowerCase().startsWith(query.toLocaleLowerCase()) + +// copied from https://tiptap.dev/api/nodes/mention#usage +export const mentionSuggestion = (users: User[]): Suggestion => ({ + items: ({ query }) => + orderBy( + users.filter((u) => searchInAny(query, u.username, u.name)), + [ + (u) => [u.name, u.username].some((s) => beginsWith(s, query)), + 'followerCountCached', + ], + ['desc', 'desc'] + ).slice(0, 5), + render: () => { + let component: ReactRenderer + let popup: ReturnType + return { + onStart: (props) => { + component = new ReactRenderer(MentionList, { + props, + editor: props.editor, + }) + if (!props.clientRect) { + return + } + + popup = tippy('body', { + getReferenceClientRect: props.clientRect as any, + appendTo: () => document.body, + content: component.element, + showOnCreate: true, + interactive: true, + trigger: 'manual', + placement: 'bottom-start', + }) + }, + onUpdate(props) { + component.updateProps(props) + + if (!props.clientRect) { + return + } + + popup[0].setProps({ + getReferenceClientRect: props.clientRect as any, + }) + }, + onKeyDown(props) { + if (props.event.key === 'Escape') { + popup[0].hide() + return true + } + return (component.ref as any)?.onKeyDown(props) + }, + onExit() { + popup[0].destroy() + component.destroy() + }, + } + }, +}) diff --git a/web/components/editor/mention.tsx b/web/components/editor/mention.tsx new file mode 100644 index 00000000..3ad5de39 --- /dev/null +++ b/web/components/editor/mention.tsx @@ -0,0 +1,29 @@ +import Mention from '@tiptap/extension-mention' +import { + mergeAttributes, + NodeViewWrapper, + ReactNodeViewRenderer, +} from '@tiptap/react' +import clsx from 'clsx' +import { Linkify } from '../linkify' + +const name = 'mention-component' + +const MentionComponent = (props: any) => { + return ( + + + + ) +} + +/** + * Mention extension that renders React. See: + * https://tiptap.dev/guide/custom-extensions#extend-existing-extensions + * https://tiptap.dev/guide/node-views/react#render-a-react-component + */ +export const DisplayMention = Mention.extend({ + parseHTML: () => [{ tag: name }], + renderHTML: ({ HTMLAttributes }) => [name, mergeAttributes(HTMLAttributes)], + addNodeView: () => ReactNodeViewRenderer(MentionComponent), +}) diff --git a/web/package.json b/web/package.json index d09ccaf0..9f27643e 100644 --- a/web/package.json +++ b/web/package.json @@ -27,6 +27,7 @@ "@tiptap/extension-character-count": "2.0.0-beta.31", "@tiptap/extension-image": "2.0.0-beta.30", "@tiptap/extension-link": "2.0.0-beta.43", + "@tiptap/extension-mention": "2.0.0-beta.102", "@tiptap/extension-placeholder": "2.0.0-beta.53", "@tiptap/react": "2.0.0-beta.114", "@tiptap/starter-kit": "2.0.0-beta.190", @@ -49,7 +50,8 @@ "react-hot-toast": "2.2.0", "react-instantsearch-hooks-web": "6.24.1", "react-query": "3.39.0", - "string-similarity": "^4.0.4" + "string-similarity": "^4.0.4", + "tippy.js": "6.3.7" }, "devDependencies": { "@tailwindcss/forms": "0.4.0", diff --git a/yarn.lock b/yarn.lock index ffa8e6f0..019a3dd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3010,6 +3010,15 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.23.tgz#6d1ac7235462b0bcee196f42bb1871669480b843" integrity sha512-AkzvdELz3ZnrlZM0r9+ritBDOnAjXHR/8zCZhW0ZlWx4zyKPMsNG5ygivY+xr4QT65NEGRT8P8b2zOhXrMjjMQ== +"@tiptap/extension-mention@2.0.0-beta.102": + version "2.0.0-beta.102" + resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.0.0-beta.102.tgz#a80036b0a4481efc4f69b788af3f5c76428624cc" + integrity sha512-QTBBpWnRnoV7/ZW31HwhPvZL3HiwnlehlHSLeMioVxAQPF5WrRtlOpxK/SRu7+KuwdCb7ZA1eWW/yjbXI3oktg== + dependencies: + "@tiptap/suggestion" "^2.0.0-beta.97" + prosemirror-model "1.18.1" + prosemirror-state "1.4.1" + "@tiptap/extension-ordered-list@^2.0.0-beta.30": version "2.0.0-beta.30" resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.30.tgz#1f656b664302d90272c244b2e478d7056203f2a8" @@ -3073,6 +3082,15 @@ "@tiptap/extension-strike" "^2.0.0-beta.29" "@tiptap/extension-text" "^2.0.0-beta.17" +"@tiptap/suggestion@^2.0.0-beta.97": + version "2.0.0-beta.97" + resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.97.tgz#2e3dc20deebc2c37c5d39c848e61e9c837e7188a" + integrity sha512-3NWG+HE7v2w97Ek6z1tUosoZKpCDH+oAtIG9XoNkK1PmlaVV/H4d6HT9uPX+Y6SeN7fSAqlcXFUGLXcDi9d+Zw== + dependencies: + prosemirror-model "1.18.1" + prosemirror-state "1.4.1" + prosemirror-view "1.26.2" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -11058,7 +11076,7 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tippy.js@^6.3.7: +tippy.js@6.3.7, tippy.js@^6.3.7: version "6.3.7" resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ== From 1f655acddb0f36406a001ba67e7ddfcb358b2c7c Mon Sep 17 00:00:00 2001 From: Olivia Appleton Date: Sun, 24 Jul 2022 02:33:19 -0400 Subject: [PATCH 04/91] Add my market manager tool (#690) --- docs/docs/awesome-manifold.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/awesome-manifold.md b/docs/docs/awesome-manifold.md index ade5caee..44167bcb 100644 --- a/docs/docs/awesome-manifold.md +++ b/docs/docs/awesome-manifold.md @@ -15,6 +15,7 @@ A list of community-created projects built on, or related to, Manifold Markets. - [PyManifold](https://github.com/bcongdon/PyManifold) - Python client for the Manifold API - [manifold-markets-python](https://github.com/vluzko/manifold-markets-python) - Python tools for working with Manifold Markets (including various accuracy metrics) +- [ManifoldMarketManager](https://github.com/gappleto97/ManifoldMarketManager) - Python script and library to automatically manage markets - [manifeed](https://github.com/joy-void-joy/manifeed) - Tool that creates an RSS feed for new Manifold markets ## Bots From 6ad43b02c79aca0e188fcbd127630cdd54f90bb8 Mon Sep 17 00:00:00 2001 From: Austin Chen Date: Sun, 24 Jul 2022 00:11:33 -0700 Subject: [PATCH 05/91] Show the number of comments and bets --- web/components/contract/contract-tabs.tsx | 15 ++++++++++++--- web/components/feed/contract-activity.tsx | 6 +----- web/components/layout/tabs.tsx | 5 +++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx index c7759fb8..fbf056e3 100644 --- a/web/components/contract/contract-tabs.tsx +++ b/web/components/contract/contract-tabs.tsx @@ -9,6 +9,7 @@ import { Tabs } from '../layout/tabs' import { Col } from '../layout/col' import { CommentTipMap } from 'web/hooks/use-tip-txns' import { LiquidityProvision } from 'common/liquidity-provision' +import { useComments } from 'web/hooks/use-comments' export function ContractTabs(props: { contract: Contract @@ -18,11 +19,15 @@ export function ContractTabs(props: { comments: Comment[] tips: CommentTipMap }) { - const { contract, user, bets, comments, tips, liquidityProvisions } = props + const { contract, user, bets, tips, liquidityProvisions } = props const { outcomeType } = contract const userBets = user && bets.filter((bet) => bet.userId === user.id) + // Load comments here, so the badge count will be correct + const updatedComments = useComments(contract.id) + const comments = updatedComments ?? props.comments + const betActivity = ( !bet.isRedemption && bet.amount !== 0 diff --git a/web/components/layout/tabs.tsx b/web/components/layout/tabs.tsx index 8aec39b1..da3593a1 100644 --- a/web/components/layout/tabs.tsx +++ b/web/components/layout/tabs.tsx @@ -10,6 +10,8 @@ type Tab = { content: ReactNode // If set, change the url to this href when the tab is selected href?: string + // If set, show a badge with this content + badge?: string } export function Tabs(props: { @@ -63,6 +65,9 @@ export function Tabs(props: { > {tab.tabIcon && {tab.tabIcon}} + {tab.badge ? ( +
{tab.badge}
+ ) : null} {tab.title}
From a1d5d161ddd779d5057ba727d9ce65a4681b2756 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Sun, 24 Jul 2022 00:26:38 -0700 Subject: [PATCH 06/91] Revamp backend code to support good local function development (#657) * Move concurrently dep upwards * Add express as explicit dependency * Accept just one HTTP method per endpoint * Fix endpoint option coalescing * Expressification of cloud functions * Nicer logging of API requests * Refactor web package.json * Add ts-node and nodemon to dev dependencies, bring back cors * Add scaffolding to point dev server at local functions * Enable emulator in dev server scaffolding * Fix up a little stuff I broke --- common/api.ts | 4 +- dev.sh | 45 +++++++++ functions/package.json | 4 + functions/src/api.ts | 63 ++++++------ functions/src/health.ts | 2 +- functions/src/index.ts | 79 +++++++++++---- functions/src/scripts/script-init.ts | 20 ++-- functions/src/serve.ts | 68 +++++++++++++ functions/src/stripe.ts | 27 ++--- functions/src/unsubscribe.ts | 110 +++++++++++---------- package.json | 5 +- web/package.json | 11 ++- yarn.lock | 143 +++++++++++++++++++++++++-- 13 files changed, 451 insertions(+), 130 deletions(-) create mode 100755 dev.sh create mode 100644 functions/src/serve.ts diff --git a/common/api.ts b/common/api.ts index b9376be5..1ae9a5fd 100644 --- a/common/api.ts +++ b/common/api.ts @@ -12,7 +12,9 @@ export class APIError extends Error { } export function getFunctionUrl(name: string) { - if (process.env.NEXT_PUBLIC_FIREBASE_EMULATE) { + if (process.env.NEXT_PUBLIC_FUNCTIONS_URL) { + return `${process.env.NEXT_PUBLIC_FUNCTIONS_URL}/${name}` + } else if (process.env.NEXT_PUBLIC_FIREBASE_EMULATE) { const { projectId, region } = ENV_CONFIG.firebaseConfig return `http://localhost:5001/${projectId}/${region}/${name}` } else { diff --git a/dev.sh b/dev.sh new file mode 100755 index 00000000..178e20e8 --- /dev/null +++ b/dev.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +ENV=${1:-dev} +case $ENV in + dev) + FIREBASE_PROJECT=dev + NEXT_ENV=DEV + EMULATOR=false ;; + prod) + FIREBASE_PROJECT=prod + NEXT_ENV=PROD + EMULATOR=false ;; + localdb) + FIREBASE_PROJECT=dev + NEXT_ENV=DEV + EMULATOR=true ;; + *) + echo "Invalid environment; must be dev, prod, or localdb." + exit 1 +esac + +firebase use $FIREBASE_PROJECT + +if [ ! -z $EMULATOR ] +then + npx concurrently \ + -n FIRESTORE,FUNCTIONS,NEXT,TS \ + -c green,white,magenta,cyan \ + "yarn --cwd=functions firestore" \ + "cross-env FIRESTORE_EMULATOR_HOST=localhost:8080 yarn --cwd=functions dev" \ + "cross-env NEXT_PUBLIC_FUNCTIONS_URL=http://localhost:8088 \ + NEXT_PUBLIC_FIREBASE_EMULATE=TRUE \ + NEXT_PUBLIC_FIREBASE_ENV=${NEXT_ENV} \ + yarn --cwd=web serve" \ + "cross-env yarn --cwd=web ts-watch" +else + npx concurrently \ + -n FUNCTIONS,NEXT,TS \ + -c white,magenta,cyan \ + "yarn --cwd=functions dev" \ + "cross-env NEXT_PUBLIC_FUNCTIONS_URL=http://localhost:8088 \ + NEXT_PUBLIC_FIREBASE_ENV=${NEXT_ENV} \ + yarn --cwd=web serve" \ + "cross-env yarn --cwd=web ts-watch" +fi diff --git a/functions/package.json b/functions/package.json index fe63dc4e..b20a8fd0 100644 --- a/functions/package.json +++ b/functions/package.json @@ -12,6 +12,8 @@ "start": "yarn shell", "deploy": "firebase deploy --only functions", "logs": "firebase functions:log", + "dev": "nodemon src/serve.ts", + "firestore": "firebase emulators:start --only firestore --import=./firestore_export", "serve": "firebase use dev && yarn build && firebase emulators:start --only functions,firestore --import=./firestore_export", "db:update-local-from-remote": "yarn db:backup-remote && gsutil rsync -r gs://$npm_package_config_firestore/firestore_export ./firestore_export", "db:backup-local": "firebase emulators:export --force ./firestore_export", @@ -29,6 +31,8 @@ "@tiptap/extension-link": "2.0.0-beta.43", "@tiptap/extension-mention": "2.0.0-beta.102", "@tiptap/starter-kit": "2.0.0-beta.190", + "cors": "2.8.5", + "express": "4.18.1", "firebase-admin": "10.0.0", "firebase-functions": "3.21.2", "lodash": "4.17.21", diff --git a/functions/src/api.ts b/functions/src/api.ts index 8c01ea05..fdda0ad5 100644 --- a/functions/src/api.ts +++ b/functions/src/api.ts @@ -1,6 +1,7 @@ import * as admin from 'firebase-admin' -import { logger } from 'firebase-functions/v2' -import { HttpsOptions, onRequest, Request } from 'firebase-functions/v2/https' +import { Request, RequestHandler, Response } from 'express' +import { error } from 'firebase-functions/logger' +import { HttpsOptions } from 'firebase-functions/v2/https' import { log } from './utils' import { z } from 'zod' import { APIError } from '../../common/api' @@ -45,7 +46,7 @@ export const parseCredentials = async (req: Request): Promise => { return { kind: 'jwt', data: await auth.verifyIdToken(payload) } } catch (err) { // This is somewhat suspicious, so get it into the firebase console - logger.error('Error verifying Firebase JWT: ', err) + error('Error verifying Firebase JWT: ', err) throw new APIError(403, 'Error validating token.') } case 'Key': @@ -83,6 +84,11 @@ export const zTimestamp = () => { }, z.date()) } +export type EndpointDefinition = { + opts: EndpointOptions & { method: string } + handler: RequestHandler +} + export const validate = (schema: T, val: unknown) => { const result = schema.safeParse(val) if (!result.success) { @@ -99,12 +105,12 @@ export const validate = (schema: T, val: unknown) => { } } -interface EndpointOptions extends HttpsOptions { - methods?: string[] +export interface EndpointOptions extends HttpsOptions { + method?: string } const DEFAULT_OPTS = { - methods: ['POST'], + method: 'POST', minInstances: 1, concurrency: 100, memory: '2GiB', @@ -113,28 +119,29 @@ const DEFAULT_OPTS = { } export const newEndpoint = (endpointOpts: EndpointOptions, fn: Handler) => { - const opts = Object.assign(endpointOpts, DEFAULT_OPTS) - return onRequest(opts, async (req, res) => { - log('Request processing started.') - try { - if (!opts.methods.includes(req.method)) { - const allowed = opts.methods.join(', ') - throw new APIError(405, `This endpoint supports only ${allowed}.`) - } - const authedUser = await lookupUser(await parseCredentials(req)) - log('User credentials processed.') - res.status(200).json(await fn(req, authedUser)) - } catch (e) { - if (e instanceof APIError) { - const output: { [k: string]: unknown } = { message: e.message } - if (e.details != null) { - output.details = e.details + const opts = Object.assign({}, DEFAULT_OPTS, endpointOpts) + return { + opts, + handler: async (req: Request, res: Response) => { + log(`${req.method} ${req.url} ${JSON.stringify(req.body)}`) + try { + if (opts.method !== req.method) { + throw new APIError(405, `This endpoint supports only ${opts.method}.`) + } + const authedUser = await lookupUser(await parseCredentials(req)) + res.status(200).json(await fn(req, authedUser)) + } catch (e) { + if (e instanceof APIError) { + const output: { [k: string]: unknown } = { message: e.message } + if (e.details != null) { + output.details = e.details + } + res.status(e.code).json(output) + } else { + error(e) + res.status(500).json({ message: 'An unknown error occurred.' }) } - res.status(e.code).json(output) - } else { - logger.error(e) - res.status(500).json({ message: 'An unknown error occurred.' }) } - } - }) + }, + } as EndpointDefinition } diff --git a/functions/src/health.ts b/functions/src/health.ts index 938261db..4ce04e05 100644 --- a/functions/src/health.ts +++ b/functions/src/health.ts @@ -1,6 +1,6 @@ import { newEndpoint } from './api' -export const health = newEndpoint({ methods: ['GET'] }, async (_req, auth) => { +export const health = newEndpoint({ method: 'GET' }, async (_req, auth) => { return { message: 'Server is working.', uid: auth.uid, diff --git a/functions/src/index.ts b/functions/src/index.ts index df311886..239806de 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -1,4 +1,6 @@ import * as admin from 'firebase-admin' +import { onRequest } from 'firebase-functions/v2/https' +import { EndpointDefinition } from './api' admin.initializeApp() @@ -25,20 +27,63 @@ export * from './on-delete-group' export * from './score-contracts' // v2 -export * from './health' -export * from './transact' -export * from './change-user-info' -export * from './create-user' -export * from './create-answer' -export * from './place-bet' -export * from './cancel-bet' -export * from './sell-bet' -export * from './sell-shares' -export * from './claim-manalink' -export * from './create-contract' -export * from './add-liquidity' -export * from './withdraw-liquidity' -export * from './create-group' -export * from './resolve-market' -export * from './unsubscribe' -export * from './stripe' +import { health } from './health' +import { transact } from './transact' +import { changeuserinfo } from './change-user-info' +import { createuser } from './create-user' +import { createanswer } from './create-answer' +import { placebet } from './place-bet' +import { cancelbet } from './cancel-bet' +import { sellbet } from './sell-bet' +import { sellshares } from './sell-shares' +import { claimmanalink } from './claim-manalink' +import { createmarket } from './create-contract' +import { addliquidity } from './add-liquidity' +import { withdrawliquidity } from './withdraw-liquidity' +import { creategroup } from './create-group' +import { resolvemarket } from './resolve-market' +import { unsubscribe } from './unsubscribe' +import { stripewebhook, createcheckoutsession } from './stripe' + +const toCloudFunction = ({ opts, handler }: EndpointDefinition) => { + return onRequest(opts, handler as any) +} +const healthFunction = toCloudFunction(health) +const transactFunction = toCloudFunction(transact) +const changeUserInfoFunction = toCloudFunction(changeuserinfo) +const createUserFunction = toCloudFunction(createuser) +const createAnswerFunction = toCloudFunction(createanswer) +const placeBetFunction = toCloudFunction(placebet) +const cancelBetFunction = toCloudFunction(cancelbet) +const sellBetFunction = toCloudFunction(sellbet) +const sellSharesFunction = toCloudFunction(sellshares) +const claimManalinkFunction = toCloudFunction(claimmanalink) +const createMarketFunction = toCloudFunction(createmarket) +const addLiquidityFunction = toCloudFunction(addliquidity) +const withdrawLiquidityFunction = toCloudFunction(withdrawliquidity) +const createGroupFunction = toCloudFunction(creategroup) +const resolveMarketFunction = toCloudFunction(resolvemarket) +const unsubscribeFunction = toCloudFunction(unsubscribe) +const stripeWebhookFunction = toCloudFunction(stripewebhook) +const createCheckoutSessionFunction = toCloudFunction(createcheckoutsession) + +export { + healthFunction as health, + transactFunction as transact, + changeUserInfoFunction as changeuserinfo, + createUserFunction as createuser, + createAnswerFunction as createanswer, + placeBetFunction as placebet, + cancelBetFunction as cancelbet, + sellBetFunction as sellbet, + sellSharesFunction as sellshares, + claimManalinkFunction as claimmanalink, + createMarketFunction as createmarket, + addLiquidityFunction as addliquidity, + withdrawLiquidityFunction as withdrawliquidity, + createGroupFunction as creategroup, + resolveMarketFunction as resolvemarket, + unsubscribeFunction as unsubscribe, + stripeWebhookFunction as stripewebhook, + createCheckoutSessionFunction as createcheckoutsession, +} diff --git a/functions/src/scripts/script-init.ts b/functions/src/scripts/script-init.ts index cc17a620..5f7dc410 100644 --- a/functions/src/scripts/script-init.ts +++ b/functions/src/scripts/script-init.ts @@ -66,10 +66,18 @@ export const getServiceAccountCredentials = (env?: string) => { } export const initAdmin = (env?: string) => { - const serviceAccount = getServiceAccountCredentials(env) - console.log(`Initializing connection to ${serviceAccount.project_id}...`) - return admin.initializeApp({ - projectId: serviceAccount.project_id, - credential: admin.credential.cert(serviceAccount), - }) + try { + const serviceAccount = getServiceAccountCredentials(env) + console.log( + `Initializing connection to ${serviceAccount.project_id} Firebase...` + ) + return admin.initializeApp({ + projectId: serviceAccount.project_id, + credential: admin.credential.cert(serviceAccount), + }) + } catch (err) { + console.error(err) + console.log(`Initializing connection to default Firebase...`) + return admin.initializeApp() + } } diff --git a/functions/src/serve.ts b/functions/src/serve.ts new file mode 100644 index 00000000..77282951 --- /dev/null +++ b/functions/src/serve.ts @@ -0,0 +1,68 @@ +import * as cors from 'cors' +import * as express from 'express' +import { Express, Request, Response, NextFunction } from 'express' +import { EndpointDefinition } from './api' + +const PORT = 8088 + +import { initAdmin } from './scripts/script-init' +initAdmin() + +import { health } from './health' +import { transact } from './transact' +import { changeuserinfo } from './change-user-info' +import { createuser } from './create-user' +import { createanswer } from './create-answer' +import { placebet } from './place-bet' +import { cancelbet } from './cancel-bet' +import { sellbet } from './sell-bet' +import { sellshares } from './sell-shares' +import { claimmanalink } from './claim-manalink' +import { createmarket } from './create-contract' +import { addliquidity } from './add-liquidity' +import { withdrawliquidity } from './withdraw-liquidity' +import { creategroup } from './create-group' +import { resolvemarket } from './resolve-market' +import { unsubscribe } from './unsubscribe' +import { stripewebhook, createcheckoutsession } from './stripe' + +type Middleware = (req: Request, res: Response, next: NextFunction) => void +const app = express() + +const addEndpointRoute = ( + path: string, + endpoint: EndpointDefinition, + ...middlewares: Middleware[] +) => { + const method = endpoint.opts.method.toLowerCase() as keyof Express + const corsMiddleware = cors({ origin: endpoint.opts.cors }) + const allMiddleware = [...middlewares, corsMiddleware] + app.options(path, corsMiddleware) // preflight requests + app[method](path, ...allMiddleware, endpoint.handler) +} + +const addJsonEndpointRoute = (name: string, endpoint: EndpointDefinition) => { + addEndpointRoute(name, endpoint, express.json()) +} + +addEndpointRoute('/health', health) +addJsonEndpointRoute('/transact', transact) +addJsonEndpointRoute('/changeuserinfo', changeuserinfo) +addJsonEndpointRoute('/createuser', createuser) +addJsonEndpointRoute('/createanswer', createanswer) +addJsonEndpointRoute('/placebet', placebet) +addJsonEndpointRoute('/cancelbet', cancelbet) +addJsonEndpointRoute('/sellbet', sellbet) +addJsonEndpointRoute('/sellshares', sellshares) +addJsonEndpointRoute('/claimmanalink', claimmanalink) +addJsonEndpointRoute('/createmarket', createmarket) +addJsonEndpointRoute('/addliquidity', addliquidity) +addJsonEndpointRoute('/withdrawliquidity', withdrawliquidity) +addJsonEndpointRoute('/creategroup', creategroup) +addJsonEndpointRoute('/resolvemarket', resolvemarket) +addJsonEndpointRoute('/unsubscribe', unsubscribe) +addJsonEndpointRoute('/createcheckoutsession', createcheckoutsession) +addEndpointRoute('/stripewebhook', stripewebhook, express.raw()) + +app.listen(PORT) +console.log(`Serving functions on port ${PORT}.`) diff --git a/functions/src/stripe.ts b/functions/src/stripe.ts index 450bbe35..79f0ad53 100644 --- a/functions/src/stripe.ts +++ b/functions/src/stripe.ts @@ -1,7 +1,7 @@ -import { onRequest } from 'firebase-functions/v2/https' import * as admin from 'firebase-admin' import Stripe from 'stripe' +import { EndpointDefinition } from './api' import { getPrivateUser, getUser, isProd, payUser } from './utils' import { sendThankYouEmail } from './emails' import { track } from './analytics' @@ -42,9 +42,9 @@ const manticDollarStripePrice = isProd() 10000: 'price_1K8bEiGdoFKoCJW7Us4UkRHE', } -export const createcheckoutsession = onRequest( - { minInstances: 1, secrets: ['STRIPE_APIKEY'] }, - async (req, res) => { +export const createcheckoutsession: EndpointDefinition = { + opts: { method: 'POST', minInstances: 1, secrets: ['STRIPE_APIKEY'] }, + handler: async (req, res) => { const userId = req.query.userId?.toString() const manticDollarQuantity = req.query.manticDollarQuantity?.toString() @@ -86,21 +86,24 @@ export const createcheckoutsession = onRequest( }) res.redirect(303, session.url || '') - } -) + }, +} -export const stripewebhook = onRequest( - { +export const stripewebhook: EndpointDefinition = { + opts: { + method: 'POST', minInstances: 1, secrets: ['MAILGUN_KEY', 'STRIPE_APIKEY', 'STRIPE_WEBHOOKSECRET'], }, - async (req, res) => { + handler: async (req, res) => { const stripe = initStripe() let event try { + // Cloud Functions jam the raw body into a special `rawBody` property + const rawBody = (req as any).rawBody ?? req.body event = stripe.webhooks.constructEvent( - req.rawBody, + rawBody, req.headers['stripe-signature'] as string, process.env.STRIPE_WEBHOOKSECRET as string ) @@ -116,8 +119,8 @@ export const stripewebhook = onRequest( } res.status(200).send('success') - } -) + }, +} const issueMoneys = async (session: StripeSession) => { const { id: sessionId } = session diff --git a/functions/src/unsubscribe.ts b/functions/src/unsubscribe.ts index 48dd29c0..fda20e16 100644 --- a/functions/src/unsubscribe.ts +++ b/functions/src/unsubscribe.ts @@ -1,66 +1,72 @@ -import { onRequest } from 'firebase-functions/v2/https' import * as admin from 'firebase-admin' +import { EndpointDefinition } from './api' import { getUser } from './utils' import { PrivateUser } from '../../common/user' -export const unsubscribe = onRequest({ minInstances: 1 }, async (req, res) => { - const id = req.query.id as string - let type = req.query.type as string - if (!id || !type) { - res.status(400).send('Empty id or type parameter.') - return - } +export const unsubscribe: EndpointDefinition = { + opts: { method: 'GET', minInstances: 1 }, + handler: async (req, res) => { + const id = req.query.id as string + let type = req.query.type as string + if (!id || !type) { + res.status(400).send('Empty id or type parameter.') + return + } - if (type === 'market-resolved') type = 'market-resolve' + if (type === 'market-resolved') type = 'market-resolve' - if ( - !['market-resolve', 'market-comment', 'market-answer', 'generic'].includes( - type - ) - ) { - res.status(400).send('Invalid type parameter.') - return - } + if ( + ![ + 'market-resolve', + 'market-comment', + 'market-answer', + 'generic', + ].includes(type) + ) { + res.status(400).send('Invalid type parameter.') + return + } - const user = await getUser(id) + const user = await getUser(id) - if (!user) { - res.send('This user is not currently subscribed or does not exist.') - return - } + if (!user) { + res.send('This user is not currently subscribed or does not exist.') + return + } - const { name } = user + const { name } = user - const update: Partial = { - ...(type === 'market-resolve' && { - unsubscribedFromResolutionEmails: true, - }), - ...(type === 'market-comment' && { - unsubscribedFromCommentEmails: true, - }), - ...(type === 'market-answer' && { - unsubscribedFromAnswerEmails: true, - }), - ...(type === 'generic' && { - unsubscribedFromGenericEmails: true, - }), - } + const update: Partial = { + ...(type === 'market-resolve' && { + unsubscribedFromResolutionEmails: true, + }), + ...(type === 'market-comment' && { + unsubscribedFromCommentEmails: true, + }), + ...(type === 'market-answer' && { + unsubscribedFromAnswerEmails: true, + }), + ...(type === 'generic' && { + unsubscribedFromGenericEmails: true, + }), + } - await firestore.collection('private-users').doc(id).update(update) + await firestore.collection('private-users').doc(id).update(update) - if (type === 'market-resolve') - res.send( - `${name}, you have been unsubscribed from market resolution emails on Manifold Markets.` - ) - else if (type === 'market-comment') - res.send( - `${name}, you have been unsubscribed from market comment emails on Manifold Markets.` - ) - else if (type === 'market-answer') - res.send( - `${name}, you have been unsubscribed from market answer emails on Manifold Markets.` - ) - else res.send(`${name}, you have been unsubscribed.`) -}) + if (type === 'market-resolve') + res.send( + `${name}, you have been unsubscribed from market resolution emails on Manifold Markets.` + ) + else if (type === 'market-comment') + res.send( + `${name}, you have been unsubscribed from market comment emails on Manifold Markets.` + ) + else if (type === 'market-answer') + res.send( + `${name}, you have been unsubscribed from market answer emails on Manifold Markets.` + ) + else res.send(`${name}, you have been unsubscribed.`) + }, +} const firestore = admin.firestore() diff --git a/package.json b/package.json index e4aee3fd..77420607 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,13 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "5.25.0", "@typescript-eslint/parser": "5.25.0", + "concurrently": "6.5.1", "eslint": "8.15.0", "eslint-plugin-lodash": "^7.4.0", "prettier": "2.5.0", - "typescript": "4.6.4" + "typescript": "4.6.4", + "ts-node": "10.9.1", + "nodemon": "2.0.19" }, "resolutions": { "@types/react": "17.0.43" diff --git a/web/package.json b/web/package.json index 9f27643e..a31dbffa 100644 --- a/web/package.json +++ b/web/package.json @@ -3,12 +3,14 @@ "version": "1.0.0", "private": true, "scripts": { - "dev": "concurrently -n NEXT,TS -c magenta,cyan \"next dev -p 3000\" \"yarn ts --watch\"", - "devdev": "cross-env NEXT_PUBLIC_FIREBASE_ENV=DEV concurrently -n NEXT,TS -c magenta,cyan \"cross-env FIREBASE_ENV=DEV next dev -p 3000\" \"cross-env FIREBASE_ENV=DEV yarn ts --watch\"", + "serve": "next dev -p 3000", + "ts-watch": "tsc --watch --noEmit --incremental --preserveWatchOutput --pretty", + "dev": "concurrently -n NEXT,TS -c magenta,cyan \"yarn serve\" \"yarn ts-watch\"", + "devdev": "cross-env NEXT_PUBLIC_FIREBASE_ENV=DEV yarn dev", "dev:dev": "yarn devdev", - "dev:the": "cross-env NEXT_PUBLIC_FIREBASE_ENV=THEOREMONE concurrently -n NEXT,TS -c magenta,cyan \"cross-env FIREBASE_ENV=THEOREMONE next dev -p 3000\" \"cross-env FIREBASE_ENV=THEOREMONE yarn ts --watch\"", + "dev:the": "cross-env NEXT_PUBLIC_FIREBASE_ENV=THEOREMONE yarn dev", + "dev:local": "cross-env NEXT_PUBLIC_FUNCTIONS_URL=http://localhost:8080 yarn devdev", "dev:emulate": "cross-env NEXT_PUBLIC_FIREBASE_EMULATE=TRUE yarn devdev", - "ts": "tsc --noEmit --incremental --preserveWatchOutput --pretty", "build": "next build", "start": "next start", "lint": "next lint", @@ -62,7 +64,6 @@ "@types/react": "17.0.43", "@types/string-similarity": "^4.0.0", "autoprefixer": "10.2.6", - "concurrently": "6.5.1", "critters": "0.0.16", "cross-env": "^7.0.3", "eslint-config-next": "12.1.6", diff --git a/yarn.lock b/yarn.lock index 019a3dd4..dea9e29b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1353,6 +1353,13 @@ resolved "https://registry.yarnpkg.com/@corex/deepmerge/-/deepmerge-2.6.148.tgz#8fa825d53ffd1cbcafce1b6a830eefd3dcc09dd5" integrity sha512-6QMz0/2h5C3ua51iAnXMPWFbb1QOU1UvSM4bKBw5mzdT+WtLgjbETBBIQZ+Sh9WvEcGwlAt/DEdRpIC3XlDBMA== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@docsearch/css@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.1.0.tgz#6781cad43fc2e034d012ee44beddf8f93ba21f19" @@ -2337,6 +2344,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.13" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" @@ -3106,6 +3121,26 @@ resolved "https://registry.yarnpkg.com/@tsconfig/docusaurus/-/docusaurus-1.0.5.tgz#5298c5b0333c6263f06c3149b38ebccc9f169a4e" integrity sha512-KM/TuJa9fugo67dTGx+ktIqf3fVc077J6jwHu845Hex4EQf7LABlNonP/mohDKT0cmncdtlYVHHF74xR/YpThg== +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + "@types/body-parser@*": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" @@ -3670,7 +3705,7 @@ acorn-walk@^7.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn-walk@^8.0.0: +acorn-walk@^8.0.0, acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== @@ -3854,6 +3889,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + arg@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" @@ -4410,7 +4450,7 @@ cheerio@^1.0.0-rc.10: parse5-htmlparser2-tree-adapter "^7.0.0" tslib "^2.4.0" -chokidar@^3.4.2, chokidar@^3.5.3: +chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -4772,6 +4812,11 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + critters@0.0.16: version "0.0.16" resolved "https://registry.yarnpkg.com/critters/-/critters-0.0.16.tgz#ffa2c5561a65b43c53b940036237ce72dcebfe93" @@ -5274,6 +5319,11 @@ didyoumean@^1.2.2: resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5927,7 +5977,7 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -express@^4.16.4, express@^4.17.1, express@^4.17.3: +express@4.18.1, express@^4.16.4, express@^4.17.1, express@^4.17.3: version "4.18.1" resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== @@ -7059,6 +7109,11 @@ idb@3.0.2: resolved "https://registry.yarnpkg.com/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384" integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw== +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + ignore@^5.1.9, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -8083,6 +8138,11 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: dependencies: semver "^6.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + markdown-escapes@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" @@ -8434,7 +8494,23 @@ node-releases@^2.0.3: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476" integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ== -nopt@1.0.10: +nodemon@2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd" + integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@1.0.10, nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= @@ -9552,6 +9628,11 @@ pseudomap@^1.0.1: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -10422,12 +10503,12 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.0.0: +semver@7.0.0, semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== @@ -10574,6 +10655,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-update-notifier@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" + integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== + dependencies: + semver "~7.0.0" + sirv@^1.0.7: version "1.0.19" resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" @@ -10937,7 +11025,7 @@ stylehacks@^5.1.0: browserslist "^4.16.6" postcss-selector-parser "^6.0.4" -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -11117,6 +11205,13 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -11142,6 +11237,25 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +ts-node@10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tsc-files@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tsc-files/-/tsc-files-1.1.3.tgz#ef4cfcb7affc9b90577d707a879dc53bb105be83" @@ -11253,6 +11367,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" @@ -11511,6 +11630,11 @@ uuid@^8.0.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -11930,6 +12054,11 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 312b244e2a254c7bf5bb9f89aab3df1f2e2a917f Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Sun, 24 Jul 2022 00:45:45 -0700 Subject: [PATCH 07/91] Small backend cleanups (#643) * Reuse DAY_MS in update-metrics job * More concise transaction in cancelbet * Remove some meaningless awaits * Do less work in onCreateLiquidityProvision * Do less work in onCreateAnswer --- functions/src/cancel-bet.ts | 4 +--- functions/src/create-contract.ts | 2 +- functions/src/on-create-answer.ts | 8 ++++---- functions/src/on-create-liquidity-provision.ts | 8 ++++---- functions/src/on-update-user.ts | 4 ++-- functions/src/update-metrics.ts | 6 ++---- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/functions/src/cancel-bet.ts b/functions/src/cancel-bet.ts index d29a6cee..0b7a42aa 100644 --- a/functions/src/cancel-bet.ts +++ b/functions/src/cancel-bet.ts @@ -10,7 +10,7 @@ const bodySchema = z.object({ export const cancelbet = newEndpoint({}, async (req, auth) => { const { betId } = validate(bodySchema, req.body) - const result = await firestore.runTransaction(async (trans) => { + return await firestore.runTransaction(async (trans) => { const snap = await trans.get( firestore.collectionGroup('bets').where('id', '==', betId) ) @@ -28,8 +28,6 @@ export const cancelbet = newEndpoint({}, async (req, auth) => { return { ...bet, isCancelled: true } }) - - return result }) const firestore = admin.firestore() diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index c8cfc7c4..d58141a5 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -120,7 +120,7 @@ export const createmarket = newEndpoint({}, async (req, auth) => { let group = null if (groupId) { - const groupDocRef = await firestore.collection('groups').doc(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.') diff --git a/functions/src/on-create-answer.ts b/functions/src/on-create-answer.ts index af4690b0..6af5e699 100644 --- a/functions/src/on-create-answer.ts +++ b/functions/src/on-create-answer.ts @@ -10,14 +10,14 @@ export const onCreateAnswer = functions.firestore contractId: string } const { eventId } = context - const contract = await getContract(contractId) - if (!contract) - throw new Error('Could not find contract corresponding with answer') - const answer = change.data() as Answer // Ignore ante answer. if (answer.number === 0) return + const contract = await getContract(contractId) + if (!contract) + throw new Error('Could not find contract corresponding with answer') + const answerCreator = await getUser(answer.userId) if (!answerCreator) throw new Error('Could not find answer creator') diff --git a/functions/src/on-create-liquidity-provision.ts b/functions/src/on-create-liquidity-provision.ts index ba17f3e7..6ec092a5 100644 --- a/functions/src/on-create-liquidity-provision.ts +++ b/functions/src/on-create-liquidity-provision.ts @@ -8,14 +8,14 @@ export const onCreateLiquidityProvision = functions.firestore .onCreate(async (change, context) => { const liquidity = change.data() as LiquidityProvision const { eventId } = context - const contract = await getContract(liquidity.contractId) - - if (!contract) - throw new Error('Could not find contract corresponding with liquidity') // Ignore Manifold Markets liquidity for now - users see a notification for free market liquidity provision if (liquidity.userId === 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2') return + const contract = await getContract(liquidity.contractId) + if (!contract) + throw new Error('Could not find contract corresponding with liquidity') + const liquidityProvider = await getUser(liquidity.userId) if (!liquidityProvider) throw new Error('Could not find liquidity provider') diff --git a/functions/src/on-update-user.ts b/functions/src/on-update-user.ts index f5558730..a76132b5 100644 --- a/functions/src/on-update-user.ts +++ b/functions/src/on-update-user.ts @@ -103,8 +103,8 @@ async function handleUserUpdatedReferral(user: User, eventId: string) { description: `Referred new user id: ${user.id} for ${REFERRAL_AMOUNT}`, } - const txnDoc = await firestore.collection(`txns/`).doc(txn.id) - await transaction.set(txnDoc, txn) + const txnDoc = firestore.collection(`txns/`).doc(txn.id) + transaction.set(txnDoc, txn) console.log('created referral with txn id:', txn.id) // We're currently not subtracting M$ from the house, not sure if we want to for accounting purposes. transaction.update(referredByUserDoc, { diff --git a/functions/src/update-metrics.ts b/functions/src/update-metrics.ts index 76570f54..cc9f8ebe 100644 --- a/functions/src/update-metrics.ts +++ b/functions/src/update-metrics.ts @@ -11,8 +11,6 @@ import { last } from 'lodash' const firestore = admin.firestore() -const oneDay = 1000 * 60 * 60 * 24 - const computeInvestmentValue = ( bets: Bet[], contractsDict: { [k: string]: Contract } @@ -59,8 +57,8 @@ export const updateMetricsCore = async () => { return { doc: firestore.collection('contracts').doc(contract.id), fields: { - volume24Hours: computeVolume(contractBets, now - oneDay), - volume7Days: computeVolume(contractBets, now - oneDay * 7), + volume24Hours: computeVolume(contractBets, now - DAY_MS), + volume7Days: computeVolume(contractBets, now - DAY_MS * 7), }, } }) From 9840742927f861ad2f92156eda34338003b6fa05 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Sun, 24 Jul 2022 02:30:28 -0700 Subject: [PATCH 08/91] Fix overaggressive emulator running in dev.sh --- dev.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dev.sh b/dev.sh index 178e20e8..ca3246ac 100755 --- a/dev.sh +++ b/dev.sh @@ -4,12 +4,10 @@ ENV=${1:-dev} case $ENV in dev) FIREBASE_PROJECT=dev - NEXT_ENV=DEV - EMULATOR=false ;; + NEXT_ENV=DEV ;; prod) FIREBASE_PROJECT=prod - NEXT_ENV=PROD - EMULATOR=false ;; + NEXT_ENV=PROD ;; localdb) FIREBASE_PROJECT=dev NEXT_ENV=DEV From e389f4cc3be1e293d51d62a023c23fd529194941 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Sun, 24 Jul 2022 22:50:33 -0700 Subject: [PATCH 09/91] referrals text --- web/pages/referrals.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/pages/referrals.tsx b/web/pages/referrals.tsx index f50c2e2b..b2666309 100644 --- a/web/pages/referrals.tsx +++ b/web/pages/referrals.tsx @@ -53,7 +53,7 @@ export default function ReferralsPage() { From df91310d0fc23f2d7d2c2a03231da6618a091a9e Mon Sep 17 00:00:00 2001 From: mantikoros Date: Sun, 24 Jul 2022 23:28:05 -0700 Subject: [PATCH 10/91] PlayMoneyDisclaimer; hide limit orders for signed out users; infobox styling --- web/components/bet-panel.tsx | 62 ++++++++++++++++++++++++------------ web/components/info-box.tsx | 2 +- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index c638fcde..7a918948 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -42,6 +42,7 @@ import { useUnfilledBets } from 'web/hooks/use-bets' import { LimitBets } from './limit-bets' import { PillButton } from './buttons/pill-button' import { YesNoSelector } from './yes-no-selector' +import { InfoBox } from './info-box' export function BetPanel(props: { contract: CPMMBinaryContract | PseudoNumericContract @@ -72,6 +73,7 @@ export function BetPanel(props: {