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 type { FastifyInstance } from 'fastify';
|
||||||
import { getRequestProductConfig } from '../../lib/request-context.js';
|
|
||||||
import { BadRequestError, UnauthorizedError } from '../../lib/errors.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 repo from './repository.js';
|
||||||
import * as jwt from './jwt.js';
|
import * as jwt from './jwt.js';
|
||||||
import { LoginSchema, RegisterSchema, RefreshSchema, type UserDoc } from './types.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('; '));
|
throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
|
||||||
}
|
}
|
||||||
const { email, password, displayName, role, productId } = parsed.data;
|
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);
|
const existing = await repo.getByEmail(email, productId);
|
||||||
if (existing) throw new BadRequestError('Email already registered');
|
if (existing) throw new BadRequestError('Email already registered');
|
||||||
@ -82,6 +87,46 @@ export async function authRoutes(app: FastifyInstance) {
|
|||||||
};
|
};
|
||||||
await repo.create(user);
|
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({
|
const accessToken = await jwt.createAccessToken({
|
||||||
sub: user.id,
|
sub: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user