feat(entitlements): delad hushålls-pott – medlemmar delar ägarens plan & skanningskvot (MIKRO 59)
CI / Typecheck, test & build (push) Failing after 43s
CI / Typecheck, test & build (push) Failing after 43s
This commit is contained in:
@@ -8,22 +8,41 @@ import {
|
||||
type SubscriptionSnapshot,
|
||||
type TrialSnapshot,
|
||||
} from "@app/subscriptions";
|
||||
import { currentMonth } from "./helpers.js";
|
||||
import { currentMonth, getActiveHouseholdId } from "./helpers.js";
|
||||
import { errors } from "./errors.js";
|
||||
|
||||
/** Vem "äger" kvoten? Hela hushållet delar ägarens plan + pott.
|
||||
* Ensam användare = sig själv (äger sitt eget hushåll). */
|
||||
async function billingUserFor(db: Database, userId: string): Promise<string> {
|
||||
const householdId = await getActiveHouseholdId(db, userId);
|
||||
if (!householdId) return userId;
|
||||
const [owner] = await db
|
||||
.select({ userId: schema.householdMembers.userId })
|
||||
.from(schema.householdMembers)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.householdMembers.householdId, householdId),
|
||||
eq(schema.householdMembers.role, "owner"),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
return owner?.userId ?? userId;
|
||||
}
|
||||
|
||||
/** Ladda och beräkna entitlements för en användare. Backend = source of truth (spec §61.14). */
|
||||
export async function loadEntitlements(db: Database, userId: string): Promise<Entitlements> {
|
||||
const billingUserId = await billingUserFor(db, userId);
|
||||
const [subRow] = await db
|
||||
.select()
|
||||
.from(schema.subscriptions)
|
||||
.where(eq(schema.subscriptions.userId, userId))
|
||||
.where(eq(schema.subscriptions.userId, billingUserId))
|
||||
.orderBy(desc(schema.subscriptions.updatedAt))
|
||||
.limit(1);
|
||||
|
||||
const [trialRow] = await db
|
||||
.select()
|
||||
.from(schema.trials)
|
||||
.where(eq(schema.trials.userId, userId))
|
||||
.where(eq(schema.trials.userId, billingUserId))
|
||||
.limit(1);
|
||||
|
||||
const [usageRow] = await db
|
||||
@@ -31,7 +50,7 @@ export async function loadEntitlements(db: Database, userId: string): Promise<En
|
||||
.from(schema.aiUsageCounters)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.aiUsageCounters.userId, userId),
|
||||
eq(schema.aiUsageCounters.userId, billingUserId),
|
||||
eq(schema.aiUsageCounters.month, currentMonth()),
|
||||
),
|
||||
)
|
||||
@@ -69,7 +88,8 @@ export async function loadEntitlementsWithToken(
|
||||
|
||||
/** Kräv AI-kvot och räkna upp användningen atomiskt. */
|
||||
export async function consumeAiScan(db: Database, userId: string): Promise<void> {
|
||||
const ent = await loadEntitlements(db, userId);
|
||||
const billingUserId = await billingUserFor(db, userId);
|
||||
const ent = await loadEntitlements(db, billingUserId);
|
||||
if (ent.aiScansUsedThisMonth >= ent.aiScansPerMonth) {
|
||||
throw errors.quotaExceeded(
|
||||
`Månadens AI-skanningar är slut (${ent.aiScansPerMonth} st). Uppgradera för fler.`,
|
||||
@@ -78,7 +98,7 @@ export async function consumeAiScan(db: Database, userId: string): Promise<void>
|
||||
const month = currentMonth();
|
||||
await db
|
||||
.insert(schema.aiUsageCounters)
|
||||
.values({ userId, month, aiScans: 1 })
|
||||
.values({ userId: billingUserId, month, aiScans: 1 })
|
||||
.onConflictDoUpdate({
|
||||
target: [schema.aiUsageCounters.userId, schema.aiUsageCounters.month],
|
||||
set: { aiScans: (await import("drizzle-orm")).sql`${schema.aiUsageCounters.aiScans} + 1` },
|
||||
|
||||
Reference in New Issue
Block a user