/
Alex_Ural
/
opencode
Обзор
Документация
Войти
/
Alex_Ural
/
opencode
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
packages/console/core/src/referral.ts
537 строк
20 KB
Victor Navarro
feat(console): add support actions (#33492)
24 июн 2026, 13:06
Не верифицирован
24 июн 2026, 13:06
ad3651d
Код
Авторство
О чём код?
import { z } from "zod" import { and, asc, eq, inArray, isNull, sql, Database } from "./drizzle" import { Actor } from "./actor" import { Identifier } from "./identifier" 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" import { WorkspaceTable } from "./schema/workspace.sql" import { centsToMicroCents, microCentsToCents } from "./util/price" import { fn } from "./util/fn" import { Billing } from "./billing" import { LiteData } from "./lite" import { Subscription } from "./subscription" import { ulid } from "ulid" export namespace Referral { export const REWARD_AMOUNT = centsToMicroCents(500) export const CODE_LENGTH = 10 export function normalizeCode(code?: string | null) { return code ?.toUpperCase() .replace(/[^A-Z0-9]/g, "") .slice(0, CODE_LENGTH) } function generateCode() { return ulid().slice(-CODE_LENGTH).toUpperCase() } async function ensureCode(workspaceID = Actor.workspace()) { return Database.use(async (db) => { const existing = await db .select({ code: ReferralCodeTable.code }) .from(ReferralCodeTable) .where(eq(ReferralCodeTable.workspaceID, workspaceID)) .then((rows) => rows[0]) if (existing) return { code: existing.code } await db.insert(ReferralCodeTable).ignore().values({ workspaceID, code: generateCode(), }) const created = await db .select({ code: ReferralCodeTable.code }) .from(ReferralCodeTable) .where(eq(ReferralCodeTable.workspaceID, workspaceID)) .then((rows) => rows[0]) if (created) return { code: created.code } throw new Error("Failed to generate referral code") }) } export const summary = fn(z.void(), async () => { const workspaceID = Actor.workspace() const accountID = Actor.account() const code = await ensureCode(workspaceID) const rows = await Database.use(async (tx) => { const [rewards, invites, inviteeReferral, inviteeRewards] = await Promise.all([ tx .select({ referralID: ReferralRewardTable.referralID, workspaceID: ReferralRewardTable.workspaceID, referralWorkspaceID: ReferralTable.workspaceID, inviteeEmail: AuthTable.subject, amount: ReferralRewardTable.amount, timeCreated: ReferralRewardTable.timeCreated, timeApplied: ReferralRewardTable.timeApplied, }) .from(ReferralRewardTable) .innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID)) .innerJoin( AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email")), ) .where( and( eq(ReferralRewardTable.workspaceID, workspaceID), isNull(ReferralRewardTable.timeDeleted), isNull(ReferralTable.timeDeleted), ), ), tx .select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) .from(ReferralTable) .innerJoin( AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email")), ) .where(and(eq(ReferralTable.workspaceID, workspaceID), isNull(ReferralTable.timeDeleted))), tx .select({ id: ReferralTable.id, inviterEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) .from(ReferralTable) .leftJoin( UserTable, and( eq(UserTable.workspaceID, ReferralTable.workspaceID), eq(UserTable.role, "admin"), isNull(UserTable.timeDeleted), ), ) .leftJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email"))) .where(and(eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralTable.timeDeleted))) .orderBy(asc(UserTable.timeCreated)) .then((rows) => rows.find((row) => row.inviterEmail) ?? rows[0]), tx .select({ referralID: ReferralRewardTable.referralID }) .from(ReferralRewardTable) .innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID)) .where( and( eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralRewardTable.timeDeleted), isNull(ReferralTable.timeDeleted), ), ), ]) return { inviteeReferral, inviteeRewards, invites, rewards } }) const rewardReferralIDs = new Set(rows.rewards.map((reward) => reward.referralID)) const inviteeRewardReferralIDs = new Set(rows.inviteeRewards.map((reward) => reward.referralID)) const rewards = rows.rewards.map((reward) => { const source = reward.workspaceID === reward.referralWorkspaceID ? ("inviter" as const) : ("invitee" as const) return { id: reward.referralID, source, status: reward.timeApplied ? ("applied" as const) : ("available" as const), email: source === "invitee" ? (rows.inviteeReferral?.inviterEmail ?? null) : reward.inviteeEmail, amount: microCentsToCents(reward.amount), timeCreated: reward.timeCreated, timeApplied: reward.timeApplied, } }) const pending = [ ...rows.invites .filter((referral) => !rewardReferralIDs.has(referral.id)) .map((referral) => ({ id: `${referral.id}:inviter`, source: "inviter" as const, status: "pending" as const, email: referral.inviteeEmail, amount: microCentsToCents(REWARD_AMOUNT), timeCreated: referral.timeCreated, timeApplied: null, })), ...(rows.inviteeReferral && !inviteeRewardReferralIDs.has(rows.inviteeReferral.id) ? [ { id: `${rows.inviteeReferral.id}:invitee`, source: "invitee" as const, status: "pending" as const, email: rows.inviteeReferral.inviterEmail, amount: microCentsToCents(REWARD_AMOUNT), timeCreated: rows.inviteeReferral.timeCreated, timeApplied: null, }, ] : []), ] const allRewards = [...pending, ...rewards].sort( (a, b) => new Date(b.timeCreated).getTime() - new Date(a.timeCreated).getTime(), ) return { referralCode: code.code, hasReferral: allRewards.length > 0, rewardAmount: microCentsToCents(REWARD_AMOUNT), rewards: allRewards, } }) export const applyReward = fn(z.object({ referralID: z.string() }), async (input) => { const workspaceID = Actor.workspace() return Database.transaction(async (tx) => { const reward = await tx .select({ amount: ReferralRewardTable.amount, timeApplied: ReferralRewardTable.timeApplied }) .from(ReferralRewardTable) .where( and( eq(ReferralRewardTable.workspaceID, workspaceID), eq(ReferralRewardTable.referralID, input.referralID), isNull(ReferralRewardTable.timeDeleted), ), ) .then((rows) => rows[0]) if (!reward) throw new Error("Referral reward not found") if (reward.timeApplied) throw new Error("Referral reward already applied") const update = await tx .update(ReferralRewardTable) .set({ timeApplied: sql`now()`, }) .where( and( eq(ReferralRewardTable.workspaceID, workspaceID), eq(ReferralRewardTable.referralID, input.referralID), isNull(ReferralRewardTable.timeApplied), isNull(ReferralRewardTable.timeDeleted), ), ) if (update.rowsAffected === 0) throw new Error("Referral reward already applied") await Billing.subtractLiteUsage(workspaceID, reward.amount) return { amount: microCentsToCents(reward.amount) } }) }) export const usagePreview = fn(z.object({ referralID: z.string() }), async (input) => { const row = await Database.use((tx) => tx .select({ rewardAmount: ReferralRewardTable.amount, rollingUsage: LiteTable.rollingUsage, weeklyUsage: LiteTable.weeklyUsage, monthlyUsage: LiteTable.monthlyUsage, timeRollingUpdated: LiteTable.timeRollingUpdated, timeWeeklyUpdated: LiteTable.timeWeeklyUpdated, timeMonthlyUpdated: LiteTable.timeMonthlyUpdated, timeCreated: LiteTable.timeCreated, }) .from(ReferralRewardTable) .innerJoin(LiteTable, eq(LiteTable.workspaceID, ReferralRewardTable.workspaceID)) .where( and( eq(ReferralRewardTable.workspaceID, Actor.workspace()), eq(ReferralRewardTable.referralID, input.referralID), isNull(ReferralRewardTable.timeApplied), isNull(ReferralRewardTable.timeDeleted), isNull(LiteTable.timeDeleted), ), ) .then((rows) => rows[0]), ) if (!row) return null const limits = LiteData.getLimits() return { rollingUsage: usagePreviewItem( Subscription.analyzeRollingUsage({ limit: limits.rollingLimit, window: limits.rollingWindow, usage: row.rollingUsage ?? 0, timeUpdated: row.timeRollingUpdated ?? new Date(), }), Subscription.analyzeRollingUsage({ limit: limits.rollingLimit, window: limits.rollingWindow, usage: Math.max(0, (row.rollingUsage ?? 0) - row.rewardAmount), timeUpdated: row.timeRollingUpdated ?? new Date(), }), ), weeklyUsage: usagePreviewItem( Subscription.analyzeWeeklyUsage({ limit: limits.weeklyLimit, usage: row.weeklyUsage ?? 0, timeUpdated: row.timeWeeklyUpdated ?? new Date(), }), Subscription.analyzeWeeklyUsage({ limit: limits.weeklyLimit, usage: Math.max(0, (row.weeklyUsage ?? 0) - row.rewardAmount), timeUpdated: row.timeWeeklyUpdated ?? new Date(), }), ), monthlyUsage: usagePreviewItem( Subscription.analyzeMonthlyUsage({ limit: limits.monthlyLimit, usage: row.monthlyUsage ?? 0, timeUpdated: row.timeMonthlyUpdated ?? new Date(), timeSubscribed: row.timeCreated, }), Subscription.analyzeMonthlyUsage({ limit: limits.monthlyLimit, usage: Math.max(0, (row.monthlyUsage ?? 0) - row.rewardAmount), timeUpdated: row.timeMonthlyUpdated ?? new Date(), timeSubscribed: row.timeCreated, }), ), } }) export async function createFromAccount(input: { accountID: string; referralCode?: string }) { const referralCode = normalizeCode(input.referralCode) if (!referralCode) return return Database.transaction(async (tx) => { const code = await tx .select({ workspaceID: ReferralCodeTable.workspaceID }) .from(ReferralCodeTable) .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, ReferralCodeTable.workspaceID)) .where(and(eq(ReferralCodeTable.code, referralCode), isNull(WorkspaceTable.timeDeleted))) .then((rows) => rows[0]) if (!code) throw new Error("Referral code invalid") const existingReferral = await tx .select({ id: ReferralTable.id }) .from(ReferralTable) .where(and(eq(ReferralTable.inviteeAccountID, input.accountID), isNull(ReferralTable.timeDeleted))) .then((rows) => rows[0]) if (existingReferral) throw new Error("Referral already redeemed") const selfReferral = await tx .select({ id: UserTable.id }) .from(UserTable) .where( and( eq(UserTable.workspaceID, code.workspaceID), eq(UserTable.accountID, input.accountID), isNull(UserTable.timeDeleted), ), ) .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 activeLite = await tx .select({ id: LiteTable.id }) .from(LiteTable) .where(and(inArray(LiteTable.workspaceID, workspaceIDs), isNull(LiteTable.timeDeleted))) .then((rows) => rows[0]) if (activeLite) 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, id: referralID, inviteeAccountID: input.accountID, }) const referral = await tx .select({ id: ReferralTable.id, workspaceID: ReferralTable.workspaceID }) .from(ReferralTable) .where(and(eq(ReferralTable.inviteeAccountID, input.accountID), isNull(ReferralTable.timeDeleted))) .then((rows) => rows[0]) if (!referral) throw new Error("Referral not created") if (referral.id !== referralID) throw new Error("Referral already redeemed") }) } export async function create(input: { inviterWorkspaceID: string; inviteeWorkspaceID: string }) { return Database.transaction(async (tx) => { if (input.inviterWorkspaceID === input.inviteeWorkspaceID) throw new Error("Self-referral workspace mismatch") const inviterWorkspace = await tx .select({ id: WorkspaceTable.id }) .from(WorkspaceTable) .where(and(eq(WorkspaceTable.id, input.inviterWorkspaceID), isNull(WorkspaceTable.timeDeleted))) .then((rows) => rows[0]) if (!inviterWorkspace) throw new Error(`Inviter workspace not found: ${input.inviterWorkspaceID}`) const inviteeWorkspace = await tx .select({ id: WorkspaceTable.id }) .from(WorkspaceTable) .where(and(eq(WorkspaceTable.id, input.inviteeWorkspaceID), isNull(WorkspaceTable.timeDeleted))) .then((rows) => rows[0]) if (!inviteeWorkspace) throw new Error(`Invitee workspace not found: ${input.inviteeWorkspaceID}`) const invitee = await tx .select({ accountID: UserTable.accountID, userID: UserTable.id, liteID: LiteTable.id }) .from(UserTable) .innerJoin( LiteTable, and( eq(LiteTable.workspaceID, UserTable.workspaceID), eq(LiteTable.userID, UserTable.id), isNull(LiteTable.timeDeleted), ), ) .where( and( eq(UserTable.workspaceID, input.inviteeWorkspaceID), eq(UserTable.role, "admin"), isNull(UserTable.timeDeleted), ), ) .then((rows) => rows[0]) if (!invitee?.accountID) throw new Error(`Invitee Lite workspace owner not found: ${input.inviteeWorkspaceID}`) const inviterUser = await tx .select({ id: UserTable.id }) .from(UserTable) .where( and( eq(UserTable.workspaceID, input.inviterWorkspaceID), eq(UserTable.accountID, invitee.accountID), isNull(UserTable.timeDeleted), ), ) .then((rows) => rows[0]) if (inviterUser) throw new Error(`Self-referral is not allowed: ${invitee.accountID}`) const existingReferral = await tx .select({ id: ReferralTable.id, workspaceID: ReferralTable.workspaceID }) .from(ReferralTable) .where(and(eq(ReferralTable.inviteeAccountID, invitee.accountID), isNull(ReferralTable.timeDeleted))) .then((rows) => rows[0]) if (existingReferral && existingReferral.workspaceID !== input.inviterWorkspaceID) { throw new Error(`Referral already belongs to ${existingReferral.workspaceID}: ${existingReferral.id}`) } const referralID = existingReferral?.id ?? Identifier.create("referral") if (!existingReferral) { await tx.insert(ReferralTable).ignore().values({ workspaceID: input.inviterWorkspaceID, id: referralID, inviteeAccountID: invitee.accountID, }) const referral = await tx .select({ id: ReferralTable.id }) .from(ReferralTable) .where(and(eq(ReferralTable.inviteeAccountID, invitee.accountID), isNull(ReferralTable.timeDeleted))) .then((rows) => rows[0]) if (!referral) throw new Error(`Referral not created: ${invitee.accountID}`) if (referral.id !== referralID) throw new Error(`Referral already redeemed: ${referral.id}`) } const rewardInsert = await tx .insert(ReferralRewardTable) .ignore() .values([ { workspaceID: input.inviterWorkspaceID, referralID, amount: REWARD_AMOUNT }, { workspaceID: input.inviteeWorkspaceID, referralID, amount: REWARD_AMOUNT }, ]) const rewards = await tx .select({ workspaceID: ReferralRewardTable.workspaceID, amount: ReferralRewardTable.amount }) .from(ReferralRewardTable) .where( and( eq(ReferralRewardTable.referralID, referralID), inArray(ReferralRewardTable.workspaceID, [input.inviterWorkspaceID, input.inviteeWorkspaceID]), isNull(ReferralRewardTable.timeDeleted), ), ) if (rewards.length !== 2) throw new Error(`Referral rewards not created: ${referralID}`) if (rewards.some((reward) => reward.amount !== REWARD_AMOUNT)) { throw new Error(`Referral reward amount mismatch: ${referralID}`) } return { referralID, createdReferral: !existingReferral, createdRewards: rewardInsert.rowsAffected, inviteeAccountID: invitee.accountID, inviteeUserID: invitee.userID, liteID: invitee.liteID, rewardWorkspaces: rewards.map((reward) => reward.workspaceID), } }) } export async function completeFromLiteSubscription(input: { workspaceID: string; userID: string }) { return Database.transaction(async (tx) => { const invitee = await tx .select({ accountID: UserTable.accountID }) .from(UserTable) .where( and( eq(UserTable.workspaceID, input.workspaceID), eq(UserTable.id, input.userID), isNull(UserTable.timeDeleted), ), ) .then((rows) => rows[0]) if (!invitee?.accountID) throw new Error("Referral invitee account missing") const referral = await tx .select({ id: ReferralTable.id, workspaceID: ReferralTable.workspaceID }) .from(ReferralTable) .where(and(eq(ReferralTable.inviteeAccountID, invitee.accountID), isNull(ReferralTable.timeDeleted))) .then((rows) => rows[0]) if (!referral) return await tx.insert(ReferralRewardTable).ignore().values({ workspaceID: referral.workspaceID, referralID: referral.id, amount: REWARD_AMOUNT, }) const existingInviteeReward = await tx .select({ workspaceID: ReferralRewardTable.workspaceID }) .from(ReferralRewardTable) .where( and( eq(ReferralRewardTable.referralID, referral.id), sql`${ReferralRewardTable.workspaceID} <> ${referral.workspaceID}`, isNull(ReferralRewardTable.timeDeleted), ), ) .then((rows) => rows[0]) if (existingInviteeReward) return await tx.insert(ReferralRewardTable).ignore().values({ workspaceID: input.workspaceID, referralID: referral.id, amount: REWARD_AMOUNT, }) }) } function usagePreviewItem( before: { usagePercent: number; resetInSec: number }, after: { usagePercent: number; resetInSec: number }, ) { return { beforePercent: before.usagePercent, afterPercent: after.usagePercent, resetInSec: after.resetInSec, } } }