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:
parent
5e38342930
commit
a264538c5e
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user