mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
Implement setup task for worker admin environment and add new database schema and snapshot files
This commit is contained in:
105
packages/web/src/routes/api/stripe/billing.ts
Normal file
105
packages/web/src/routes/api/stripe/billing.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { getAuth } from "@/lib/auth"
|
||||
import { db } from "@/db/connection"
|
||||
import { stripe_subscriptions, storage_usage } from "@/db/schema"
|
||||
import { eq, and, gte, lte } from "drizzle-orm"
|
||||
|
||||
const json = (data: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(data), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
})
|
||||
|
||||
export const Route = createFileRoute("/api/stripe/billing")({
|
||||
server: {
|
||||
handlers: {
|
||||
GET: async ({ request }) => {
|
||||
const session = await getAuth().api.getSession({
|
||||
headers: request.headers,
|
||||
})
|
||||
|
||||
// Guest user
|
||||
if (!session?.user?.id) {
|
||||
return json({
|
||||
isGuest: true,
|
||||
isPaid: false,
|
||||
planName: "Guest",
|
||||
})
|
||||
}
|
||||
|
||||
const database = db()
|
||||
|
||||
try {
|
||||
// Check for active subscription
|
||||
const [subscription] = await database
|
||||
.select()
|
||||
.from(stripe_subscriptions)
|
||||
.where(
|
||||
and(
|
||||
eq(stripe_subscriptions.user_id, session.user.id),
|
||||
eq(stripe_subscriptions.status, "active")
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (subscription) {
|
||||
// Get usage for current billing period
|
||||
const now = new Date()
|
||||
const [usage] = await database
|
||||
.select()
|
||||
.from(storage_usage)
|
||||
.where(
|
||||
and(
|
||||
eq(storage_usage.user_id, session.user.id),
|
||||
lte(storage_usage.period_start, now),
|
||||
gte(storage_usage.period_end, now)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
return json({
|
||||
isGuest: false,
|
||||
isPaid: true,
|
||||
planName: "Archive Pro",
|
||||
usage: {
|
||||
archives: {
|
||||
used: usage?.archives_used ?? 0,
|
||||
limit: usage?.archives_limit ?? 10,
|
||||
remaining: Math.max(
|
||||
0,
|
||||
(usage?.archives_limit ?? 10) - (usage?.archives_used ?? 0)
|
||||
),
|
||||
},
|
||||
storage: {
|
||||
used: usage?.storage_bytes_used ?? 0,
|
||||
limit: usage?.storage_bytes_limit ?? 1073741824,
|
||||
remaining: Math.max(
|
||||
0,
|
||||
(usage?.storage_bytes_limit ?? 1073741824) -
|
||||
(usage?.storage_bytes_used ?? 0)
|
||||
),
|
||||
},
|
||||
},
|
||||
currentPeriodEnd: subscription.current_period_end,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
})
|
||||
}
|
||||
|
||||
// Free authenticated user
|
||||
return json({
|
||||
isGuest: false,
|
||||
isPaid: false,
|
||||
planName: "Free",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[billing] Error getting status:", error)
|
||||
return json({
|
||||
isGuest: false,
|
||||
isPaid: false,
|
||||
planName: "Free",
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
66
packages/web/src/routes/api/stripe/portal.ts
Normal file
66
packages/web/src/routes/api/stripe/portal.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { getAuth } from "@/lib/auth"
|
||||
import { getStripe } from "@/lib/stripe"
|
||||
import { db } from "@/db/connection"
|
||||
import { stripe_customers } from "@/db/schema"
|
||||
import { eq } from "drizzle-orm"
|
||||
|
||||
const json = (data: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(data), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
})
|
||||
|
||||
export const Route = createFileRoute("/api/stripe/portal")({
|
||||
server: {
|
||||
handlers: {
|
||||
POST: async ({ request }) => {
|
||||
const session = await getAuth().api.getSession({
|
||||
headers: request.headers,
|
||||
})
|
||||
if (!session?.user?.id) {
|
||||
return json({ error: "Unauthorized" }, 401)
|
||||
}
|
||||
|
||||
const stripe = getStripe()
|
||||
if (!stripe) {
|
||||
return json({ error: "Stripe not configured" }, 500)
|
||||
}
|
||||
|
||||
const database = db()
|
||||
|
||||
try {
|
||||
// Get Stripe customer
|
||||
const [customer] = await database
|
||||
.select()
|
||||
.from(stripe_customers)
|
||||
.where(eq(stripe_customers.user_id, session.user.id))
|
||||
.limit(1)
|
||||
|
||||
if (!customer) {
|
||||
return json({ error: "No billing account found" }, 404)
|
||||
}
|
||||
|
||||
// Parse request body for return URL
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
returnUrl?: string
|
||||
}
|
||||
|
||||
const origin = new URL(request.url).origin
|
||||
const returnUrl = body.returnUrl ?? `${origin}/archive`
|
||||
|
||||
// Create portal session
|
||||
const portalSession = await stripe.billingPortal.sessions.create({
|
||||
customer: customer.stripe_customer_id,
|
||||
return_url: returnUrl,
|
||||
})
|
||||
|
||||
return json({ url: portalSession.url })
|
||||
} catch (error) {
|
||||
console.error("[stripe] Portal error:", error)
|
||||
return json({ error: "Failed to create portal session" }, 500)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user