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/core": "^10.4.8",
|
||||||
"@nestjs/platform-express": "^10.4.8",
|
"@nestjs/platform-express": "^10.4.8",
|
||||||
"@nestjs/typeorm": "^11.0.1",
|
"@nestjs/typeorm": "^11.0.1",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.15.1",
|
||||||
"mysql2": "^3.22.3",
|
"mysql2": "^3.22.3",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
@@ -592,6 +594,12 @@
|
|||||||
"undici-types": "~6.21.0"
|
"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": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.59.3",
|
"version": "8.59.3",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz",
|
"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"
|
"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": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
@@ -2592,6 +2617,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"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": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
@@ -3962,6 +3993,15 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
"@nestjs/core": "^10.4.8",
|
"@nestjs/core": "^10.4.8",
|
||||||
"@nestjs/platform-express": "^10.4.8",
|
"@nestjs/platform-express": "^10.4.8",
|
||||||
"@nestjs/typeorm": "^11.0.1",
|
"@nestjs/typeorm": "^11.0.1",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.15.1",
|
||||||
"mysql2": "^3.22.3",
|
"mysql2": "^3.22.3",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.setGlobalPrefix('api');
|
app.setGlobalPrefix('api');
|
||||||
|
app.useGlobalPipes(
|
||||||
|
new ValidationPipe({
|
||||||
|
whitelist: true,
|
||||||
|
forbidNonWhitelisted: true,
|
||||||
|
transform: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
const port = process.env.PORT ? Number(process.env.PORT) : 7002;
|
const port = process.env.PORT ? Number(process.env.PORT) : 7002;
|
||||||
await app.listen(port);
|
await app.listen(port);
|
||||||
console.log(`Fabric.Backend.Guild listening on :${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 { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
|
||||||
|
import { CreateMessageDto } from './dto.create-message.dto';
|
||||||
|
|
||||||
type Message = {
|
type Message = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
seq: number;
|
seq: number;
|
||||||
content: string;
|
content: string;
|
||||||
|
authorUserId: string;
|
||||||
|
replyToMessageId: string | null;
|
||||||
|
mentions: string[];
|
||||||
|
attachments: Array<{ url: string; name?: string; mimeType?: string }>;
|
||||||
|
createdAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Controller('channels/:id/messages')
|
@Controller('channels/:id/messages')
|
||||||
@@ -12,14 +18,19 @@ export class MessagingController {
|
|||||||
private messagesByChannel = new Map<string, Message[]>();
|
private messagesByChannel = new Map<string, Message[]>();
|
||||||
|
|
||||||
@Post()
|
@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;
|
const next = (this.seqByChannel.get(channelId) ?? 0) + 1;
|
||||||
this.seqByChannel.set(channelId, next);
|
this.seqByChannel.set(channelId, next);
|
||||||
|
|
||||||
const message: Message = {
|
const message: Message = {
|
||||||
messageId: body.messageId ?? `m-${channelId}-${next}`,
|
messageId: body.clientMessageId ?? `m-${channelId}-${next}`,
|
||||||
seq: 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) ?? [];
|
const arr = this.messagesByChannel.get(channelId) ?? [];
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
- [x] 索引设计(channel_id + seq, created_at 等)
|
- [x] 索引设计(channel_id + seq, created_at 等)
|
||||||
|
|
||||||
### 2.2 消息主链路
|
### 2.2 消息主链路
|
||||||
- [ ] 发送消息(content/reply/mentions/attachments 元数据)
|
- [x] 发送消息(content/reply/mentions/attachments 元数据)
|
||||||
- [ ] 编辑消息(可编辑窗口策略先简化)
|
- [ ] 编辑消息(可编辑窗口策略先简化)
|
||||||
- [ ] 删除消息(软删 vs 硬删,先定策略)
|
- [ ] 删除消息(软删 vs 硬删,先定策略)
|
||||||
- [ ] `GET messages` 分页(seq 区间 + limit)
|
- [ ] `GET messages` 分页(seq 区间 + limit)
|
||||||
|
|||||||
Reference in New Issue
Block a user