From 7c31f9b4174790906fd7f753c8a3d61f00d0fd3a Mon Sep 17 00:00:00 2001 From: Aslam H Date: Tue, 8 Oct 2024 13:55:36 +0700 Subject: [PATCH] fix: authed --- web/app/actions.ts | 106 +----------------- .../components/sidebar/partials/feedback.tsx | 23 +++- web/app/routes/__root.tsx | 19 +++- .../_pages/_protected/links/-link-form.tsx | 83 +++++++++++++- 4 files changed, 120 insertions(+), 111 deletions(-) diff --git a/web/app/actions.ts b/web/app/actions.ts index 8f3fff17..a2d7926a 100644 --- a/web/app/actions.ts +++ b/web/app/actions.ts @@ -1,28 +1,5 @@ -import { clerkClient, getAuth } from "@clerk/tanstack-start/server" import { createServerFn } from "@tanstack/start" -import { create, get } from "ronin" -import * as cheerio from "cheerio" -import { ensureUrlProtocol } from "@/lib/utils" -import { urlSchema } from "@/lib/utils/schema" - -interface Metadata { - title: string - description: string - icon: string | null - url: string -} - -const DEFAULT_VALUES = { - TITLE: "", - DESCRIPTION: "", - FAVICON: null, -} - -export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => { - const auth = await getAuth(ctx.request) - - return auth -}) +import { get } from "ronin" export const getFeatureFlag = createServerFn( "GET", @@ -34,84 +11,3 @@ export const getFeatureFlag = createServerFn( return response }, ) - -export const sendFeedbackFn = createServerFn( - "POST", - async (data: { content: string }, { request }) => { - const auth = await getAuth(request) - if (!auth.userId) { - throw new Error("Unauthorized") - } - const user = await clerkClient({ - telemetry: { disabled: true }, - }).users.getUser(auth.userId) - await create.feedback.with({ - message: data.content, - emailFrom: user.emailAddresses[0].emailAddress, - }) - }, -) - -export const getMetadata = createServerFn("GET", async (url: string) => { - if (!url) { - return new Response('Missing "url" query parameter', { - status: 400, - }) - } - - const result = urlSchema.safeParse(url) - if (!result.success) { - throw new Error( - result.error.issues.map((issue) => issue.message).join(", "), - ) - } - - url = ensureUrlProtocol(url) - - try { - const response = await fetch(url, { - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", - }, - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.text() - const $ = cheerio.load(data) - - const metadata: Metadata = { - title: - $("title").text() || - $('meta[property="og:title"]').attr("content") || - DEFAULT_VALUES.TITLE, - description: - $('meta[name="description"]').attr("content") || - $('meta[property="og:description"]').attr("content") || - DEFAULT_VALUES.DESCRIPTION, - icon: - $('link[rel="icon"]').attr("href") || - $('link[rel="shortcut icon"]').attr("href") || - DEFAULT_VALUES.FAVICON, - url: url, - } - - if (metadata.icon && !metadata.icon.startsWith("http")) { - metadata.icon = new URL(metadata.icon, url).toString() - } - - return metadata - } catch (error) { - console.error("Error fetching metadata:", error) - const defaultMetadata: Metadata = { - title: DEFAULT_VALUES.TITLE, - description: DEFAULT_VALUES.DESCRIPTION, - icon: DEFAULT_VALUES.FAVICON, - url: url, - } - return defaultMetadata - } -}) diff --git a/web/app/components/sidebar/partials/feedback.tsx b/web/app/components/sidebar/partials/feedback.tsx index ebdb093e..ec2db9c9 100644 --- a/web/app/components/sidebar/partials/feedback.tsx +++ b/web/app/components/sidebar/partials/feedback.tsx @@ -29,7 +29,28 @@ import { zodResolver } from "@hookform/resolvers/zod" import { toast } from "sonner" import { Spinner } from "@/components/custom/spinner" import { Editor } from "@tiptap/react" -import { sendFeedbackFn } from "~/actions" +import { createServerFn } from "@tanstack/start" +import { clerkClient, getAuth } from "@clerk/tanstack-start/server" +import { create } from "ronin" + +export const sendFeedbackFn = createServerFn( + "POST", + async (data: { content: string }, { request }) => { + const auth = await getAuth(request) + + if (!auth.userId) { + throw new Error("User not authenticated") + } + + const user = await clerkClient({ + telemetry: { disabled: true }, + }).users.getUser(auth.userId) + await create.feedback.with({ + message: data.content, + emailFrom: user.emailAddresses[0].emailAddress, + }) + }, +) const formSchema = z.object({ content: z.string().min(1, { diff --git a/web/app/routes/__root.tsx b/web/app/routes/__root.tsx index 110e6aa2..65f27489 100644 --- a/web/app/routes/__root.tsx +++ b/web/app/routes/__root.tsx @@ -1,13 +1,20 @@ /// +import { getAuth } from "@clerk/tanstack-start/server" import type { QueryClient } from "@tanstack/react-query" import { Outlet, ScrollRestoration, createRootRouteWithContext, } from "@tanstack/react-router" -import { Body, Head, Html, Meta, Scripts } from "@tanstack/start" +import { + Body, + createServerFn, + Head, + Html, + Meta, + Scripts, +} from "@tanstack/start" import * as React from "react" -import { fetchClerkAuth } from "~/actions" import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary.js" import { NotFound } from "~/components/NotFound.js" import appCss from "~/styles/app.css?url" @@ -25,11 +32,17 @@ export const ReactQueryDevtools = process.env.NODE_ENV === "production" ? () => null : React.lazy(() => - import("@tanstack/react-query-devtools/production").then((d) => ({ + import("@tanstack/react-query-devtools").then((d) => ({ default: d.ReactQueryDevtools, })), ) +export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => { + const auth = await getAuth(ctx.request) + + return auth +}) + export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({ diff --git a/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx b/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx index 4deedd30..5e54be51 100644 --- a/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx +++ b/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx @@ -5,7 +5,7 @@ import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { toast } from "sonner" import { createLinkSchema, LinkFormValues } from "./-schema" -import { cn, generateUniqueSlug } from "@/lib/utils" +import { cn, ensureUrlProtocol, generateUniqueSlug } from "@/lib/utils" import { Form } from "@/components/ui/form" import { Button } from "@/components/ui/button" import { atom, useAtom } from "jotai" @@ -22,12 +22,91 @@ import { useOnClickOutside } from "~/hooks/use-on-click-outside" import TopicSelector, { topicSelectorAtom, } from "~/components/custom/topic-selector" -import { getMetadata } from "~/actions" +import { createServerFn } from "@tanstack/start" +import { urlSchema } from "~/lib/utils/schema" +import * as cheerio from "cheerio" + +interface Metadata { + title: string + description: string + icon: string | null + url: string +} + +const DEFAULT_VALUES = { + TITLE: "", + DESCRIPTION: "", + FAVICON: null, +} export const globalLinkFormExceptionRefsAtom = atom< React.RefObject[] >([]) +export const getMetadata = createServerFn("GET", async (url: string) => { + if (!url) { + return new Response('Missing "url" query parameter', { + status: 400, + }) + } + + const result = urlSchema.safeParse(url) + if (!result.success) { + throw new Error( + result.error.issues.map((issue) => issue.message).join(", "), + ) + } + + url = ensureUrlProtocol(url) + + try { + const response = await fetch(url, { + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + }, + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.text() + const $ = cheerio.load(data) + + const metadata: Metadata = { + title: + $("title").text() || + $('meta[property="og:title"]').attr("content") || + DEFAULT_VALUES.TITLE, + description: + $('meta[name="description"]').attr("content") || + $('meta[property="og:description"]').attr("content") || + DEFAULT_VALUES.DESCRIPTION, + icon: + $('link[rel="icon"]').attr("href") || + $('link[rel="shortcut icon"]').attr("href") || + DEFAULT_VALUES.FAVICON, + url: url, + } + + if (metadata.icon && !metadata.icon.startsWith("http")) { + metadata.icon = new URL(metadata.icon, url).toString() + } + + return metadata + } catch (error) { + console.error("Error fetching metadata:", error) + const defaultMetadata: Metadata = { + title: DEFAULT_VALUES.TITLE, + description: DEFAULT_VALUES.DESCRIPTION, + icon: DEFAULT_VALUES.FAVICON, + url: url, + } + return defaultMetadata + } +}) + interface LinkFormProps extends React.ComponentPropsWithoutRef<"form"> { onClose?: () => void onSuccess?: () => void