Skip to content

Commit

Permalink
Feat/user friends (#92)
Browse files Browse the repository at this point in the history
* feat: start user friends feature

* chore: add uploads folder into gitignore

* feat: create simple friend request functional

* feat: add option to add,remove and list friends on front
  • Loading branch information
iaurg authored Nov 11, 2023
1 parent 9b30c3a commit 0c5b385
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 145 deletions.
4 changes: 3 additions & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ lerna-debug.log*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/extensions.json

uploads
32 changes: 15 additions & 17 deletions backend/src/database/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ enum UserStatus {
BUSY
}

enum FriendshipStatus {
PENDING
ACCEPTED
DECLINED
BLOCKED
}

model User {
id String @id @default(uuid())
login String @unique
Expand All @@ -23,7 +30,6 @@ model User {
avatar String?
status UserStatus @default(ONLINE)
victory Int @default(0)
friends Friend[] @relation("Friendship")
refreshToken String?
mfaEnabled Boolean @default(false)
mfaSecret String?
Expand All @@ -33,35 +39,27 @@ model User {
messages Message[]
winner MatchHistory[] @relation(name: "winner")
loser MatchHistory[] @relation(name: "loser")
friends User[] @relation("UserFriends")
// to satisfy prisma's requirements; we won't access it directly.
symmetricFriends User[] @relation("UserFriends")
}


model MatchHistory {
id Int @id @default(autoincrement())
id String @id @default(uuid())
winner User @relation(fields: [winnerId], references: [id], name: "winner")
winnerId String
winnerId String
winnerPoints Int
loser User @relation(fields: [loserId], references: [id], name: "loser")
loserId String
loserId String
loserPoints Int
createdAt DateTime @default(now())
}

model Friend {
id String @id @default(uuid())
status FriendshipStatus
users User[] @relation("Friendship")
}

enum FriendshipStatus {
PENDING
ACCEPTED
DECLINED
BLOCKED
}

model Message {
id String @id @default(uuid())
user User? @relation(fields: [userLogin], references: [login])
Expand Down
53 changes: 41 additions & 12 deletions backend/src/database/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,55 @@ import { faker } from '@faker-js/faker';

const prisma = new PrismaClient();

/* User Schema
/* Schemas
enum UserStatus {
ONLINE
OFFLINE
AWAY
BUSY
}
enum FriendshipStatus {
PENDING
ACCEPTED
DECLINED
BLOCKED
}
model User {
id String @id @default(uuid())
login String @unique
id String @id @default(uuid())
login String @unique
displayName String
email String
avatar String?
status UserStatus @default(ONLINE)
victory Int @default(0)
friends Friend[] @relation("Friendship")
status UserStatus @default(ONLINE)
victory Int @default(0)
refreshToken String?
mfaEnabled Boolean @default(false)
mfaEnabled Boolean @default(false)
mfaSecret String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
chats ChatMember[]
messages Message[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
chats ChatMember[]
messages Message[]
winner MatchHistory[] @relation(name: "winner")
loser MatchHistory[] @relation(name: "loser")
friends Friendship[] @relation(name: "user")
friendOf Friendship[] @relation(name: "friend")
}
model Friendship {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], name: "user")
userId String
friend User @relation(fields: [friendId], references: [id], name: "friend")
friendId String
status FriendshipStatus @default(PENDING)
@@unique([userId, friendId], name: "userId_friendId")
}
MatchHistory Schema
model MatchHistory {
id Int @id @default(autoincrement())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { IsString } from 'class-validator';
export class CreateFriendDto {
@IsString()
userId: string;

export class CreateFriendDto {
@IsString()
friendId: string;
friend_id: string;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { IsString } from 'class-validator';
export class DeleteFriendDto {
@IsString()
userId: string;

export class DeleteFriendDto {
@IsString()
friendId: string;
friend_id: string;
}
12 changes: 0 additions & 12 deletions backend/src/friends/dto/updateFriend.dto.ts

This file was deleted.

47 changes: 33 additions & 14 deletions backend/src/friends/friends.controller.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
import { Body, Controller, Delete, Get, Post, Put } from '@nestjs/common';
import { UpdateFriendDto } from './dto/updateFriend.dto';
import {
Body,
Controller,
Delete,
Get,
Post,
Put,

Check warning on line 7 in backend/src/friends/friends.controller.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

'Put' is defined but never used
Req,
UseGuards,
} from '@nestjs/common';
import { FriendsService } from './friends.service';
import { CreateFriendDto } from './dto/createFriend.dto';
import { DeleteFriendDto } from './dto/deleteFriend.dto';
import { AccessTokenGuard } from 'src/auth/jwt/jwt.guard';
import { User } from '@prisma/client';
import { create } from 'domain';

Check warning on line 14 in backend/src/friends/friends.controller.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

'create' is defined but never used
import { CreateFriendDto } from './dto/createFriendDto';
import { DeleteFriendDto } from './dto/deleteFriendDto';

@Controller('friends')
@UseGuards(AccessTokenGuard)
export class FriendsController {
constructor(private friendsService: FriendsService) {}

@Post()
async createFriend(@Body() createFriendDto: CreateFriendDto) {
return this.friendsService.createFriend(createFriendDto);
async createFriend(
@Req() request: Request & { user: User },
@Body() createFriendDto: CreateFriendDto,
) {
const { id } = request.user;

return this.friendsService.createFriend(id, createFriendDto);
}

@Get()
async getFriends(@Body('userId') userId) {
return this.friendsService.getFriends(userId);
}
async getFriends(@Req() request: Request & { user: User }) {
const { id: userId } = request.user;

@Put()
async updateFriendStatus(@Body() updateFriendDto: UpdateFriendDto) {
return this.friendsService.updateFriendStatus(updateFriendDto);
return this.friendsService.getFriends(userId);
}

@Delete()
async deleteFriend(@Body() deleteFriendDto: DeleteFriendDto) {
return this.friendsService.deleteFriend(deleteFriendDto);
async deleteFriend(
@Req() request: Request & { user: User },
@Body() deleteFriendDto: DeleteFriendDto,
) {
const { id: userId } = request.user;

return this.friendsService.deleteFriend(userId, deleteFriendDto);
}
}
133 changes: 58 additions & 75 deletions backend/src/friends/friends.service.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,81 @@
import { Injectable } from '@nestjs/common';
import {
Injectable,
NotAcceptableException,
NotFoundException,
} from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreateFriendDto } from './dto/createFriend.dto';
import { UpdateFriendDto } from './dto/updateFriend.dto';
import { DeleteFriendDto } from './dto/deleteFriend.dto';
import { CreateFriendDto } from './dto/createFriendDto';
import { DeleteFriendDto } from './dto/deleteFriendDto';

@Injectable()
export class FriendsService {
constructor(private prismaService: PrismaService) {}
constructor(private prisma: PrismaService) {}

async findRelationship(user1Id, user2Id) {
return await this.prismaService.friend.findFirst({
where: {
AND: [
{ users: { some: { id: user1Id } } },
{ users: { some: { id: user2Id } } },
],
},
async createFriend(userId: string, createFriendDto: CreateFriendDto) {
const friendship = await this.prisma.user.findUnique({
where: { id: userId },
select: { friends: { where: { id: createFriendDto.friend_id } } },
});
}

async createFriend(createFriendDto: CreateFriendDto) {
const existingFriendship = await this.findRelationship(
createFriendDto.userId,
createFriendDto.friendId,
);
if (friendship.friends.length > 0) {
throw new NotAcceptableException('Friendship already exists');
}

const friendUser = await this.prisma.user.findUnique({
where: { id: createFriendDto.friend_id },
});

if (existingFriendship) {
return existingFriendship;
if (!friendUser) {
throw new NotFoundException('Friend not found');
}
const friendRequest = await this.prismaService.friend.create({
data: {
status: 'PENDING',
users: {
connect: [
{ id: createFriendDto.userId },
{ id: createFriendDto.friendId },
],
},
},

await this.prisma.user.update({
where: { id: userId },
data: { friends: { connect: [{ id: createFriendDto.friend_id }] } },
});

await this.prisma.user.update({
where: { id: createFriendDto.friend_id },
data: { friends: { connect: [{ id: userId }] } },
});
return friendRequest;

return {
message: 'Friend added',
friend: friendUser,
};
}

async getFriends(userId: string) {
const friendships = await this.prismaService.friend.findMany({
where: {
users: {
some: {
id: userId,
},
},
status: 'ACCEPTED',
},
select: {
users: {
where: {
id: {
not: userId
}
}
}
},
const friends = await this.prisma.user.findUnique({
where: { id: userId },
select: { friends: true },
});

return friendships;
return friends;
}

async updateFriendStatus(updateFriendDto: UpdateFriendDto) {
const relationShip = await this.findRelationship(
updateFriendDto.friendId,
updateFriendDto.userId,
);
return await this.prismaService.friend.update({
where: {
id: relationShip.id,
},
data: {
status: updateFriendDto.friendshipStatus,
},
async deleteFriend(userId: string, deleteFriendDto: DeleteFriendDto) {
const friendship = await this.prisma.user.findUnique({
where: { id: userId },
select: { friends: { where: { id: deleteFriendDto.friend_id } } },
});
}

async deleteFriend(deleteFriendDto: DeleteFriendDto) {
const relationShip = await this.findRelationship(
deleteFriendDto.friendId,
deleteFriendDto.userId,
);
if (!relationShip) {
return relationShip;
if (friendship.friends.length === 0) {
throw new NotFoundException('Friendship not found');
}
return await this.prismaService.friend.delete({
where: {
id: relationShip.id,
},

await this.prisma.user.update({
where: { id: userId },
data: { friends: { disconnect: [{ id: deleteFriendDto.friend_id }] } },
});

await this.prisma.user.update({
where: { id: deleteFriendDto.friend_id },
data: { friends: { disconnect: [{ id: userId }] } },
});

return {
message: 'Friend deleted',
};
}
}
Loading

0 comments on commit 0c5b385

Please sign in to comment.