feat(platform-service): register hook provisions subscription + license from product config

- /auth/register now validates product from products cache
- Automatically provisions initial subscription using product defaultPlan + trialDays
- Automatically provisions initial license using product licensePrefix + deviceLimits
- Keeps auth user creation as primary flow while adding provisioning side-effects
- Verified: tsc --noEmit clean, 19 test files / 178 tests passing
This commit is contained in:
saravanakumardb1 2026-02-15 14:44:31 -08:00
parent 5e38342930
commit a264538c5e

View File

@ -9,8 +9,10 @@
*/
import type { FastifyInstance } from 'fastify';
import { getRequestProductConfig } from '../../lib/request-context.js';
import { BadRequestError, UnauthorizedError } from '../../lib/errors.js';
import { getProduct } from '../products/cache.js';
import * as subscriptionRepo from '../subscriptions/repository.js';
import * as licenseRepo from '../licenses/repository.js';
import * as repo from './repository.js';
import * as jwt from './jwt.js';
import { LoginSchema, RegisterSchema, RefreshSchema, type UserDoc } from './types.js';
@ -61,7 +63,10 @@ export async function authRoutes(app: FastifyInstance) {
throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
}
const { email, password, displayName, role, productId } = parsed.data;
const product = getRequestProductConfig(req);
const product = getProduct(productId);
if (!product || product.status !== 'active') {
throw new BadRequestError(`Unknown or disabled product: ${productId}`);
}
const existing = await repo.getByEmail(email, productId);
if (existing) throw new BadRequestError('Email already registered');
@ -82,6 +87,46 @@ export async function authRoutes(app: FastifyInstance) {
};
await repo.create(user);
const nowDate = new Date();
const nowIso = nowDate.toISOString();
const trialEnd = new Date(nowDate);
trialEnd.setDate(trialEnd.getDate() + product.trialDays);
const hasTrial = product.trialDays > 0;
const initialPlan = product.defaultPlan;
// Registration hook: initialize subscription + license using product defaults.
// Best-effort during migration; auth account creation remains primary.
await subscriptionRepo.createSubscription({
id: `sub_${user.id}_${Date.now()}`,
productId,
userId: user.id,
plan: initialPlan,
status: hasTrial ? 'trialing' : 'active',
currentPeriodStart: nowIso,
currentPeriodEnd: hasTrial ? trialEnd.toISOString() : nowIso,
cancelAtPeriodEnd: false,
monthlyPrice: 0,
tokensIncluded: 0,
tokensUsed: 0,
createdAt: nowIso,
updatedAt: nowIso,
});
await licenseRepo.create({
id: `lic_${crypto.randomUUID()}`,
productId,
key: licenseRepo.generateKey(product.licensePrefix),
userId: user.id,
plan: initialPlan,
status: 'active',
activatedAt: null,
expiresAt: hasTrial ? trialEnd.toISOString() : null,
deviceIds: [],
maxDevices: product.deviceLimits[initialPlan],
createdAt: nowIso,
updatedAt: nowIso,
});
const accessToken = await jwt.createAccessToken({
sub: user.id,
email: user.email,