diff --git a/src/backend/auth-server/src/auth/auth.service.ts b/src/backend/auth-server/src/auth/auth.service.ts
index 2b97bb98..3ac4c448 100644
--- a/src/backend/auth-server/src/auth/auth.service.ts
+++ b/src/backend/auth-server/src/auth/auth.service.ts
@@ -196,7 +196,7 @@ export class AuthService {
const isPasswordMatch = await bcrypt.compare(password, user.password);
if (!isPasswordMatch) {
- throw new BadRequestException(MESSAGES.INVALID_LOGIN_INFO);
+ throw new UnauthorizedException(MESSAGES.INVALID_LOGIN_INFO);
}
const { password: _, ...withoutPassword } = user;
diff --git a/src/backend/user-server/src/user/user.controller.ts b/src/backend/user-server/src/user/user.controller.ts
index cba7705e..80b17c4d 100644
--- a/src/backend/user-server/src/user/user.controller.ts
+++ b/src/backend/user-server/src/user/user.controller.ts
@@ -102,8 +102,9 @@ export class UserController {
async findAll(
@Query("page", new DefaultValuePipe(0), ParseIntPipe) page: number = 0,
@Query("size", new DefaultValuePipe(10), ParseIntPipe) size: number = 10,
+ @Query("nickname") nickname?: string,
) {
- return this.userService.findAll(page, size);
+ return this.userService.findAll(page, size, nickname);
}
@Get(":id")
diff --git a/src/backend/user-server/src/user/user.service.ts b/src/backend/user-server/src/user/user.service.ts
index 5fbc93ca..135303c4 100644
--- a/src/backend/user-server/src/user/user.service.ts
+++ b/src/backend/user-server/src/user/user.service.ts
@@ -5,7 +5,7 @@ import {
NotFoundException,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
-import { Repository } from "typeorm";
+import { Like, Repository } from "typeorm";
import { User } from "./entity/user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
import * as bcrypt from "bcryptjs";
@@ -17,6 +17,7 @@ import { REDIS_KEY } from "./constants/redis-key.constant";
import { DeviceType } from "./enum/device-type.enum";
import { MESSAGES } from "./constants/constants";
import { ENV_KEY } from "./constants/env-key.constants";
+
@Injectable()
export class UserService {
private readonly redis: Redis;
@@ -68,15 +69,18 @@ export class UserService {
return this.userRepository.findOne({ where: { email } });
}
- // NOTE: pagination 필요할 경우 추가
- async findAll(page: number = 0, size: number = 10) {
+ async findAll(page: number = 0, size: number = 10, nickname?: string) {
+ const whereCondition = nickname ? { nickname: Like(`%${nickname}%`) } : {};
+
const [users, total] = await this.userRepository.findAndCount({
+ where: whereCondition,
skip: page * size,
take: size,
});
+
return {
users,
- total,
+ totalLength: total,
};
}
diff --git a/src/frontend/index.html b/src/frontend/index.html
index 57100265..5b9162f9 100644
--- a/src/frontend/index.html
+++ b/src/frontend/index.html
@@ -2,9 +2,9 @@
-
+
- Kicktube
+ KickTube
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 30aaad97..b608f992 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -18,6 +18,7 @@
"@types/sockjs-client": "^1.5.4",
"@types/stompjs": "^2.3.9",
"axios": "^1.7.9",
+ "lucide-react": "^0.475.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.1.3",
diff --git a/src/frontend/pnpm-lock.yaml b/src/frontend/pnpm-lock.yaml
index b3e047a4..0bea3881 100644
--- a/src/frontend/pnpm-lock.yaml
+++ b/src/frontend/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
axios:
specifier: ^1.7.9
version: 1.7.9
+ lucide-react:
+ specifier: ^0.475.0
+ version: 0.475.0(react@18.3.1)
react:
specifier: ^18.3.1
version: 18.3.1
@@ -1991,6 +1994,11 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ lucide-react@0.475.0:
+ resolution: {integrity: sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
lz-string@1.5.0:
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
hasBin: true
@@ -4711,6 +4719,10 @@ snapshots:
dependencies:
yallist: 3.1.1
+ lucide-react@0.475.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
lz-string@1.5.0: {}
magic-string@0.27.0:
diff --git a/src/frontend/public/favicon.svg b/src/frontend/public/favicon.svg
new file mode 100644
index 00000000..66b75fee
--- /dev/null
+++ b/src/frontend/public/favicon.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/frontend/src/api/endpoints/friend/friend.api.ts b/src/frontend/src/api/endpoints/friend/friend.api.ts
new file mode 100644
index 00000000..1d23074d
--- /dev/null
+++ b/src/frontend/src/api/endpoints/friend/friend.api.ts
@@ -0,0 +1,49 @@
+import instance from '@/api/axios.instance';
+import { FriendDto, NotificationDto } from './friend.interface';
+
+export const friendApi = {
+ // 친구 목록 제공
+ getFriends: async () => {
+ const { data } = await instance.get<{ friends: FriendDto[] }>('/friends/list');
+ return data.friends;
+ },
+
+ // 친구 요청
+ requestFriend: async (me: number, receiverId: number) => {
+ const { data } = await instance.post(`/friends/request`, {
+ senderId: me,
+ receiverId,
+ });
+ return data;
+ },
+
+ // 친구 요청 수락
+ acceptFriend: async (me: number, senderId: number) => {
+ const { data } = await instance.post(`/friends/accept`, {
+ senderId,
+ receiverId: me,
+ });
+ return data;
+ },
+
+ // 친구 요청 거절
+ rejectFriend: async (me: number, senderId: number) => {
+ const { data } = await instance.post(`/friends/reject`, {
+ senderId,
+ receiverId: me,
+ });
+ return data;
+ },
+
+ // 알림 정보
+ getNotifications: async () => {
+ const { data } = await instance.get<{ requests: NotificationDto[] }>('/friends/requests');
+ return data.requests;
+ },
+
+ // 안 읽은 알림 개수 제공
+ getUnreadNotificationsCount: async () => {
+ const { data } = await instance.get('/friends/unread');
+ return data;
+ },
+};
diff --git a/src/frontend/src/api/endpoints/friend/friend.interface.ts b/src/frontend/src/api/endpoints/friend/friend.interface.ts
new file mode 100644
index 00000000..efb891c8
--- /dev/null
+++ b/src/frontend/src/api/endpoints/friend/friend.interface.ts
@@ -0,0 +1,23 @@
+export interface FriendDto {
+ friend_id: number;
+ nickname: string;
+ profile_image_url?: string;
+ role: number;
+ status: string;
+}
+
+export interface NotificationDto {
+ isRead: boolean;
+ receiverId: number;
+ receiverNickname: string;
+ roomCode: string | null;
+ roomId: string | null;
+ senderId: number;
+ senderNickname: string;
+ status: NotificationStatus;
+ timestamp: number;
+ type: NotificationType;
+}
+
+export type NotificationStatus = 'PENDING' | 'ACCEPTED' | 'REJECTED';
+export type NotificationType = 'friend_request' | 'room_request';
diff --git a/src/frontend/src/api/endpoints/user/user.api.ts b/src/frontend/src/api/endpoints/user/user.api.ts
index 72707d0d..7664b72b 100644
--- a/src/frontend/src/api/endpoints/user/user.api.ts
+++ b/src/frontend/src/api/endpoints/user/user.api.ts
@@ -15,8 +15,11 @@ export const userApi = {
},
// 전체 유저 조회
- getUsers: async (page: number = 0, size: number = 10) => {
- const { data } = await instance.get(`users`, { params: { page, size } });
+ getUsers: async (page: number = 0, size: number = 10, nickname?: string) => {
+ const { data } = await instance.get<{ users: UserResponseDto[]; totalLength: number }>(
+ `users`,
+ { params: { page, size, nickname } },
+ );
return data;
},
diff --git a/src/frontend/src/assets/img/AddUser.svg b/src/frontend/src/assets/img/AddUser.svg
index 4887f7d0..a5de72ba 100644
--- a/src/frontend/src/assets/img/AddUser.svg
+++ b/src/frontend/src/assets/img/AddUser.svg
@@ -1,10 +1,3 @@
-