Skip to content

Commit

Permalink
add: last message content and time in list rooms, author avatar in ge… (
Browse files Browse the repository at this point in the history
  • Loading branch information
sedeve authored Oct 30, 2023
2 parents 75212df + 017cd3f commit 301dbe4
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 38 deletions.
4 changes: 4 additions & 0 deletions backend/code/prisma/dbml/schema.dbml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Table users {
roomMember room_members [not null]
blocked_by blocked_friends [not null]
blocked blocked_friends [not null]
Message messages [not null]
}

Table friends {
Expand Down Expand Up @@ -75,6 +76,7 @@ Table messages {
id String [pk]
authorId String [not null]
room rooms [not null]
author users [not null]
roomId String [not null]
content String
}
Expand Down Expand Up @@ -145,6 +147,8 @@ Ref: matches.participant2Id > users.userId

Ref: messages.roomId > rooms.id [delete: Cascade]

Ref: messages.authorId > users.userId

Ref: rooms.ownerId > users.userId

Ref: room_members.userId > users.userId
Expand Down
52 changes: 52 additions & 0 deletions backend/code/prisma/migrations/20231030183113_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Warnings:
- A unique constraint covering the columns `[createdAt]` on the table `messages` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[Username]` on the table `users` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateEnum
CREATE TYPE "NotifType" AS ENUM ('addFriend');

-- DropForeignKey
ALTER TABLE "messages" DROP CONSTRAINT "messages_roomId_fkey";

-- AlterTable
ALTER TABLE "blocked_friends" ADD COLUMN "dmRoomId" TEXT;

-- AlterTable
ALTER TABLE "room_members" ADD COLUMN "bannedAt" TIMESTAMP(3);

-- AlterTable
ALTER TABLE "users" ADD COLUMN "tfaSecret" TEXT,
ADD COLUMN "tfaStatus" BOOLEAN NOT NULL DEFAULT false;

-- CreateTable
CREATE TABLE "notifications" (
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"id" TEXT NOT NULL,
"recipientId" TEXT NOT NULL,
"content" "NotifType" NOT NULL,
"is_read" BOOLEAN NOT NULL DEFAULT false,
"readAt" TIMESTAMP(3),

CONSTRAINT "notifications_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "notifications_createdAt_key" ON "notifications"("createdAt");

-- CreateIndex
CREATE UNIQUE INDEX "messages_createdAt_key" ON "messages"("createdAt");

-- CreateIndex
CREATE UNIQUE INDEX "users_Username_key" ON "users"("Username");

-- AddForeignKey
ALTER TABLE "matches" ADD CONSTRAINT "matches_participant2Id_fkey" FOREIGN KEY ("participant2Id") REFERENCES "users"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "messages" ADD CONSTRAINT "messages_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "rooms"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "messages" ADD CONSTRAINT "messages_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;
29 changes: 15 additions & 14 deletions backend/code/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ model User {
intraId String? @unique
profileFinished Boolean @default(false)
Username String? @unique
Username String? @unique
firstName String?
lastName String?
discreption String @default("")
discreption String @default("")
avatar String?
tfaEnabled Boolean @default(false)
tfaSecret String?
Expand All @@ -41,6 +41,7 @@ model User {
roomMember RoomMember[]
blocked_by BlockedUsers[] @relation("blocked_by")
blocked BlockedUsers[] @relation("blocked")
Message Message[]
@@map("users")
}
Expand All @@ -64,12 +65,12 @@ model BlockedUsers {
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
id String @id
Blcoked_by User @relation("blocked_by", fields: [blocked_by_id], references: [userId])
blocked_by_id String @unique
Blocked User @relation("blocked", fields: [blocked_id], references: [userId])
blocked_id String @unique
dmRoomId String?
id String @id
Blcoked_by User @relation("blocked_by", fields: [blocked_by_id], references: [userId])
blocked_by_id String @unique
Blocked User @relation("blocked", fields: [blocked_id], references: [userId])
blocked_id String @unique
dmRoomId String?
@@map("blocked_friends")
}
Expand Down Expand Up @@ -98,12 +99,12 @@ model Message {
id String @id @default(cuid())
authorId String
room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [userId])
roomId String
content String?
@@map("messages")
@@unique([createdAt])
@@map("messages")
}

model Room {
Expand Down Expand Up @@ -142,16 +143,16 @@ model RoomMember {
}

model Notification {
createdAt DateTime @default(now())
createdAt DateTime @default(now())
id String @id @default(cuid())
id String @id @default(cuid())
recipientId String
content NotifType
is_read Boolean @default(false)
is_read Boolean @default(false)
readAt DateTime?
@@map("notifications")
@@unique([createdAt])
@@map("notifications")
}

enum NotifType {
Expand Down
30 changes: 27 additions & 3 deletions backend/code/src/friends/friends.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PrismaService } from 'src/prisma/prisma.service';
import { UsersService } from 'src/users/users.service';
import { FriendResponseDto } from './dto/frined-response.dto';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PICTURE } from 'src/profile/dto/profile.dto';

@Injectable()
export class FriendsService {
Expand Down Expand Up @@ -208,22 +209,45 @@ export class FriendsService {
userId: true,
firstName: true,
lastName: true,
avatar: true,
},
},
to: {
select: {
userId: true,
firstName: true,
lastName: true,
avatar: true,
},
},
},
});
return friends.map((friend) => {

return friends.map((friend: any) => {
if (friend.from.userId === userId) {
return friend.to;
friend = friend.to as any;
const avatar: PICTURE = {
thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${friend.avatar}`,
medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${friend.avatar}`,
large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${friend.avatar}`,
};
delete friend.avatar;
return {
...friend.to,
avatar,
};
} else {
return friend.from;
friend = friend.from as any;
const avatar: PICTURE = {
thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${friend.avatar}`,
medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${friend.avatar}`,
large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${friend.avatar}`,
};
delete friend.avatar;
return {
...friend.from,
avatar,
};
}
});
}
Expand Down
7 changes: 1 addition & 6 deletions backend/code/src/gateways/gateways.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,10 @@ export class Gateways implements OnGatewayConnection {
}

@SubscribeMessage('startGame')
handleGameStartEvent(client: any) {
handleGameStartEvent(client: Socket) {
this.eventEmitter.emit('game.start', client);
}

@SubscribeMessage('movePaddle')
handleMovePaddleEvent(client: any, data: any) {
this.server.to(data.channel).emit('movePaddle', data);
}

@OnEvent('game.launched')
handleGameLaunchedEvent(clients: any) {
const game_channel = `Game:${clients[0].id}:${clients[1].id}`;
Expand Down
13 changes: 11 additions & 2 deletions backend/code/src/messages/dto/message-format.dto.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { Message } from '@prisma/client';
import { Message, User } from '@prisma/client';
import { PICTURE } from 'src/profile/dto/profile.dto';

export class MessageFormatDto {
constructor(messageData: Message) {
constructor(messageData: Message & { author: Partial<User> }) {
this.id = messageData.id;
this.content = messageData.content;
this.time = messageData.createdAt;
this.roomId = messageData.roomId;
this.authorId = messageData.authorId;
this.avatar = {
thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${messageData.author.avatar}`,
medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${messageData.author.avatar}`,
large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${messageData.author.avatar}`,
};
}

@ApiProperty({ example: 'clnx16e7a00003b6moh6yipir' })
Expand All @@ -20,4 +26,7 @@ export class MessageFormatDto {
roomId: string;
@ApiProperty({ example: 'clnx18i8x00003b6lrp84ufb3' })
authorId: string;

@ApiProperty({ example: 'clnx18i8x00003b6lrp84ufb3' })
avatar: PICTURE;
}
1 change: 1 addition & 0 deletions backend/code/src/messages/messages.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class MessagesController {
return this.messagesService.sendMessages(userId, channelId, messageDto);
}

@ApiResponse({ type: MessageFormatDto, isArray: true })
@Get('room/:id')
@UseGuards(AtGuard)
getMessages(
Expand Down
14 changes: 14 additions & 0 deletions backend/code/src/messages/messages.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ export class MessagesService {
roomId: channelId,
authorId: userId,
},
include: {
author: {
select: {
avatar: true,
},
},
},
});

const responseMessage: MessageFormatDto = new MessageFormatDto(messageData);
Expand Down Expand Up @@ -117,6 +124,13 @@ export class MessagesService {
orderBy: {
createdAt: 'desc',
},
include: {
author: {
select: {
avatar: true,
},
},
},
skip: offset,
take: limit,
});
Expand Down
8 changes: 6 additions & 2 deletions backend/code/src/rooms/dto/create-room.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import {
IsOptional,
IsString,
Length,
ValidateIf,
} from 'class-validator';

export class CreateRoomDto {
@ApiProperty()
// @IgnoreName('type', { message: 'room name is not required on type dm' })
@ApiProperty({ description: 'name of the room required if type is not dm' })
@IsString()
@IsNotEmpty()
@ValidateIf((o) => o.type !== 'dm')
name: string;

@ApiProperty()
Expand All @@ -27,8 +30,9 @@ export class CreateRoomDto {
@Length(8, 32)
password?: string;

@ApiProperty({ description: 'second member of the room' })
@ValidateIf((o) => o.type === 'dm')
@IsNotEmpty()
@IsString()
@IsOptional()
secondMember: string;
}
61 changes: 50 additions & 11 deletions backend/code/src/rooms/rooms.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,10 @@ export class RoomsService {
skip: offset,
take: limit,
where: {
...(joined && { members: { some: { userId: userId } } }),
...(joined && {
members: { some: { userId: userId } },
NOT: { type: 'dm' },
}),
...(!joined && { OR: [{ type: 'public' }, { type: 'protected' }] }),
},
select: {
Expand All @@ -553,15 +556,51 @@ export class RoomsService {
},
});
if (!joined) return rooms;
return rooms.map((room) => {
const is_owner = room.ownerId === userId;
return {
id: room.id,
name: room.name,
type: room.type,
is_admin: room.members[0].is_admin,
is_owner,
};
});

type roomsData = {
is_admin: boolean;
id: string;
name: string;
type: string;
is_owner: boolean;
countMembers: number;
last_message: {
createdAt: Date;
content: string;
} | null;
};
const roomsData: roomsData[] = await Promise.all(
rooms.map(async (room) => {
const countMembers = await this.prisma.roomMember.count({
where: {
roomId: room.id,
},
});

const last_message = await this.prisma.message.findFirst({
where: {
roomId: room.id,
},
orderBy: {
createdAt: 'desc',
},
select: {
content: true,
createdAt: true,
},
});
const is_owner = room.ownerId === userId;
return {
id: room.id,
name: room.name,
type: room.type,
is_admin: room.members[0].is_admin,
is_owner,
countMembers,
last_message,
};
}),
);
return roomsData;
}
}

0 comments on commit 301dbe4

Please sign in to comment.