mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
fix: authed
This commit is contained in:
@@ -1,28 +1,5 @@
|
|||||||
import { clerkClient, getAuth } from "@clerk/tanstack-start/server"
|
|
||||||
import { createServerFn } from "@tanstack/start"
|
import { createServerFn } from "@tanstack/start"
|
||||||
import { create, get } from "ronin"
|
import { 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
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getFeatureFlag = createServerFn(
|
export const getFeatureFlag = createServerFn(
|
||||||
"GET",
|
"GET",
|
||||||
@@ -34,84 +11,3 @@ export const getFeatureFlag = createServerFn(
|
|||||||
return response
|
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -29,7 +29,28 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
|||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { Spinner } from "@/components/custom/spinner"
|
import { Spinner } from "@/components/custom/spinner"
|
||||||
import { Editor } from "@tiptap/react"
|
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({
|
const formSchema = z.object({
|
||||||
content: z.string().min(1, {
|
content: z.string().min(1, {
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
import { getAuth } from "@clerk/tanstack-start/server"
|
||||||
import type { QueryClient } from "@tanstack/react-query"
|
import type { QueryClient } from "@tanstack/react-query"
|
||||||
import {
|
import {
|
||||||
Outlet,
|
Outlet,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
createRootRouteWithContext,
|
createRootRouteWithContext,
|
||||||
} from "@tanstack/react-router"
|
} 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 * as React from "react"
|
||||||
import { fetchClerkAuth } from "~/actions"
|
|
||||||
import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary.js"
|
import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary.js"
|
||||||
import { NotFound } from "~/components/NotFound.js"
|
import { NotFound } from "~/components/NotFound.js"
|
||||||
import appCss from "~/styles/app.css?url"
|
import appCss from "~/styles/app.css?url"
|
||||||
@@ -25,11 +32,17 @@ export const ReactQueryDevtools =
|
|||||||
process.env.NODE_ENV === "production"
|
process.env.NODE_ENV === "production"
|
||||||
? () => null
|
? () => null
|
||||||
: React.lazy(() =>
|
: React.lazy(() =>
|
||||||
import("@tanstack/react-query-devtools/production").then((d) => ({
|
import("@tanstack/react-query-devtools").then((d) => ({
|
||||||
default: d.ReactQueryDevtools,
|
default: d.ReactQueryDevtools,
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => {
|
||||||
|
const auth = await getAuth(ctx.request)
|
||||||
|
|
||||||
|
return auth
|
||||||
|
})
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<{
|
export const Route = createRootRouteWithContext<{
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
}>()({
|
}>()({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useForm } from "react-hook-form"
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { createLinkSchema, LinkFormValues } from "./-schema"
|
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 { Form } from "@/components/ui/form"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { atom, useAtom } from "jotai"
|
import { atom, useAtom } from "jotai"
|
||||||
@@ -22,12 +22,91 @@ import { useOnClickOutside } from "~/hooks/use-on-click-outside"
|
|||||||
import TopicSelector, {
|
import TopicSelector, {
|
||||||
topicSelectorAtom,
|
topicSelectorAtom,
|
||||||
} from "~/components/custom/topic-selector"
|
} 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<
|
export const globalLinkFormExceptionRefsAtom = atom<
|
||||||
React.RefObject<HTMLElement>[]
|
React.RefObject<HTMLElement>[]
|
||||||
>([])
|
>([])
|
||||||
|
|
||||||
|
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"> {
|
interface LinkFormProps extends React.ComponentPropsWithoutRef<"form"> {
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
onSuccess?: () => void
|
onSuccess?: () => void
|
||||||
|
|||||||
Reference in New Issue
Block a user