feat(guild-messaging): support message metadata for reply mentions and attachments
This commit is contained in:
40
Fabric.Backend.Guild/package-lock.json
generated
40
Fabric.Backend.Guild/package-lock.json
generated
@@ -12,6 +12,8 @@
|
||||
"@nestjs/core": "^10.4.8",
|
||||
"@nestjs/platform-express": "^10.4.8",
|
||||
"@nestjs/typeorm": "^11.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.15.1",
|
||||
"mysql2": "^3.22.3",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
@@ -592,6 +594,12 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/validator": {
|
||||
"version": "13.15.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz",
|
||||
"integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.59.3",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz",
|
||||
@@ -1290,6 +1298,23 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.15.1.tgz",
|
||||
"integrity": "sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/validator": "^13.15.3",
|
||||
"libphonenumber-js": "^1.11.1",
|
||||
"validator": "^13.15.22"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
@@ -2592,6 +2617,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/libphonenumber-js": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.1.tgz",
|
||||
"integrity": "sha512-GEw0GLL7YUUA6nv21IsCvVjtI5Ejn84sjbdfQ9KxdbqEVOk1PZh7xejn01EEiniKw+dBeCfim+8MGeuvVuE2BA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -3962,6 +3993,15 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.35",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
|
||||
"integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"@nestjs/core": "^10.4.8",
|
||||
"@nestjs/platform-express": "^10.4.8",
|
||||
"@nestjs/typeorm": "^11.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.15.1",
|
||||
"mysql2": "^3.22.3",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import 'reflect-metadata';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.setGlobalPrefix('api');
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
);
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 7002;
|
||||
await app.listen(port);
|
||||
console.log(`Fabric.Backend.Guild listening on :${port}`);
|
||||
|
||||
59
Fabric.Backend.Guild/src/messaging/dto.create-message.dto.ts
Normal file
59
Fabric.Backend.Guild/src/messaging/dto.create-message.dto.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
IsArray,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
class AttachmentDto {
|
||||
@IsString()
|
||||
@MaxLength(2048)
|
||||
url!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
export class CreateMessageDto {
|
||||
@IsString()
|
||||
@MaxLength(4000)
|
||||
content!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
clientMessageId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
replyToMessageId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ArrayMaxSize(50)
|
||||
@IsString({ each: true })
|
||||
mentions?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ArrayMaxSize(10)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AttachmentDto)
|
||||
attachments?: AttachmentDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(64)
|
||||
authorUserId?: string;
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
|
||||
import { CreateMessageDto } from './dto.create-message.dto';
|
||||
|
||||
type Message = {
|
||||
messageId: string;
|
||||
seq: number;
|
||||
content: string;
|
||||
authorUserId: string;
|
||||
replyToMessageId: string | null;
|
||||
mentions: string[];
|
||||
attachments: Array<{ url: string; name?: string; mimeType?: string }>;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
@Controller('channels/:id/messages')
|
||||
@@ -12,14 +18,19 @@ export class MessagingController {
|
||||
private messagesByChannel = new Map<string, Message[]>();
|
||||
|
||||
@Post()
|
||||
create(@Param('id') channelId: string, @Body() body: { content?: string; messageId?: string }) {
|
||||
create(@Param('id') channelId: string, @Body() body: CreateMessageDto) {
|
||||
const next = (this.seqByChannel.get(channelId) ?? 0) + 1;
|
||||
this.seqByChannel.set(channelId, next);
|
||||
|
||||
const message: Message = {
|
||||
messageId: body.messageId ?? `m-${channelId}-${next}`,
|
||||
messageId: body.clientMessageId ?? `m-${channelId}-${next}`,
|
||||
seq: next,
|
||||
content: body.content ?? '',
|
||||
content: body.content,
|
||||
authorUserId: body.authorUserId ?? 'anonymous',
|
||||
replyToMessageId: body.replyToMessageId ?? null,
|
||||
mentions: body.mentions ?? [],
|
||||
attachments: body.attachments ?? [],
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const arr = this.messagesByChannel.get(channelId) ?? [];
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
- [x] 索引设计(channel_id + seq, created_at 等)
|
||||
|
||||
### 2.2 消息主链路
|
||||
- [ ] 发送消息(content/reply/mentions/attachments 元数据)
|
||||
- [x] 发送消息(content/reply/mentions/attachments 元数据)
|
||||
- [ ] 编辑消息(可编辑窗口策略先简化)
|
||||
- [ ] 删除消息(软删 vs 硬删,先定策略)
|
||||
- [ ] `GET messages` 分页(seq 区间 + limit)
|
||||
|
||||
Reference in New Issue
Block a user