Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
Expand Down
28 changes: 27 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/entities/comment/api/addCommentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const addCommentApi = async (comment: { body: string; postId: number; userId: number }) => {
const res = await fetch(`/api/comments/add`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(comment),
});
return res.json();
};
5 changes: 5 additions & 0 deletions src/entities/comment/api/deleteCommentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const deleteCommentApi = async (commentId: number) => {
return fetch(`/api/comments/${commentId}`, {
method: "DELETE",
});
};
7 changes: 7 additions & 0 deletions src/entities/comment/api/fetchCommentsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Comment } from "../model/types.ts"

export const fetchCommentsApi = async (postId: number): Promise<Comment[]> => {
const res = await fetch(`/api/comments/post/${postId}`)
const data = await res.json()
return data.comments
}
8 changes: 8 additions & 0 deletions src/entities/comment/api/likeCommentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const likeCommentApi = async (commentId: number, newLikes: number) => {
const res = await fetch(`/api/comments/${commentId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ likes: newLikes }),
});
return res.json();
};
8 changes: 8 additions & 0 deletions src/entities/comment/api/updateCommentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const updateCommentApi = async (commentId: number, body: string) => {
const res = await fetch(`/api/comments/${commentId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body }),
});
return res.json();
};
34 changes: 34 additions & 0 deletions src/entities/comment/model/comments-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { create } from "zustand/react"
import { Comment } from "./types.ts"

interface NewComment {
body: string
postId: number | null
userId: number
}

interface CommentsState {
comments: Record<number, Comment[]>
selectedComment: Comment | null
newComment: NewComment

setComments: (postId: number, comments: Comment[]) => void
setSelectedComment: (comment: Comment | null) => void
setNewComment: (comment: { body: string; postId: number | null; userId: number }) => void
}

export const useCommentsStore = create<CommentsState>((set) => ({
comments: {},
selectedComment: null,
newComment: { body: "", postId: 1, userId: 1 },

setComments: (postId, comments) =>
set((state) => ({
comments: {
...state.comments,
[postId]: comments,
},
})),
setSelectedComment: (selectedComment) => set({ selectedComment }),
setNewComment: (newComment) => set({ newComment }),
}))
10 changes: 10 additions & 0 deletions src/entities/comment/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Comment {
id: number;
body: string;
likes: number;
postId: number;
user:{
id: number;
username: string;
};
}
8 changes: 8 additions & 0 deletions src/entities/post/api/addPostApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const addPostApi = async (post: { title: string; body: string; userId: number }) => {
const res = await fetch("/api/posts/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(post),
});
return res.json();
};
5 changes: 5 additions & 0 deletions src/entities/post/api/deletePostApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const deletePostApi = async (postId: number) => {
return fetch(`/api/posts/${postId}`, {
method: "DELETE",
});
};
16 changes: 16 additions & 0 deletions src/entities/post/api/fetchPostsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Post, User } from "../model/types.ts"

export const fetchPostsApi = async (skip: number, limit: number) => {
const postsRes = await fetch(`/api/posts?limit=${limit}&skip=${skip}`);
const postsData = await postsRes.json();

const usersRes = await fetch(`/api/users?limit=0&select=username,image`);
const usersData = await usersRes.json();

const postsWithUsers = postsData.posts.map((post: Post) => ({
...post,
author: usersData.users.find((user: User) => user.id === post.userId),
}));

return { posts: postsWithUsers, total: postsData.total };
};
16 changes: 16 additions & 0 deletions src/entities/post/api/fetchPostsByTagApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Post, User } from "../model/types.ts"

export const fetchPostsByTagApi = async (tag: string) => {
const postsRes = await fetch(`/api/posts/tag/${tag}`);
const postsData = await postsRes.json();

const usersRes = await fetch(`/api/users?limit=0&select=username,image`);
const usersData = await usersRes.json();

const postsWithUsers = postsData.posts.map((post: Post) => ({
...post,
author: usersData.users.find((user: User) => user.id === post.userId),
}));

return { posts: postsWithUsers, total: postsData.total };
};
5 changes: 5 additions & 0 deletions src/entities/post/api/searchPostsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const searchPostsApi = async (query: string) => {
const res = await fetch(`/api/posts/search?q=${query}`);
const data = await res.json();
return { posts: data.posts, total: data.total };
};
8 changes: 8 additions & 0 deletions src/entities/post/api/updatePostApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const updatePostApi = async (postId: number, post: { title: string; body: string }) => {
const res = await fetch(`/api/posts/${postId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(post),
});
return res.json();
};
51 changes: 51 additions & 0 deletions src/entities/post/model/posts-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { create } from "zustand/react"
import { Post } from "./types.ts"

interface PostsState {
posts: Post[]
total: number
skip: number
limit: number
searchQuery: string
sortBy: string
sortOrder: "asc" | "desc"
selectedTag: string
selectedPost: Post | null
newPost: { title: string; body: string; userId: number }

setPosts: (posts: Post[]) => void
setTotal: (total: number) => void
setSkip: (skip: number) => void
setLimit: (limit: number) => void
setSearchQuery: (query: string) => void
setSortBy: (sortBy: string) => void
setSortOrder: (sortOrder: "asc" | "desc") => void
setSelectedTag: (tag: string) => void
setSelectedPost: (post: Post | null) => void
setNewPost: (post: { title: string; body: string; userId: number }) => void

}

export const usePostsStore = create<PostsState>((set) => ({
posts: [],
total: 0,
skip: 0,
limit: 10,
searchQuery: "",
sortBy: "",
sortOrder: "asc",
selectedTag: "",
selectedPost: null,
newPost: { title: "", body: "", userId: 1 },

setPosts: (posts) => set({ posts }),
setTotal: (total) => set({ total }),
setSkip: (skip) => set({ skip }),
setLimit: (limit) => set({ limit }),
setSearchQuery: (searchQuery) => set({ searchQuery }),
setSortBy: (sortBy) => set({ sortBy }),
setSortOrder: (sortOrder) => set({ sortOrder }),
setSelectedTag: (selectedTag) => set({ selectedTag }),
setSelectedPost: (selectedPost) => set({ selectedPost }),
setNewPost: (newPost) => set({ newPost }),
}))
20 changes: 20 additions & 0 deletions src/entities/post/model/searchPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { usePostsStore } from "./posts-store.ts"

export const useSearchPosts = () => {
const { setPosts, setTotal } = usePostsStore()

const searchPosts = async (query: string) => {
if (!query.trim()) return

try {
const res = await fetch(`/api/posts/search?q=${query}`)
const data = await res.json()
setPosts(data.posts)
setTotal(data.total)
} catch (err) {
console.error("검색 실패:", err)
}
}

return { searchPosts }
}
17 changes: 17 additions & 0 deletions src/entities/post/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

export interface Post {
id: number
title: string
body: string
tags: string[]
userId: number
author?: {
id: number
username: string
image: string
}
reactions?: {
likes: number
dislikes: number
}
}
28 changes: 28 additions & 0 deletions src/features/posts-manager/model/addComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useCommentsStore } from "../../../entities/comment/model/comments-store.ts"
import { useDialogStore } from "../../../shared/model/dialog-store.ts"
import { addCommentApi } from "../../../entities/comment/api/addCommentApi.ts"

export const useAddComment = () => {
const { comments, setComments, newComment, setNewComment } = useCommentsStore()
const { closeDialog } = useDialogStore()

const addComment = async () => {
try {
if (newComment.postId == null) {
console.error("postId가 없습니다. 댓글을 추가할 수 없습니다.")
return
}

const added = await addCommentApi(newComment as { body: string; postId: number; userId: number })

setComments(added.postId, [...(comments[added.postId] || []), added])

setNewComment({ body: "", postId: null, userId: 1 })
closeDialog("showAddCommentDialog")
} catch (err) {
console.error("댓글 추가 오류", err)
}
}

return { addComment }
}
23 changes: 23 additions & 0 deletions src/features/posts-manager/model/addPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { usePostsStore } from "../../../entities/post/model/posts-store.ts"
import { useDialogStore } from "../../../shared/model/dialog-store.ts"
import { Post } from "../../../entities/post/model/types.ts"
import { addPostApi } from "../../../entities/post/api/addPostApi.ts"

export const useAddPost = () => {
const { setPosts } = usePostsStore()
const { closeDialog } = useDialogStore()

const addPost = async (newPost: Omit<Post, "id" | "tags" | "reactions" | "author">) => {
try {
const created = await addPostApi(newPost)

const prevPosts = usePostsStore.getState().posts
setPosts([created, ...prevPosts])

closeDialog("showAddPostDialog")
} catch (err) {
console.error("게시물 추가 오류", err)
}
}
return { addPost }
}
20 changes: 20 additions & 0 deletions src/features/posts-manager/model/deleteComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useCommentsStore } from "../../../entities/comment/model/comments-store.ts"
import { deleteCommentApi } from "../../../entities/comment/api/deleteCommentApi.ts"

export const useDeleteComment = () => {
const { setComments } = useCommentsStore()

const deleteComment = async (commentId: number, postId: number) => {
try {
await deleteCommentApi(commentId)
const current = useCommentsStore.getState().comments
const updatedComments = current[postId]?.filter((c) => c.id !== commentId) || []

setComments(postId, updatedComments)
} catch (err) {
console.error("댓글 삭제 실패:", err)
}
}

return { deleteComment }
}
Loading