mirror of
https://github.com/linsa-io/linsa.git
synced 2026-04-25 01:38:35 +02:00
Move to TanStack Start from Next.js (#184)
This commit is contained in:
@@ -1,91 +1,140 @@
|
||||
"use server"
|
||||
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"
|
||||
|
||||
import { authedProcedure } from "@/lib/utils/auth-procedure"
|
||||
import { currentUser } from "@clerk/nextjs/server"
|
||||
import { get } from "ronin"
|
||||
import { create } from "ronin"
|
||||
import { z } from "zod"
|
||||
import { ZSAError } from "zsa"
|
||||
|
||||
const MAX_FILE_SIZE = 1 * 1024 * 1024
|
||||
const ALLOWED_FILE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp"]
|
||||
|
||||
export const getFeatureFlag = authedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string()
|
||||
})
|
||||
)
|
||||
.handler(async ({ input }) => {
|
||||
const { name } = input
|
||||
const flag = await get.featureFlag.with.name(name)
|
||||
|
||||
return { flag }
|
||||
})
|
||||
|
||||
export const sendFeedback = authedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
content: z.string()
|
||||
})
|
||||
)
|
||||
.handler(async ({ input, ctx }) => {
|
||||
const { clerkUser } = ctx
|
||||
const { content } = input
|
||||
|
||||
try {
|
||||
await create.feedback.with({
|
||||
message: content,
|
||||
emailFrom: clerkUser?.emailAddresses[0].emailAddress
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new ZSAError("ERROR", "Failed to send feedback")
|
||||
}
|
||||
})
|
||||
|
||||
export const storeImage = authedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
file: z
|
||||
.any()
|
||||
.refine(file => file instanceof File, {
|
||||
message: "Not a file"
|
||||
})
|
||||
.refine(file => ALLOWED_FILE_TYPES.includes(file.type), {
|
||||
message: "Invalid file type. Only JPEG, PNG, GIF, and WebP images are allowed."
|
||||
})
|
||||
.refine(file => file.size <= MAX_FILE_SIZE, {
|
||||
message: "File size exceeds the maximum limit of 1 MB."
|
||||
})
|
||||
}),
|
||||
{ type: "formData" }
|
||||
)
|
||||
.handler(async ({ ctx, input }) => {
|
||||
const { file } = input
|
||||
const { clerkUser } = ctx
|
||||
|
||||
if (!clerkUser?.id) {
|
||||
throw new ZSAError("NOT_AUTHORIZED", "You are not authorized to upload files")
|
||||
}
|
||||
|
||||
try {
|
||||
const fileModel = await create.image.with({
|
||||
content: file,
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size
|
||||
})
|
||||
|
||||
return { fileModel }
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new ZSAError("ERROR", "Failed to store image")
|
||||
}
|
||||
})
|
||||
|
||||
export const isExistingUser = async () => {
|
||||
const clerkUser = await currentUser()
|
||||
const roninUser = await get.existingStripeSubscriber.with({ email: clerkUser?.emailAddresses[0].emailAddress })
|
||||
return clerkUser?.emailAddresses[0].emailAddress === roninUser?.email
|
||||
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 {
|
||||
user: auth,
|
||||
}
|
||||
})
|
||||
|
||||
export const getFeatureFlag = createServerFn(
|
||||
"GET",
|
||||
async (data: { name: string }) => {
|
||||
const response = await get.featureFlag.with({
|
||||
name: data.name,
|
||||
})
|
||||
|
||||
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 isExistingUserFn = createServerFn(
|
||||
"GET",
|
||||
async (_, { request }) => {
|
||||
const auth = await getAuth(request)
|
||||
|
||||
if (!auth.userId) {
|
||||
return false
|
||||
}
|
||||
|
||||
const user = await clerkClient({
|
||||
telemetry: { disabled: true },
|
||||
}).users.getUser(auth.userId)
|
||||
|
||||
const roninUser = await get.existingStripeSubscriber.with({
|
||||
email: user.emailAddresses[0].emailAddress,
|
||||
})
|
||||
|
||||
return user.emailAddresses[0].emailAddress === roninUser?.email
|
||||
},
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user