package.json type=module, tsconfig module/moduleResolution=NodeNext, target es2022, explicit .js on all relative imports. Center: jsonwebtoken & bcryptjs switched to default imports (ESM/CJS interop). Verified: builds, boots, full auth + plugin round-trip work under ESM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
60 lines
1.7 KiB
TypeScript
60 lines
1.7 KiB
TypeScript
import { ConflictException, Injectable } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import bcrypt from 'bcryptjs';
|
|
import { randomBytes } from 'crypto';
|
|
import { GuildNode } from '../entities/guild-node.entity.js';
|
|
import { AuditService } from '../audit/audit.service.js';
|
|
|
|
@Injectable()
|
|
export class NodeAdminService {
|
|
constructor(
|
|
@InjectRepository(GuildNode)
|
|
private readonly nodeRepo: Repository<GuildNode>,
|
|
private readonly audit: AuditService,
|
|
) {}
|
|
|
|
async registerNode(input: { nodeId: string; name: string; endpoint: string }) {
|
|
const existedByNodeId = await this.nodeRepo.findOne({ where: { nodeId: input.nodeId } });
|
|
if (existedByNodeId) {
|
|
throw new ConflictException('nodeId already exists');
|
|
}
|
|
|
|
const existedByEndpoint = await this.nodeRepo.findOne({ where: { endpoint: input.endpoint } });
|
|
if (existedByEndpoint) {
|
|
throw new ConflictException('endpoint already exists');
|
|
}
|
|
|
|
const node = this.nodeRepo.create({
|
|
nodeId: input.nodeId,
|
|
name: input.name,
|
|
endpoint: input.endpoint,
|
|
status: 'active',
|
|
apiKeyHash: null,
|
|
});
|
|
|
|
const rawApiKey = `gk_${randomBytes(24).toString('hex')}`;
|
|
node.apiKeyHash = await bcrypt.hash(rawApiKey, 10);
|
|
const saved = await this.nodeRepo.save(node);
|
|
|
|
await this.audit.write({
|
|
action: 'node.register',
|
|
targetType: 'node',
|
|
targetId: saved.nodeId,
|
|
detail: JSON.stringify({ endpoint: saved.endpoint, via: 'cli' }),
|
|
});
|
|
|
|
return {
|
|
node: {
|
|
id: saved.id,
|
|
nodeId: saved.nodeId,
|
|
name: saved.name,
|
|
endpoint: saved.endpoint,
|
|
status: saved.status,
|
|
},
|
|
apiKey: rawApiKey,
|
|
};
|
|
}
|
|
}
|
|
|