diff --git a/Fabric.Backend.Center/src/nodes/dto.register-node.dto.ts b/Fabric.Backend.Center/src/nodes/dto.register-node.dto.ts new file mode 100644 index 0000000..b12eaee --- /dev/null +++ b/Fabric.Backend.Center/src/nodes/dto.register-node.dto.ts @@ -0,0 +1,18 @@ +import { IsString, IsUrl, MinLength } from 'class-validator'; + +export class RegisterNodeDto { + @IsString() + @MinLength(3) + nodeId!: string; + + @IsString() + @MinLength(2) + name!: string; + + @IsUrl({ require_tld: false }) + endpoint!: string; + + @IsString() + @MinLength(8) + sharedSecret!: string; +} diff --git a/Fabric.Backend.Center/src/nodes/nodes.controller.ts b/Fabric.Backend.Center/src/nodes/nodes.controller.ts index 23318e1..433c466 100644 --- a/Fabric.Backend.Center/src/nodes/nodes.controller.ts +++ b/Fabric.Backend.Center/src/nodes/nodes.controller.ts @@ -1,24 +1,53 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; - -type NodeRegistration = { - nodeId?: string; - name?: string; - endpoint?: string; - handshakeProof?: string; -}; +import { + Body, + Controller, + ForbiddenException, + Get, + Post, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { GuildNode } from '../entities/guild-node.entity'; +import { RegisterNodeDto } from './dto.register-node.dto'; @Controller('nodes') export class NodesController { - private readonly nodes: NodeRegistration[] = []; + constructor( + @InjectRepository(GuildNode) + private readonly nodeRepo: Repository, + ) {} @Post('register') - register(@Body() body: NodeRegistration) { - this.nodes.push(body); - return { status: 'accepted', handshake: 'todo-verify-shared-secret', node: body }; + async register(@Body() body: RegisterNodeDto) { + if (body.sharedSecret !== process.env.CENTER_SHARED_SECRET) { + throw new ForbiddenException('invalid shared secret'); + } + + const node = this.nodeRepo.create({ + nodeId: body.nodeId, + name: body.name, + endpoint: body.endpoint, + status: 'active', + }); + const saved = await this.nodeRepo.save(node); + + return { + status: 'accepted', + node: { + id: saved.id, + nodeId: saved.nodeId, + name: saved.name, + endpoint: saved.endpoint, + status: saved.status, + }, + }; } @Get() - list() { - return { items: this.nodes }; + async list() { + const items = await this.nodeRepo.find({ + order: { createdAt: 'DESC' }, + }); + return { items }; } } diff --git a/Fabric.Backend.Center/src/nodes/nodes.module.ts b/Fabric.Backend.Center/src/nodes/nodes.module.ts index ee6ffb4..098024a 100644 --- a/Fabric.Backend.Center/src/nodes/nodes.module.ts +++ b/Fabric.Backend.Center/src/nodes/nodes.module.ts @@ -1,7 +1,10 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { NodesController } from './nodes.controller'; +import { GuildNode } from '../entities/guild-node.entity'; @Module({ + imports: [TypeOrmModule.forFeature([GuildNode])], controllers: [NodesController], }) export class NodesModule {} diff --git a/docs/TODO-backend-center-guild.md b/docs/TODO-backend-center-guild.md index 6afa350..b1a7861 100644 --- a/docs/TODO-backend-center-guild.md +++ b/docs/TODO-backend-center-guild.md @@ -23,7 +23,7 @@ - [x] DTO + 参数校验 + 错误码规范 ### 1.2 Guild Node 注册与握手 -- [ ] `POST /nodes/register` shared-secret 校验 +- [x] `POST /nodes/register` shared-secret 校验 - [ ] node 唯一性校验(nodeId/endpoint) - [ ] node 状态模型(active/offline/revoked) - [ ] `GET /nodes` 列表 + 分页