diff --git a/common/dashboard.ts b/common/post.ts similarity index 70% rename from common/dashboard.ts rename to common/post.ts index 6ccd15d4..d2a4e539 100644 --- a/common/dashboard.ts +++ b/common/post.ts @@ -1,6 +1,6 @@ import { JSONContent } from '@tiptap/core' -export type Dashboard = { +export type Post = { id: string name: string content: JSONContent @@ -9,4 +9,4 @@ export type Dashboard = { slug: string } -export const MAX_DASHBOARD_NAME_LENGTH = 480 +export const MAX_POST_NAME_LENGTH = 480 diff --git a/firestore.rules b/firestore.rules index 23bf19d1..a4f8a1e3 100644 --- a/firestore.rules +++ b/firestore.rules @@ -170,7 +170,7 @@ service cloud.firestore { } } - match /dashboards/{dashboardId} { + match /posts/{postId} { allow read; allow update: if request.auth.uid == resource.data.creatorId && request.resource.data.diff(resource.data) diff --git a/functions/src/create-dashboard.ts b/functions/src/create-post.ts similarity index 60% rename from functions/src/create-dashboard.ts rename to functions/src/create-post.ts index 34c066bc..b15348c7 100644 --- a/functions/src/create-dashboard.ts +++ b/functions/src/create-post.ts @@ -3,7 +3,7 @@ import * as admin from 'firebase-admin' import { getUser } from './utils' import { slugify } from '../../common/util/slugify' import { randomString } from '../../common/util/random' -import { Dashboard, MAX_DASHBOARD_NAME_LENGTH } from '../../common/dashboard' +import { Post, MAX_POST_NAME_LENGTH } from '../../common/post' import { APIError, newEndpoint, validate } from './api' import { JSONContent } from '@tiptap/core' import { z } from 'zod' @@ -31,27 +31,27 @@ const contentSchema: z.ZodType = z.lazy(() => ) ) -const dashboardSchema = z.object({ - name: z.string().min(1).max(MAX_DASHBOARD_NAME_LENGTH), +const postSchema = z.object({ + name: z.string().min(1).max(MAX_POST_NAME_LENGTH), content: contentSchema, }) -export const createdashboard = newEndpoint({}, async (req, auth) => { +export const createpost = newEndpoint({}, async (req, auth) => { const firestore = admin.firestore() - const { name, content } = validate(dashboardSchema, req.body) + const { name, content } = validate(postSchema, req.body) const creator = await getUser(auth.uid) if (!creator) throw new APIError(400, 'No user exists with the authenticated user ID.') - console.log('creating dashboard owned by', creator.username, 'named', name) + console.log('creating post owned by', creator.username, 'named', name) const slug = await getSlug(name) - const dashboardRef = firestore.collection('dashboards').doc() + const postRef = firestore.collection('posts').doc() - const dashboard: Dashboard = { - id: dashboardRef.id, + const post: Post = { + id: postRef.id, creatorId: creator.id, slug, name, @@ -59,27 +59,25 @@ export const createdashboard = newEndpoint({}, async (req, auth) => { content: content, } - await dashboardRef.create(dashboard) + await postRef.create(post) - return { status: 'success', dashboard: dashboard } + return { status: 'success', post: post } }) export const getSlug = async (name: string) => { const proposedSlug = slugify(name) - const preexistingDashboard = await getDashboardFromSlug(proposedSlug) + const preexistingPost = await getPostFromSlug(proposedSlug) - return preexistingDashboard - ? proposedSlug + '-' + randomString() - : proposedSlug + return preexistingPost ? proposedSlug + '-' + randomString() : proposedSlug } -export async function getDashboardFromSlug(slug: string) { +export async function getPostFromSlug(slug: string) { const firestore = admin.firestore() const snap = await firestore - .collection('dashboards') + .collection('posts') .where('slug', '==', slug) .get() - return snap.empty ? undefined : (snap.docs[0].data() as Dashboard) + return snap.empty ? undefined : (snap.docs[0].data() as Post) } diff --git a/functions/src/index.ts b/functions/src/index.ts index 123d3064..8430f5f6 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -71,7 +71,7 @@ import { stripewebhook, createcheckoutsession } from './stripe' import { getcurrentuser } from './get-current-user' import { acceptchallenge } from './accept-challenge' import { getcustomtoken } from './get-custom-token' -import { createdashboard } from './create-dashboard' +import { createpost } from './create-post' const toCloudFunction = ({ opts, handler }: EndpointDefinition) => { return onRequest(opts, handler as any) @@ -97,7 +97,7 @@ const createCheckoutSessionFunction = toCloudFunction(createcheckoutsession) const getCurrentUserFunction = toCloudFunction(getcurrentuser) const acceptChallenge = toCloudFunction(acceptchallenge) const getCustomTokenFunction = toCloudFunction(getcustomtoken) -const createDashboardFunction = toCloudFunction(createdashboard) +const createPostFunction = toCloudFunction(createpost) export { healthFunction as health, @@ -121,5 +121,5 @@ export { getCurrentUserFunction as getcurrentuser, acceptChallenge as acceptchallenge, getCustomTokenFunction as getcustomtoken, - createDashboardFunction as createdashboard, + createPostFunction as createpost, } diff --git a/functions/src/serve.ts b/functions/src/serve.ts index b78d6307..db847a70 100644 --- a/functions/src/serve.ts +++ b/functions/src/serve.ts @@ -27,7 +27,7 @@ import { unsubscribe } from './unsubscribe' import { stripewebhook, createcheckoutsession } from './stripe' import { getcurrentuser } from './get-current-user' import { getcustomtoken } from './get-custom-token' -import { createdashboard } from './create-dashboard' +import { createpost } from './create-post' type Middleware = (req: Request, res: Response, next: NextFunction) => void const app = express() @@ -68,7 +68,7 @@ addJsonEndpointRoute('/createcheckoutsession', createcheckoutsession) addJsonEndpointRoute('/getcurrentuser', getcurrentuser) addEndpointRoute('/getcustomtoken', getcustomtoken) addEndpointRoute('/stripewebhook', stripewebhook, express.raw()) -addEndpointRoute('/createdashboard', createdashboard) +addEndpointRoute('/createpost', createpost) app.listen(PORT) console.log(`Serving functions on port ${PORT}.`) diff --git a/functions/src/utils.ts b/functions/src/utils.ts index 34e56164..a0878e4f 100644 --- a/functions/src/utils.ts +++ b/functions/src/utils.ts @@ -4,7 +4,7 @@ import { chunk } from 'lodash' import { Contract } from '../../common/contract' import { PrivateUser, User } from '../../common/user' import { Group } from '../../common/group' -import { Dashboard } from 'common/dashboard' +import { Post } from 'common/post' export const log = (...args: unknown[]) => { console.log(`[${new Date().toISOString()}]`, ...args) @@ -81,8 +81,8 @@ export const getGroup = (groupId: string) => { return getDoc('groups', groupId) } -export const getDashboard = (dashboardId: string) => { - return getDoc('dashboards', dashboardId) +export const getPost = (postId: string) => { + return getDoc('posts', postId) } export const getUser = (userId: string) => { diff --git a/web/components/share-dashboard-modal.tsx b/web/components/share-post-modal.tsx similarity index 88% rename from web/components/share-dashboard-modal.tsx rename to web/components/share-post-modal.tsx index ddc64e5c..fa8658c6 100644 --- a/web/components/share-dashboard-modal.tsx +++ b/web/components/share-post-modal.tsx @@ -9,7 +9,7 @@ import { Button } from './button' import { TweetButton } from './tweet-button' import { Row } from './layout/row' -export function ShareDashboardModal(props: { +export function SharePostModal(props: { shareUrl: string isOpen: boolean setOpen: (open: boolean) => void @@ -21,7 +21,7 @@ export function ShareDashboardModal(props: { return ( - + <Title className="!mt-0 !mb-2" text="Share this post" /> <Button size="2xl" color="gradient" @@ -31,7 +31,7 @@ export function ShareDashboardModal(props: { toast.success('Link copied!', { icon: linkIcon, }) - track('copy share dashboard link') + track('copy share post link') }} > {linkIcon} Copy link diff --git a/web/lib/firebase/api.ts b/web/lib/firebase/api.ts index 4e352fa9..0364a7cc 100644 --- a/web/lib/firebase/api.ts +++ b/web/lib/firebase/api.ts @@ -89,6 +89,6 @@ export function getCurrentUser(params: any) { return call(getFunctionUrl('getcurrentuser'), 'GET', params) } -export function createDashboard(params: any) { - return call(getFunctionUrl('createdashboard'), 'POST', params) +export function createPost(params: any) { + return call(getFunctionUrl('createpost'), 'POST', params) } diff --git a/web/lib/firebase/dashboards.ts b/web/lib/firebase/dashboards.ts deleted file mode 100644 index 9f2dd33e..00000000 --- a/web/lib/firebase/dashboards.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - deleteDoc, - doc, - getDocs, - query, - updateDoc, - where, -} from 'firebase/firestore' -import { Dashboard } from 'common/dashboard' -import { coll, getValue } from './utils' - -export const dashboards = coll<Dashboard>('dashboards') - -export function dashboardPath(dashboardSlug: string) { - return `/dashboard/${dashboardSlug}` -} - -export function updateDashboard( - dashboard: Dashboard, - updates: Partial<Dashboard> -) { - return updateDoc(doc(dashboards, dashboard.id), updates) -} - -export function deleteDashboard(dashboard: Dashboard) { - return deleteDoc(doc(dashboards, dashboard.id)) -} - -export function getDashboard(dashboardId: string) { - return getValue<Dashboard>(doc(dashboards, dashboardId)) -} - -export async function getDashboardBySlug(slug: string) { - const q = query(dashboards, where('slug', '==', slug)) - const docs = (await getDocs(q)).docs - return docs.length === 0 ? null : docs[0].data() -} diff --git a/web/lib/firebase/posts.ts b/web/lib/firebase/posts.ts new file mode 100644 index 00000000..10bea499 --- /dev/null +++ b/web/lib/firebase/posts.ts @@ -0,0 +1,34 @@ +import { + deleteDoc, + doc, + getDocs, + query, + updateDoc, + where, +} from 'firebase/firestore' +import { Post } from 'common/post' +import { coll, getValue } from './utils' + +export const posts = coll<Post>('posts') + +export function postPath(postSlug: string) { + return `/post/${postSlug}` +} + +export function updatePost(post: Post, updates: Partial<Post>) { + return updateDoc(doc(posts, post.id), updates) +} + +export function deletePost(post: Post) { + return deleteDoc(doc(posts, post.id)) +} + +export function getPost(postId: string) { + return getValue<Post>(doc(posts, postId)) +} + +export async function getPostBySlug(slug: string) { + const q = query(posts, where('slug', '==', slug)) + const docs = (await getDocs(q)).docs + return docs.length === 0 ? null : docs[0].data() +} diff --git a/web/pages/create-dashboard.tsx b/web/pages/create-post.tsx similarity index 73% rename from web/pages/create-dashboard.tsx rename to web/pages/create-post.tsx index 13849745..0b7e6eb1 100644 --- a/web/pages/create-dashboard.tsx +++ b/web/pages/create-post.tsx @@ -5,13 +5,13 @@ import { Title } from 'web/components/title' import Textarea from 'react-expanding-textarea' import { TextEditor, useTextEditor } from 'web/components/editor' -import { createDashboard } from 'web/lib/firebase/api' +import { createPost } from 'web/lib/firebase/api' import clsx from 'clsx' import { useRouter } from 'next/router' -import { Dashboard, MAX_DASHBOARD_NAME_LENGTH } from 'common/dashboard' -import { dashboardPath } from 'web/lib/firebase/dashboards' +import { Post, MAX_POST_NAME_LENGTH } from 'common/post' +import { postPath } from 'web/lib/firebase/posts' -export default function CreateDashboard() { +export default function CreatePost() { const [name, setName] = useState('') const [error, setError] = useState('') const [isSubmitting, setIsSubmitting] = useState(false) @@ -23,20 +23,20 @@ export default function CreateDashboard() { const isValid = editor && name.length > 0 && editor.isEmpty === false - async function saveDashboard(name: string) { + async function savePost(name: string) { if (!editor) return - const newDashboard = { + const newPost = { name: name, content: editor.getJSON(), } - const result = await createDashboard(newDashboard).catch((e) => { + const result = await createPost(newPost).catch((e) => { console.log(e) - setError('There was an error creating the dashboard, please try again') + setError('There was an error creating the post, please try again') return e }) - if (result.dashboard) { - await router.push(dashboardPath((result.dashboard as Dashboard).slug)) + if (result.post) { + await router.push(postPath((result.post as Post).slug)) } } @@ -44,7 +44,7 @@ export default function CreateDashboard() { <Page> <div className="mx-auto w-full max-w-2xl"> <div className="rounded-lg px-6 py-4 sm:py-0"> - <Title className="!mt-0" text="Create a dashboard" /> + <Title className="!mt-0" text="Create a post" /> <form> <div className="form-control w-full"> <label className="label"> @@ -53,10 +53,10 @@ export default function CreateDashboard() { </span> </label> <Textarea - placeholder="e.g. Elon Mania Dashboard" + placeholder="e.g. Elon Mania Post" className="input input-bordered resize-none" autoFocus - maxLength={MAX_DASHBOARD_NAME_LENGTH} + maxLength={MAX_POST_NAME_LENGTH} value={name} onChange={(e) => setName(e.target.value || '')} /> @@ -78,11 +78,11 @@ export default function CreateDashboard() { disabled={isSubmitting || !isValid || upload.isLoading} onClick={async () => { setIsSubmitting(true) - await saveDashboard(name) + await savePost(name) setIsSubmitting(false) }} > - {isSubmitting ? 'Creating...' : 'Create a dashboard'} + {isSubmitting ? 'Creating...' : 'Create a post'} </button> {error !== '' && <div className="text-red-700">{error}</div>} </div> diff --git a/web/pages/dashboard/[...slugs]/index.tsx b/web/pages/post/[...slugs]/index.tsx similarity index 76% rename from web/pages/dashboard/[...slugs]/index.tsx rename to web/pages/post/[...slugs]/index.tsx index 8ff78b67..e29244c0 100644 --- a/web/pages/dashboard/[...slugs]/index.tsx +++ b/web/pages/post/[...slugs]/index.tsx @@ -1,8 +1,8 @@ import { Page } from 'web/components/page' import { fromPropz, usePropz } from 'web/hooks/use-propz' -import { dashboardPath, getDashboardBySlug } from 'web/lib/firebase/dashboards' -import { Dashboard } from 'common/dashboard' +import { postPath, getPostBySlug } from 'web/lib/firebase/posts' +import { Post } from 'common/post' import { Title } from 'web/components/title' import { Spacer } from 'web/components/layout/spacer' import { Content } from 'web/components/editor' @@ -12,7 +12,7 @@ import { ShareIcon } from '@heroicons/react/solid' import clsx from 'clsx' import { Button } from 'web/components/button' import { useState } from 'react' -import { ShareDashboardModal } from 'web/components/share-dashboard-modal' +import { SharePostModal } from 'web/components/share-post-modal' import { Row } from 'web/components/layout/row' import { Col } from 'web/components/layout/col' import { ENV_CONFIG } from 'common/envs/constants' @@ -22,13 +22,13 @@ export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { params: { slugs: string[] } }) { const { slugs } = props.params - const dashboard = await getDashboardBySlug(slugs[0]) - const creatorPromise = dashboard ? getUser(dashboard.creatorId) : null + const post = await getPostBySlug(slugs[0]) + const creatorPromise = post ? getUser(post.creatorId) : null const creator = await creatorPromise return { props: { - dashboard: dashboard, + post: post, creator: creator, }, @@ -40,28 +40,23 @@ export async function getStaticPaths() { return { paths: [], fallback: 'blocking' } } -export default function DashboardPage(props: { - dashboard: Dashboard - creator: User -}) { +export default function PostPage(props: { post: Post; creator: User }) { props = usePropz(props, getStaticPropz) ?? { - dashboard: null, + post: null, } const [isShareOpen, setShareOpen] = useState(false) - if (props.dashboard === null) { + if (props.post === null) { return <Custom404 /> } - const shareUrl = `https://${ENV_CONFIG.domain}${dashboardPath( - props?.dashboard.slug - )}` + const shareUrl = `https://${ENV_CONFIG.domain}${postPath(props?.post.slug)}` return ( <Page> <div className="mx-auto w-full max-w-3xl "> <Spacer h={1} /> - <Title className="!mt-0" text={props.dashboard.name} /> + <Title className="!mt-0" text={props.post.name} /> <Row> <Col className="flex-1"> <div className={'inline-flex'}> @@ -87,7 +82,7 @@ export default function DashboardPage(props: { aria-hidden="true" /> Share - <ShareDashboardModal + <SharePostModal isOpen={isShareOpen} setOpen={setShareOpen} shareUrl={shareUrl} @@ -99,7 +94,7 @@ export default function DashboardPage(props: { <Spacer h={2} /> <div className="rounded-lg bg-white px-6 py-4 sm:py-0"> <div className="form-control w-full py-2"> - <Content content={props.dashboard.content} /> + <Content content={props.post.content} /> </div> </div> </div>