Skip to content

Commit

Permalink
feat: set join policy command
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrunton committed Aug 17, 2024
1 parent a5e26e8 commit 93ed307
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 14 deletions.
1 change: 1 addition & 0 deletions client/src/data/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type Room = {
id: string
ownerId: string
name: string
joinPolicy: string
}

export type RoomResponse = {
Expand Down
32 changes: 23 additions & 9 deletions client/src/features/room/organisms/ChatBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,46 @@ import { Button, Icon, Textarea, Spinner, VStack, Alert, AlertIcon, Spacer } fro
import React, { useState, KeyboardEventHandler, useRef, useEffect } from 'react'
import { AiOutlineArrowRight } from 'react-icons/ai'
import { usePostMessage } from '../../../data/messages'
import { useJoinRoom } from '../../../data/rooms'
import { Room, RoomResponse, useJoinRoom } from '../../../data/rooms'
import { useUserDetails } from '../../../data/users'
import { can } from '../../../data/lib'

export type ChatBoxProps = {
roomId: string
canJoin: boolean
}

const JoinAlert = ({ roomId }: ChatBoxProps) => {
const JoinAlert = ({ roomId, canJoin }: ChatBoxProps) => {
const { mutate: joinRoom, isLoading, isSuccess: isJoined } = useJoinRoom(roomId)

useEffect(() => {
if (isJoined) {
window.location.reload()
}
}, [isJoined])

if (canJoin) {
return (
<Alert status='info' variant='top-accent'>
<AlertIcon />
You need to join this room to chat.
<Spacer />
<Button rightIcon={isLoading ? <Spinner /> : undefined} onClick={() => joinRoom()}>
Join
</Button>
</Alert>
)
}

return (
<Alert status='info' variant='top-accent'>
<AlertIcon />
You need to join this room to chat.
<Spacer />
<Button rightIcon={isLoading ? <Spinner /> : undefined} onClick={() => joinRoom()}>
Join
</Button>
You need an invite to join.
</Alert>
)
}

export const ChatBox: React.FC<ChatBoxProps> = ({ roomId }: ChatBoxProps) => {
export const ChatBox: React.FC<ChatBoxProps> = ({ roomId, canJoin }: ChatBoxProps) => {
const [content, setContent] = useState<string>('')
const { data: user, isLoading } = useUserDetails()
const joined = user?.rooms.some((room) => room.id === roomId)
Expand All @@ -54,8 +67,9 @@ export const ChatBox: React.FC<ChatBoxProps> = ({ roomId }: ChatBoxProps) => {
}
}

console.info({ joined })
if (!isLoading && !joined) {
return <JoinAlert roomId={roomId} />
return <JoinAlert roomId={roomId} canJoin={canJoin} />
}

return (
Expand Down
7 changes: 4 additions & 3 deletions client/src/features/room/pages/Room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ type Params = {

export const RoomPage = () => {
const { roomId } = useParams() as Params
const { data: roomResponse, isLoading: isLoadingRoom } = useRoom(roomId)
const { data: roomResponse } = useRoom(roomId)

const canRead = can('read', roomResponse)
const canJoin = can('join', roomResponse)

const { data: messages, isLoading: isLoadingMessages } = useMessages(roomId, { enabled: canRead })
useMessagesSubscription(roomId, { enabled: canRead })

if ((canRead && isLoadingMessages) || isLoadingRoom) return <LoadingIndicator />
if ((canRead && isLoadingMessages) || !roomResponse) return <LoadingIndicator />

return (
<Box display='flex' flexFlow='column' height='100%' flex='1'>
{<MessagesList messages={messages ?? [restrictedMessage(roomId)]} />}
<ChatBox roomId={roomId} />
<ChatBox roomId={roomId} canJoin={canJoin} />
</Box>
)
}
Expand Down
8 changes: 8 additions & 0 deletions services/api/src/app/messages/command.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RenameUserUseCase } from '@usecases/users/rename';
import { Dispatcher } from '@entities/messages';
import { LoremCommandUseCase } from '@usecases/commands/lorem';
import { ParseCommandUseCase } from '@usecases/commands/parse';
import { ChangeRoomJoinPolicyUseCase } from '@usecases/rooms/change-room-join-policy';

@Injectable()
export class CommandService {
Expand All @@ -16,6 +17,7 @@ export class CommandService {
private readonly lorem: LoremCommandUseCase,
private readonly help: HelpCommandUseCase,
private readonly parse: ParseCommandUseCase,
private readonly changeRoomJoinPolicy: ChangeRoomJoinPolicyUseCase,
readonly dispatcher: Dispatcher,
) {}

Expand All @@ -35,6 +37,12 @@ export class CommandService {
case 'lorem':
await this.lorem.exec({ ...params, roomId, authenticatedUser });
break;
case 'changeRoomJoinPolicy':
await this.changeRoomJoinPolicy.exec({
...params,
roomId,
authenticatedUser,
});
}
}
}
2 changes: 2 additions & 0 deletions services/api/src/app/messages/messages.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { HelpCommandUseCase } from '@usecases/commands/help';
import { LoremCommandUseCase, LoremGenerator } from '@usecases/commands/lorem';
import { FakerLoremGenerator } from './faker.lorem.generator';
import { DispatcherModule } from '../dispatcher/dispatcher.module';
import { ChangeRoomJoinPolicyUseCase } from '@usecases/rooms/change-room-join-policy';

@Module({
imports: [AuthModule, DispatcherModule],
Expand All @@ -28,6 +29,7 @@ import { DispatcherModule } from '../dispatcher/dispatcher.module';
CommandService,
RenameRoomUseCase,
RenameUserUseCase,
ChangeRoomJoinPolicyUseCase,
LoremCommandUseCase,
HelpCommandUseCase,
],
Expand Down
4 changes: 3 additions & 1 deletion services/api/src/domain/entities/rooms.repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Room } from './room.entity';

export type CreateRoomParams = Omit<Room, 'id'>;
export type UpdateRoomParams = Partial<Pick<Room, 'name'>> & Pick<Room, 'id'>;
export type UpdateRoomParams = Partial<
Pick<Room, 'name' | 'id' | 'joinPolicy'>
>;

export abstract class RoomsRepository {
abstract createRoom(params: CreateRoomParams): Promise<Room>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@entities/command.entity';
import { JoinPolicy } from '@entities/room.entity';
import { BadRequestException } from '@nestjs/common';
import { equals } from 'rambda';
import { z, ZodIssue, ZodType } from 'zod';
Expand All @@ -7,6 +8,7 @@ export type ParsedCommand =
| { tag: 'help'; params: null }
| { tag: 'renameRoom'; params: { newName: string } }
| { tag: 'renameUser'; params: { newName: string } }
| { tag: 'changeRoomJoinPolicy'; params: { newJoinPolicy: JoinPolicy } }
| {
tag: 'lorem';
params: { count: number; typeToken: 'words' | 'paragraphs' };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from 'zod';
import { CommandParser, ParsedCommand } from '../command.parser';
import { JoinPolicy } from '@entities/room.entity';

const schema = z
.tuple([
z.literal('set'),
z.literal('room'),
z.literal('join'),
z.literal('policy'),
z.enum([JoinPolicy.Anyone, JoinPolicy.Invite]),
])
.rest(z.string())
.transform<ParsedCommand>(([, , , , joinPolicy]) => ({
tag: 'changeRoomJoinPolicy',
params: { newJoinPolicy: joinPolicy },
}));

export const changeRoomJoinPolicyParser = new CommandParser({
matchTokens: ['set', 'room', 'join', 'policy'],
schema,
signature: `/set room join policy {'anyone', 'invite'}`,
summary: 'set the room join policy',
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CommandParser } from '../command.parser';
import { changeRoomJoinPolicyParser } from './change-room-join-policy-parser';
import { helpParser } from './help.parser';
import { loremParser } from './lorem.parser';
import { renameRoomParser } from './rename.room.parser';
Expand All @@ -9,4 +10,5 @@ export const parsers: CommandParser[] = [
loremParser,
renameRoomParser,
renameUserParser,
changeRoomJoinPolicyParser,
];
57 changes: 57 additions & 0 deletions services/api/src/domain/usecases/rooms/change-room-join-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { MembershipsRepository } from '@entities/memberships.repository';
import { Dispatcher, DraftMessage, UpdatedEntity } from '@entities/messages';
import { JoinPolicy } from '@entities/room.entity';
import { RoomsRepository } from '@entities/rooms.repository';
import { User } from '@entities/users';
import { Injectable } from '@nestjs/common';
import { AuthService, Role } from '@usecases/auth.service';

export type ChangeRoomJoinPolicyParams = {
roomId: string;
authenticatedUser: User;
newJoinPolicy: JoinPolicy;
};

@Injectable()
export class ChangeRoomJoinPolicyUseCase {
constructor(
private readonly rooms: RoomsRepository,
private readonly memberships: MembershipsRepository,
private readonly authService: AuthService,
private readonly dispatcher: Dispatcher,
) {}

async exec({
roomId,
authenticatedUser,
newJoinPolicy,
}: ChangeRoomJoinPolicyParams): Promise<void> {
const room = await this.rooms.getRoom(roomId);

await this.authService.authorize({
user: authenticatedUser,
subject: room,
action: Role.Manage,
});

await this.rooms.updateRoom({
id: roomId,
joinPolicy: newJoinPolicy,
});

const message: DraftMessage = {
content: `Room join policy updated to ${newJoinPolicy}`,
roomId: room.id,
authorId: 'system',
updatedEntities: [UpdatedEntity.Room],
};

await this.dispatcher.send(message);
}
}

const titleCase = (s: string): string => {
const titleCaseWord = (word: string) =>
word[0].toUpperCase() + word.substring(1);
return s.split(' ').map(titleCaseWord).join(' ');
};
2 changes: 1 addition & 1 deletion services/api/src/domain/usecases/rooms/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class CreateRoomUseCase {
ownerId: owner.id,
name: titleCase(name),
contentPolicy: ContentPolicy.Private,
joinPolicy: JoinPolicy.Anyone,
joinPolicy: JoinPolicy.Invite,
});
await this.memberships.createMembership({
userId: owner.id,
Expand Down

0 comments on commit 93ed307

Please sign in to comment.