mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-31 05:15:32 +00:00
feat: improve referral system (#29720)
This commit is contained in:
parent
1e5ddbd812
commit
fdc574ff81
7 changed files with 55 additions and 65 deletions
|
|
@ -2,6 +2,7 @@ import { action, json, query, useAction, useSubmission } from "@solidjs/router"
|
|||
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import { Referral } from "@opencode-ai/console-core/referral.js"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { Modal } from "~/component/modal"
|
||||
import { IconCheck, IconCopy } from "~/component/icon"
|
||||
|
|
@ -9,6 +10,7 @@ import { useI18n } from "~/context/i18n"
|
|||
import { useLanguage } from "~/context/language"
|
||||
import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
||||
import { queryLiteSubscription } from "~/routes/workspace/[id]/go/lite-section"
|
||||
import { clearReferralCookie, referralCodeFromCookieHeader } from "~/lib/referral-invite"
|
||||
import "./go-referral.css"
|
||||
|
||||
type GoReferralSummary = Awaited<ReturnType<typeof Referral.summary>>
|
||||
|
|
@ -25,7 +27,21 @@ const emptyUsagePreview = {
|
|||
|
||||
export const queryGoReferral = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(() => Referral.summary(), workspaceID)
|
||||
return withActor(async () => {
|
||||
const event = getRequestEvent()
|
||||
const referralCode = referralCodeFromCookieHeader(event?.request.headers.get("cookie") ?? null)
|
||||
if (referralCode) {
|
||||
await Referral.createFromAccount({
|
||||
accountID: Actor.account(),
|
||||
referralCode,
|
||||
}).catch((error) => {
|
||||
console.error("Referral create failed", error)
|
||||
})
|
||||
event?.response.headers.append("set-cookie", clearReferralCookie())
|
||||
}
|
||||
|
||||
return Referral.summary()
|
||||
}, workspaceID)
|
||||
}, "go.referral.get")
|
||||
|
||||
export const queryGoReferralUsagePreview = query(async (workspaceID: string, referralID?: string) => {
|
||||
|
|
@ -65,6 +81,7 @@ function rewardDescriptionKey(source: GoReferralReward["source"]) {
|
|||
|
||||
function rewardActionKey(reward: GoReferralReward, hasActiveGo: boolean) {
|
||||
if (reward.status === "applied") return "workspace.referral.reward.action.applied" as const
|
||||
if (reward.status === "pending" && reward.source === "inviter") return "workspace.referral.reward.source.pendingInviter" as const
|
||||
if (reward.status === "pending" || !hasActiveGo) return "workspace.referral.reward.action.subscribeUnlock" as const
|
||||
return "workspace.referral.reward.action.view" as const
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { redirect } from "@solidjs/router"
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Referral } from "@opencode-ai/console-core/referral.js"
|
||||
import { AuthClient } from "~/context/auth"
|
||||
import { useAuthSession } from "~/context/auth"
|
||||
import { i18n } from "~/i18n"
|
||||
import { localeFromRequest, route } from "~/lib/language"
|
||||
import { clearReferralCookie, referralCodeFromCookieHeader } from "~/lib/referral-invite"
|
||||
|
||||
export async function GET(input: APIEvent) {
|
||||
const url = new URL(input.request.url)
|
||||
|
|
@ -19,7 +17,6 @@ export async function GET(input: APIEvent) {
|
|||
if (result.err) throw new Error(result.err.message)
|
||||
const decoded = AuthClient.decode(result.tokens.access, {} as any)
|
||||
if (decoded.err) throw new Error(decoded.err.message)
|
||||
const referralCode = referralCodeFromCookieHeader(input.request.headers.get("cookie"))
|
||||
const session = await useAuthSession()
|
||||
const id = decoded.subject.properties.accountID
|
||||
await session.update((value) => {
|
||||
|
|
@ -35,15 +32,8 @@ export async function GET(input: APIEvent) {
|
|||
current: id,
|
||||
}
|
||||
})
|
||||
if (decoded.subject.properties.newAccount && referralCode) {
|
||||
await Referral.createFromAccount({ accountID: id, referralCode }).catch((error) => {
|
||||
console.error("Referral create failed", error)
|
||||
})
|
||||
}
|
||||
const next = url.pathname === "/auth/callback" ? "/auth" : url.pathname.replace("/auth/callback", "")
|
||||
const response = redirect(route(locale, next))
|
||||
if (referralCode) response.headers.append("set-cookie", clearReferralCookie())
|
||||
return response
|
||||
return redirect(route(locale, next))
|
||||
} catch (e: any) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export default defineConfig({
|
|||
],
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
port: 3001,
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { z } from "zod"
|
||||
import { and, asc, eq, isNull, sql, Database } from "./drizzle"
|
||||
import { and, asc, eq, inArray, isNull, sql, Database } from "./drizzle"
|
||||
import { Actor } from "./actor"
|
||||
import { Identifier } from "./identifier"
|
||||
import { LiteTable } from "./schema/billing.sql"
|
||||
import { LiteTable, PaymentTable } from "./schema/billing.sql"
|
||||
import { ReferralCodeTable, ReferralRewardTable, ReferralTable } from "./schema/referral.sql"
|
||||
import { AuthTable } from "./schema/auth.sql"
|
||||
import { UserTable } from "./schema/user.sql"
|
||||
|
|
@ -318,6 +318,26 @@ export namespace Referral {
|
|||
.then((rows) => rows[0])
|
||||
if (selfReferral) throw new Error("Self-referral is not allowed")
|
||||
|
||||
const workspaceIDs = await tx
|
||||
.select({ workspaceID: UserTable.workspaceID })
|
||||
.from(UserTable)
|
||||
.where(and(eq(UserTable.accountID, input.accountID), isNull(UserTable.timeDeleted)))
|
||||
.then((rows) => rows.map((row) => row.workspaceID))
|
||||
if (workspaceIDs.length === 0) return
|
||||
|
||||
const litePayment = await tx
|
||||
.select({ id: PaymentTable.id })
|
||||
.from(PaymentTable)
|
||||
.where(
|
||||
and(
|
||||
inArray(PaymentTable.workspaceID, workspaceIDs),
|
||||
isNull(PaymentTable.timeDeleted),
|
||||
sql`JSON_UNQUOTE(JSON_EXTRACT(${PaymentTable.enrichment}, '$.type')) = 'lite'`,
|
||||
),
|
||||
)
|
||||
.then((rows) => rows[0])
|
||||
if (litePayment) return
|
||||
|
||||
const referralID = Identifier.create("referral")
|
||||
await tx.insert(ReferralTable).ignore().values({
|
||||
workspaceID: code.workspaceID,
|
||||
|
|
@ -355,7 +375,7 @@ export namespace Referral {
|
|||
.from(ReferralTable)
|
||||
.where(and(eq(ReferralTable.inviteeAccountID, invitee.accountID), isNull(ReferralTable.timeDeleted)))
|
||||
.then((rows) => rows[0])
|
||||
if (!referral) throw new Error("Referral not found")
|
||||
if (!referral) return
|
||||
|
||||
const result = await tx
|
||||
.insert(ReferralRewardTable)
|
||||
|
|
@ -373,7 +393,7 @@ export namespace Referral {
|
|||
},
|
||||
])
|
||||
|
||||
if (result.rowsAffected === 0) throw new Error("Referral already completed")
|
||||
if (result.rowsAffected === 0) return
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export default defineConfig({
|
|||
server: {
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
port: 3002,
|
||||
},
|
||||
worker: {
|
||||
format: "es",
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@
|
|||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"include": ["*.ts", "src", "../core/src/resource.d.ts"]
|
||||
}
|
||||
|
|
|
|||
56
sst-env.d.ts
vendored
56
sst-env.d.ts
vendored
|
|
@ -26,6 +26,14 @@ declare module "sst" {
|
|||
"AuthApi": import("@cloudflare/workers-types").Service
|
||||
"AuthStorage": import("@cloudflare/workers-types").KVNamespace
|
||||
"Bucket": import("@cloudflare/workers-types").R2Bucket
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
|
|
@ -91,37 +99,6 @@ declare module "sst" {
|
|||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"InferenceEvent": {
|
||||
"catalog": string
|
||||
"database": string
|
||||
"region": string
|
||||
"table": string
|
||||
"tableBucket": string
|
||||
"type": "sst.sst.Linkable"
|
||||
"workgroup": string
|
||||
}
|
||||
"LakeIngest": {
|
||||
"secret": string
|
||||
"type": "sst.sst.Linkable"
|
||||
"url": string
|
||||
}
|
||||
"LakeIngestConfig": {
|
||||
"secret": string
|
||||
"streamName": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"LakeIngestSecret": {
|
||||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"LakeIngestService": {
|
||||
"service": string
|
||||
"type": "sst.aws.Service"
|
||||
"url": string
|
||||
}
|
||||
"LakeVpc": {
|
||||
"type": "sst.aws.Vpc"
|
||||
}
|
||||
"LogProcessor": import("@cloudflare/workers-types").Service
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
|
|
@ -156,23 +133,6 @@ declare module "sst" {
|
|||
"value": string
|
||||
}
|
||||
"Stat": import("@cloudflare/workers-types").Service
|
||||
"StatsDatabase": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"url": string
|
||||
"username": string
|
||||
}
|
||||
"StatsSyncConfig": {
|
||||
"dataset": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"StatsSyncService": {
|
||||
"service": string
|
||||
"type": "sst.aws.Service"
|
||||
}
|
||||
"Teams": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue