diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 5999cde..5c9ba92 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,4 +1,3 @@ - import { Body, Controller, Post, HttpCode, HttpStatus, Request , UseGuards, Put, Param, Get} from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthDto, CheckDto, SetNicknameDto } from './dto/auth.dto'; @@ -56,4 +55,27 @@ export class AuthController { const email = req.user.email; return this.authService.getNicknameByEmail(email); } + + @UseGuards(AuthGuard('jwt')) + @ApiOperation({ summary: '친구 요청' }) + @Post('request') + async sendFriendRequest(@Request() req, @Body('nickname') nickname: string) { + const email = req.user.email; + return await this.authService.sendFriendRequest(email, nickname); + } + + @UseGuards(AuthGuard('jwt')) + @Post('accept') + async acceptFriendRequest(@Request() req) { + const user_email = req.user.email; + return await this.authService.acceptFriendRequest(user_email); + } + + @UseGuards(AuthGuard('jwt')) + @Post('reject') + async rejectFriendRequest(@Request() req) { + const user_email = req.user.email; + return await this.authService.rejectFriendRequest(user_email); + } + } \ No newline at end of file diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 1949cf2..001797f 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -11,7 +11,7 @@ import { APP_GUARD, NestFactory } from '@nestjs/core'; import { AuthGuard } from './auth.guard'; import { PassportModule } from '@nestjs/passport'; import { JwtStrategy } from 'src/jwt.strategy'; - +import { FriendshipSchema, FriendSummarySchema } from './schemas/auth.friend.schema'; @Module({ @@ -23,7 +23,11 @@ import { JwtStrategy } from 'src/jwt.strategy'; signOptions: { expiresIn: process.env.JWT_EXPIRES }, }), }), - MongooseModule.forFeature([{ name: 'Auth', schema: AuthSchema }]), + MongooseModule.forFeature([ + { name: 'Auth', schema: AuthSchema }, + { name: 'Friendship', schema: FriendshipSchema }, + { name: 'FriendSummary', schema: FriendSummarySchema }, + ]), ], providers: [AuthService, { provide: APP_GUARD, diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 3386d9c..fc74b0f 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,6 +1,7 @@ import { ObjectId } from 'mongoose'; +import { FriendSummary, Friendship, FriendshipSchema } from './schemas/auth.friend.schema'; import { AuthDto } from './dto/auth.dto'; -import { Injectable, UnauthorizedException, BadRequestException,ConflictException, HttpException, HttpStatus } from '@nestjs/common'; +import { Injectable, UnauthorizedException, BadRequestException,ConflictException, HttpException, HttpStatus, NotFoundException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { InjectModel } from '@nestjs/mongoose'; import { Auth, AuthSchema} from './schemas/auth.schema'; @@ -11,7 +12,9 @@ import * as bcrypt from 'bcrypt'; export class AuthService { usersService: any; constructor( + @InjectModel(Friendship.name) private friendshipModel: Model, @InjectModel(Auth.name) private readonly authModel: Model, + @InjectModel(FriendSummary.name) private friendSummaryModel: Model, private jwtService: JwtService ) {} @@ -139,4 +142,146 @@ export class AuthService { const user = await this.authModel.findOne({ _id: userid }); return user.socketid; } + + async getUserByNickname(nickname: string) { + const user = await this.authModel.findOne({ nickname: nickname }); + if (!user) { + throw new NotFoundException('User not found'); + } + return user; + } + + async sendFriendRequest(senderEmail: string, receiverNickname: string) { + const sender = await this.authModel.findOne({ email: senderEmail }); + const receiver = await this.authModel.findOne({ nickname: receiverNickname }); + + if (!sender || !receiver) { + throw new Error('Sender or receiver not found.'); + } + + // Create a new friendship request and save it + const friendRequest = new this.friendshipModel({ + user: sender._id, + friend: receiver._id, + isRequest: true, + isConfirmed: false, + nickname: sender.nickname, + level: sender.level, + online: sender.online, + }); + + + const savedFriendRequest = await friendRequest.save(); + + const friendRequestForReceiver = new this.friendshipModel({ + nickname: sender.nickname, + }); + + receiver.friendRequests.push(sender.nickname); + await receiver.save(); + + return {message: "친구 요청 성공!"}; + } + +async acceptFriendRequest(userEmail: string) { + const user = await this.authModel.findOne({ email: userEmail }); + + if (!user) { + throw new NotFoundException('유저를 찾을 수 없습니다.'); + } + const friendFriendRequest = await this.friendshipModel.findOne({ + friend: user._id, + isRequest: true, + }); + + if (!friendFriendRequest) { + throw new BadRequestException('친구 요청을 찾을 수 없습니다.'); + } + + // Accept the friend request and update it in the database + friendFriendRequest.isRequest = false; + friendFriendRequest.isConfirmed = true; + await friendFriendRequest.save(); + + const friend = new this.friendSummaryModel({ + user: friendFriendRequest.friend, + nickname: friendFriendRequest.nickname, + online: friendFriendRequest.online, + level: friendFriendRequest.level, + }); + user.friends.push(friend); + await user.save(); + + const friendUser = await this.authModel.findOne({ _id: friendFriendRequest.user }); + + const userSummary = new this.friendSummaryModel({ + user: user._id, + nickname: user.nickname, + online: user.online, + level: user.level, + }); + + friendUser.friends.push(userSummary); + await friendUser.save(); + user.friendRequests = user.friendRequests.filter(request => request !== friendFriendRequest.nickname); + await user.save(); + + return { + message: `${user.nickname}님과 ${friendFriendRequest.nickname}님이 친구가 되었습니다.` + }; + } + + + +async rejectFriendRequest(userEmail: string) { + const user = await this.authModel.findOne({ email: userEmail }); + + if (!user) { + throw new NotFoundException('User not found.'); + } + + const friendFriendRequest = await this.friendshipModel.findOne({ + friend: user._id, + isRequest: true, + }); + + if (!friendFriendRequest) { + throw new BadRequestException('Friend request not found.'); + } + + //데이터 베이스에서 request를 찾아서 삭제 + await this.friendshipModel.deleteOne({ _id: friendFriendRequest._id }); + user.friendRequests = user.friendRequests.filter(request => request !== friendFriendRequest.nickname); + await user.save(); + + //친구수락 거절 메세지를 리턴 + return { message: '친구 요청 거절' }; + } + + async getFriendList(userEmail: string) { + const user = await this.authModel.findOne({ email: userEmail }); + + if (!user) { + throw new Error('User not found.'); + } + + const friendList = user.friends.map(friend => { + return { + nickname: friend.nickname, + online: friend.online, + level: friend.level + }; + }); + + const friendRequests = user.friendRequests.map(request => { + return { + nickname: request + }; + }); + + return { + friendlist: friendList, + friendRequests: friendRequests + }; +} } diff --git a/src/auth/schemas/auth.friend.schema.ts b/src/auth/schemas/auth.friend.schema.ts new file mode 100644 index 0000000..9dc4815 --- /dev/null +++ b/src/auth/schemas/auth.friend.schema.ts @@ -0,0 +1,46 @@ +import { Schema, Prop, SchemaFactory } from "@nestjs/mongoose" +import { Document } from "mongoose"; + + + +@Schema() +export class Friendship extends Document { + @Prop({ ref: 'Auth', required: true }) + user: string; // Reference to the user's ObjectId + + @Prop({ ref: 'Auth', required: true }) + friend: string; + + @Prop({ default: false }) + isRequest: boolean; // true 라면 친구 수락전, false 라면 친구 수락 이후 + + @Prop({ default: false }) + isConfirmed: boolean; // true 라면 친구 수락 상태 , false 라면 아직 친구 수락 대기상태 + + @Prop() + nickname: string; // 친구의 닉네임 + + @Prop() + online: boolean; // 친구의 온라인 상태 + + @Prop() + level: number; // 친구의 레벨 +} + +@Schema() +export class FriendSummary extends Document { + @Prop({ ref: 'Auth', required: true }) + user: string; // 유저 ObjectID + + @Prop() + nickname: string; // 친구의 닉네임 + + @Prop() + online: boolean; // 친구의 온라인 상태 + + @Prop() + level: number; // 친구의 레벨 +} + +export const FriendshipSchema = SchemaFactory.createForClass(Friendship); +export const FriendSummarySchema = SchemaFactory.createForClass(FriendSummary); \ No newline at end of file diff --git a/src/auth/schemas/auth.schema.ts b/src/auth/schemas/auth.schema.ts index d42bdd4..5f94d3f 100644 --- a/src/auth/schemas/auth.schema.ts +++ b/src/auth/schemas/auth.schema.ts @@ -1,6 +1,7 @@ import { Schema, Prop, SchemaFactory } from "@nestjs/mongoose" import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; import { Document, SchemaOptions } from "mongoose"; +import { FriendSummary, FriendSummarySchema } from "./auth.friend.schema"; @Schema() export class Auth extends Document { @@ -38,6 +39,12 @@ export class Auth extends Document { @Prop({default : null}) socketid : string; + + @Prop({ type: [FriendSummarySchema] }) + friends: FriendSummary[]; + + @Prop({}) + friendRequests: string[]; } export const AuthSchema = SchemaFactory.createForClass(Auth); \ No newline at end of file diff --git a/src/gateway/gateway.module.ts b/src/gateway/gateway.module.ts index 5a180b2..32942db 100644 --- a/src/gateway/gateway.module.ts +++ b/src/gateway/gateway.module.ts @@ -5,6 +5,7 @@ import { RoomModule } from 'src/room/room.module'; import { UsersModule } from 'src/users/users.module'; import { PassportModule } from '@nestjs/passport'; import { CodingtestModule } from 'src/codingtest/codingtest.module'; + @Module({ imports: [UsersModule, RoomModule, CodingtestModule, AuthModule], providers : [AppGateway], diff --git a/src/gateway/gateway.ts b/src/gateway/gateway.ts index 896728a..53c9851 100644 --- a/src/gateway/gateway.ts +++ b/src/gateway/gateway.ts @@ -23,6 +23,7 @@ import { userInfo } from 'os'; + @ApiTags('Room') @UseGuards(jwtSocketIoMiddleware) @WebSocketGateway({cors : true, namespace: 'room'}) @@ -317,6 +318,14 @@ export class AppGateway implements OnGatewayConnection, OnGatewayDisconnect{ } + @SubscribeMessage('friendlist') + async handleFriendList(@ConnectedSocket() socket: ExtendedSocket) { + const friendList = await this.authService.getFriendList(socket.decoded.email); + socket.emit('friendlist', { success: true, payload: friendList }); + } + + + @SubscribeMessage('timer') async handleTimer( @MessageBody('title') title: string, @@ -341,4 +350,5 @@ export class AppGateway implements OnGatewayConnection, OnGatewayDisconnect{ }, 1000); } + }