Compare commits
1 Commits
78d2179e8c
...
774dff11ba
| Author | SHA1 | Date | |
|---|---|---|---|
| 774dff11ba |
@@ -1,18 +1,33 @@
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Post, Query, Req, UnauthorizedException } from '@nestjs/common';
|
||||
import { ChannelsService } from './channels.service';
|
||||
|
||||
// ApiKeyGuard attaches the introspected Center user id onto the request.
|
||||
type AuthedRequest = { userId?: string };
|
||||
|
||||
@Controller('channels')
|
||||
export class ChannelsController {
|
||||
constructor(private readonly channelsService: ChannelsService) {}
|
||||
|
||||
@Get()
|
||||
list(@Query('guildId') guildId?: string) {
|
||||
if (!guildId) return this.channelsService.listAll();
|
||||
return this.channelsService.listByGuild(guildId);
|
||||
list(@Req() req: AuthedRequest, @Query('guildId') guildId?: string) {
|
||||
const userId = req.userId ?? '';
|
||||
if (!userId) throw new UnauthorizedException('missing user');
|
||||
return this.channelsService.listForUser(String(guildId ?? ''), userId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(@Body() body: Record<string, unknown>) {
|
||||
return this.channelsService.create(body);
|
||||
create(@Req() req: AuthedRequest, @Body() body: Record<string, unknown>) {
|
||||
const userId = req.userId ?? '';
|
||||
if (!userId) throw new UnauthorizedException('missing user');
|
||||
return this.channelsService.create(
|
||||
{
|
||||
guildId: body.guildId as string | undefined,
|
||||
name: body.name as string | undefined,
|
||||
kind: body.kind as string | undefined,
|
||||
isPublic: Boolean(body.isPublic),
|
||||
memberUserIds: Array.isArray(body.memberUserIds) ? (body.memberUserIds as string[]) : [],
|
||||
},
|
||||
userId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ChannelsController } from './channels.controller';
|
||||
import { Channel } from '../entities/channel.entity';
|
||||
import { ChannelMember } from '../entities/channel-member.entity';
|
||||
import { ChannelsService } from './channels.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Channel])],
|
||||
imports: [TypeOrmModule.forFeature([Channel, ChannelMember])],
|
||||
controllers: [ChannelsController],
|
||||
providers: [ChannelsService],
|
||||
exports: [ChannelsService],
|
||||
|
||||
@@ -1,43 +1,74 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { Channel } from '../entities/channel.entity';
|
||||
import { ChannelMember } from '../entities/channel-member.entity';
|
||||
|
||||
type CreateChannelInput = {
|
||||
guildId?: string;
|
||||
name?: string;
|
||||
kind?: string;
|
||||
isPublic?: boolean;
|
||||
memberUserIds?: string[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ChannelsService {
|
||||
constructor(
|
||||
@InjectRepository(Channel)
|
||||
private readonly channelRepo: Repository<Channel>,
|
||||
@InjectRepository(ChannelMember)
|
||||
private readonly memberRepo: Repository<ChannelMember>,
|
||||
) {}
|
||||
|
||||
listByGuild(guildId: string) {
|
||||
return this.channelRepo.find({
|
||||
where: { guildId },
|
||||
// Channels visible to a user within a guild:
|
||||
// - every public channel of the guild (incl. ones created before the user
|
||||
// joined the guild), OR
|
||||
// - a non-public channel the user is an explicit member of.
|
||||
async listForUser(guildId: string, userId: string) {
|
||||
const all = await this.channelRepo.find({
|
||||
where: guildId ? { guildId } : {},
|
||||
order: { createdAt: 'ASC' },
|
||||
take: 200,
|
||||
take: 500,
|
||||
});
|
||||
if (!all.length) return [];
|
||||
|
||||
const memberRows = await this.memberRepo.find({
|
||||
where: { userId, channelId: In(all.map((c) => c.id)) },
|
||||
});
|
||||
const memberChannelIds = new Set(memberRows.map((m) => m.channelId));
|
||||
|
||||
return all.filter((c) => c.isPublic || memberChannelIds.has(c.id));
|
||||
}
|
||||
|
||||
listAll() {
|
||||
return this.channelRepo.find({
|
||||
order: { createdAt: 'ASC' },
|
||||
take: 200,
|
||||
});
|
||||
}
|
||||
|
||||
create(input: Partial<Channel>) {
|
||||
async create(input: CreateChannelInput, creatorUserId: string) {
|
||||
const guildId = String(input.guildId ?? '').trim();
|
||||
const name = String(input.name ?? '').trim();
|
||||
if (!guildId) throw new BadRequestException('guildId is required');
|
||||
if (!name) throw new BadRequestException('name is required');
|
||||
if (!creatorUserId) throw new BadRequestException('creator is required');
|
||||
|
||||
const channel = this.channelRepo.create({
|
||||
const channel = await this.channelRepo.save(
|
||||
this.channelRepo.create({
|
||||
guildId,
|
||||
name,
|
||||
kind: input.kind === 'announcement' ? 'announcement' : 'text',
|
||||
isPrivate: Boolean(input.isPrivate),
|
||||
isPrivate: !input.isPublic,
|
||||
isPublic: Boolean(input.isPublic),
|
||||
lastSeq: 0,
|
||||
});
|
||||
return this.channelRepo.save(channel);
|
||||
}),
|
||||
);
|
||||
|
||||
// creator is always a member; merge in any explicitly selected members
|
||||
const memberIds = new Set<string>([creatorUserId]);
|
||||
for (const id of input.memberUserIds ?? []) {
|
||||
const trimmed = String(id ?? '').trim();
|
||||
if (trimmed) memberIds.add(trimmed);
|
||||
}
|
||||
await this.memberRepo.save(
|
||||
[...memberIds].map((userId) => this.memberRepo.create({ channelId: channel.id, userId })),
|
||||
);
|
||||
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
import { Guild } from './entities/guild.entity';
|
||||
import { Channel } from './entities/channel.entity';
|
||||
import { ChannelMember } from './entities/channel-member.entity';
|
||||
import { Message } from './entities/message.entity';
|
||||
import { DmConversation } from './entities/dm-conversation.entity';
|
||||
import { DmParticipant } from './entities/dm-participant.entity';
|
||||
@@ -19,6 +20,7 @@ export const buildTypeOrmConfig = (): TypeOrmModuleOptions => ({
|
||||
entities: [
|
||||
Guild,
|
||||
Channel,
|
||||
ChannelMember,
|
||||
Message,
|
||||
DmConversation,
|
||||
DmParticipant,
|
||||
|
||||
19
src/entities/channel-member.entity.ts
Normal file
19
src/entities/channel-member.entity.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('channel_members')
|
||||
@Index(['channelId', 'userId'], { unique: true })
|
||||
@Index(['userId'])
|
||||
export class ChannelMember {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: 'char', length: 36 })
|
||||
channelId!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 64 })
|
||||
userId!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
}
|
||||
@@ -19,6 +19,11 @@ export class Channel {
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isPrivate!: boolean;
|
||||
|
||||
// public channels are visible to every guild member (including those who
|
||||
// join after the channel was created); default off (unchecked)
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isPublic!: boolean;
|
||||
|
||||
@Index()
|
||||
@Column({ default: 0 })
|
||||
lastSeq!: number;
|
||||
|
||||
Reference in New Issue
Block a user