- Updated all package.json name fields and dependency references - Updated all TypeScript/JavaScript imports - Updated .woodpecker/publish.yml filters and registry paths - Updated tools/install.sh scope default - Updated .npmrc registry paths (worktree + host) - Enhanced update-checker.ts with checkForAllUpdates() multi-package support - Updated CLI update command to show table of all packages - Added KNOWN_PACKAGES, formatAllPackagesTable, getInstallAllCommand - Marked checkForUpdate() with @deprecated JSDoc Closes #391
102 lines
2.8 KiB
TypeScript
102 lines
2.8 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
ForbiddenException,
|
|
Get,
|
|
Inject,
|
|
InternalServerErrorException,
|
|
Post,
|
|
} from '@nestjs/common';
|
|
import { randomBytes, createHash } from 'node:crypto';
|
|
import { count, eq, type Db, users as usersTable, adminTokens } from '@mosaicstack/db';
|
|
import type { Auth } from '@mosaicstack/auth';
|
|
import { v4 as uuid } from 'uuid';
|
|
import { AUTH } from '../auth/auth.tokens.js';
|
|
import { DB } from '../database/database.module.js';
|
|
import type { BootstrapSetupDto, BootstrapStatusDto, BootstrapResultDto } from './bootstrap.dto.js';
|
|
|
|
@Controller('api/bootstrap')
|
|
export class BootstrapController {
|
|
constructor(
|
|
@Inject(AUTH) private readonly auth: Auth,
|
|
@Inject(DB) private readonly db: Db,
|
|
) {}
|
|
|
|
@Get('status')
|
|
async status(): Promise<BootstrapStatusDto> {
|
|
const [result] = await this.db.select({ total: count() }).from(usersTable);
|
|
return { needsSetup: (result?.total ?? 0) === 0 };
|
|
}
|
|
|
|
@Post('setup')
|
|
async setup(@Body() dto: BootstrapSetupDto): Promise<BootstrapResultDto> {
|
|
// Only allow setup when zero users exist
|
|
const [result] = await this.db.select({ total: count() }).from(usersTable);
|
|
if ((result?.total ?? 0) > 0) {
|
|
throw new ForbiddenException('Setup already completed — users exist');
|
|
}
|
|
|
|
// Create admin user via BetterAuth API
|
|
const authApi = this.auth.api as unknown as {
|
|
createUser: (opts: {
|
|
body: { name: string; email: string; password: string; role?: string };
|
|
}) => Promise<{
|
|
user: { id: string; name: string; email: string };
|
|
}>;
|
|
};
|
|
|
|
const created = await authApi.createUser({
|
|
body: {
|
|
name: dto.name,
|
|
email: dto.email,
|
|
password: dto.password,
|
|
role: 'admin',
|
|
},
|
|
});
|
|
|
|
// Verify user was created
|
|
const [user] = await this.db
|
|
.select()
|
|
.from(usersTable)
|
|
.where(eq(usersTable.id, created.user.id))
|
|
.limit(1);
|
|
|
|
if (!user) throw new InternalServerErrorException('User created but not found');
|
|
|
|
// Ensure role is admin (createUser may not set it via BetterAuth)
|
|
if (user.role !== 'admin') {
|
|
await this.db.update(usersTable).set({ role: 'admin' }).where(eq(usersTable.id, user.id));
|
|
}
|
|
|
|
// Generate admin API token
|
|
const plaintext = randomBytes(32).toString('hex');
|
|
const tokenHash = createHash('sha256').update(plaintext).digest('hex');
|
|
const tokenId = uuid();
|
|
|
|
const [token] = await this.db
|
|
.insert(adminTokens)
|
|
.values({
|
|
id: tokenId,
|
|
userId: user.id,
|
|
tokenHash,
|
|
label: 'Initial setup token',
|
|
scope: 'admin',
|
|
})
|
|
.returning();
|
|
|
|
return {
|
|
user: {
|
|
id: user.id,
|
|
name: user.name,
|
|
email: user.email,
|
|
role: 'admin',
|
|
},
|
|
token: {
|
|
id: token!.id,
|
|
plaintext,
|
|
label: token!.label,
|
|
},
|
|
};
|
|
}
|
|
}
|