-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 방명록 읽기/쓰기 기능 구현 #21
Changes from all commits
899e258
9c9e647
5c25392
bd71dc9
72c7251
e8d9bd9
0078898
6907c3d
5a92acd
6e40d1c
1ffac6c
deb31f4
c32e1b5
7104b08
9055b8a
3157226
1e768a8
567152b
d3e8b13
aecc524
a07fbef
9dfd964
d5aecc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
DROP TABLE IF EXISTS Users; | ||
CREATE TABLE Users ( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
githubUserId KEY, | ||
thumbnailUrl TEXT, | ||
name TEXT, | ||
githubUserName TEXT, | ||
bio TEXT, | ||
githubUrl TEXT, | ||
createdAt TEXT, | ||
updatedAt TEXT | ||
); | ||
|
||
DROP TABLE IF EXISTS Minihomes; | ||
CREATE TABLE Minihomes ( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
userId INTEGER UNIQUE, | ||
createdAt TEXT, | ||
updatedAt TEXT, | ||
FOREIGN KEY (userId) REFERENCES Users(id) | ||
); | ||
|
||
|
||
DROP TABLE IF EXISTS Guestbooks; | ||
CREATE TABLE Guestbooks ( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
minihomeId INTEGER UNIQUE, | ||
createdAt TEXT, | ||
updatedAt TEXT, | ||
FOREIGN KEY (minihomeId) REFERENCES Minihomes(id) | ||
); | ||
|
||
DROP TABLE IF EXISTS Comments; | ||
CREATE TABLE Comments ( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
guestbookId INTEGER, | ||
authorId INTEGER, | ||
content TEXT, | ||
parentId INTEGER NULL, | ||
createdAt TEXT, | ||
updatedAt TEXT, | ||
FOREIGN KEY (guestbookId) REFERENCES Guestbooks(id) | ||
FOREIGN KEY (authorId) REFERENCES Users(id) | ||
); |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { UserService } from "../router/api/user/user.service"; | ||
import { Context, Next } from "hono"; | ||
import { HTTPException } from "hono/http-exception"; | ||
import { JwtPayload } from "../router/api/auth/types"; | ||
import { User } from "../router/api/user/user.schema"; | ||
|
||
declare module "hono" { | ||
interface ContextVariableMap { | ||
user: User; | ||
} | ||
} | ||
|
||
export const getUserJwtMiddleware = | ||
({ userService }: { userService: UserService }) => | ||
async (ctx: Context, next: Next) => { | ||
const payload = ctx.get("jwtPayload") as JwtPayload; | ||
if (payload == null) { | ||
throw new Error("jwt middleware 와 함께 사용해주세요."); | ||
} | ||
const user = await userService.getUserById(payload.sub); | ||
|
||
if (user == null) { | ||
throw new HTTPException(401, { message: "유저 정보가 없습니다." }); | ||
} | ||
|
||
ctx.set("user", user); | ||
|
||
await next(); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type JwtPayload = { | ||
sub: number; | ||
name: string; | ||
exp: number; | ||
iat: number; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { Kysely, sql } from "kysely"; | ||
import { DataBase } from "../../../types/database"; | ||
import { addTimeStamp } from "../../../utils/addTimeStamp"; | ||
import { Comment } from "./comment.schema"; | ||
import { User } from "../user/user.schema"; | ||
|
||
export class CommentRepository { | ||
private db; | ||
constructor({ db }: { db: Kysely<DataBase> }) { | ||
this.db = db; | ||
} | ||
|
||
async getAllCommentsByGuestbookId(guestbookId: number) { | ||
return await this.db | ||
.selectFrom("Comments") | ||
.where("Comments.guestbookId", "=", guestbookId) | ||
.innerJoin("Users", "Users.id", "Comments.authorId") | ||
.select([ | ||
"Comments.id", | ||
"Comments.guestbookId", | ||
"Comments.content", | ||
"Comments.parentId", | ||
"Comments.createdAt", | ||
"Comments.updatedAt", | ||
sql<User>` | ||
json_object( | ||
'id', Users.id, | ||
'githubUserId', Users.githubUserId, | ||
'thumbnailUrl', Users.thumbnailUrl, | ||
'name', Users.name, | ||
'githubUserName', Users.githubUserName, | ||
'bio', Users.bio, | ||
'githubUrl', Users.githubUrl, | ||
'createdAt', Users.createdAt, | ||
'updatedAt', Users.updatedAt | ||
)`.as("author"), | ||
Comment on lines
+13
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거 DB에서 긁으려니까, 좀 이상하네요. 타입 지원이 안돼서 많이 위험한듯 합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JS단에서 처리하는것도 저는 좋은 방법이라고 생각해요! 결국 Repository 의 역할은 데이터 소스에서 데이터를 가져와 가공하는 것이기 때문에, DB에 JSON 이라는 형태로 저장된 포맷을 파싱하는 JS 로직이 들어가도 자연스러운 것 같아요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
와우 그렇네요. 감사합니다 반영해볼게요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이거 수정하려고 했는데, 어차피 두개 테이블 조인한 결과를 selectAll 로 추출하기 어렵더라고요. id, createdAt, updatedAt 이 중복되서, alias 지어줘야함. 하지만 말씀하신 의견은 너무 좋은거같아서, 다른 부분 있으면 적용해볼게요! async getAllCommentsByGuestbookId(guestbookId: number) {
const comments = await this.db
.selectFrom("Comments")
.where("Comments.guestbookId", "=", guestbookId)
.innerJoin("Users", "Users.id", "Comments.authorId")
.select([
"Comments.id as commentId",
"Comments.createdAt as commentCreatedAt",
"Comments.updatedAt as commentUpdatedAt",
"Comments.content",
"Comments.guestbookId",
"Comments.authorId",
"Users.id as userId",
"Users.createdAt as userCreatedAt",
"Users.updatedAt as userUpdatedAt",
])
.execute();
return comments.map(
({
id,
content,
parentId,
createdAt,
updatedAt,
authorId,
...author
}) => ({
id,
content,
parentId,
createdAt,
updatedAt,
author: { ...author, id: authorId },
})
);
} |
||
]) | ||
.execute(); | ||
} | ||
|
||
async createComment(props: { | ||
guestbookId: Comment["guestbookId"]; | ||
authorId: Comment["authorId"]; | ||
content: Comment["content"]; | ||
parentId: Comment["parentId"]; | ||
}) { | ||
return await this.db | ||
.insertInto("Comments") | ||
.values(addTimeStamp(props) as Comment) | ||
.returningAll() | ||
.executeTakeFirstOrThrow(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface Comment { | ||
id: number; | ||
guestbookId: number; | ||
authorId: number; | ||
content: string; | ||
parentId: number | null; | ||
createdAt: string; | ||
updatedAt: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Env } from "../../../worker-env"; | ||
import { User } from "../user/user.schema"; | ||
import { UserService } from "../user/user.service"; | ||
import { CommentRepository } from "./comment.repository"; | ||
import { Comment } from "./comment.schema"; | ||
|
||
type AllCommentDTO = { | ||
guestbookId: number; | ||
comments: Array<CommentWithRepies>; | ||
}; | ||
|
||
type CommentWithRepies = CommentDTO & { replies: CommentDTO[] }; | ||
|
||
interface CommentDTO { | ||
id: number; | ||
content: string; | ||
author: User; | ||
createdAt: string; | ||
updatedAt: string; | ||
} | ||
|
||
export class CommentService { | ||
private env; | ||
private commentRepository; | ||
private userService; | ||
constructor({ | ||
env, | ||
commentRepository, | ||
userService, | ||
}: { | ||
env: Env; | ||
commentRepository: CommentRepository; | ||
userService: UserService; | ||
}) { | ||
this.env = env; | ||
this.commentRepository = commentRepository; | ||
this.userService = userService; | ||
} | ||
|
||
async getAllGuestbookCommentsByGithubUserName(githubUserName: string) { | ||
const guestbook = | ||
await this.userService.getGuestbookByGithubUserName(githubUserName); | ||
if (guestbook == null) { | ||
return { result: "notFound" as const }; | ||
} | ||
const comments = await this.commentRepository.getAllCommentsByGuestbookId( | ||
guestbook.id | ||
); | ||
const commentMap = new Map<number, CommentWithRepies>(); | ||
comments.forEach((comment) => { | ||
const { parentId, ...commentData } = comment; | ||
|
||
if (!parentId) { | ||
// parentId가 없는 경우 (최상위 댓글) | ||
commentMap.set(commentData.id, { ...commentData, replies: [] }); | ||
} else { | ||
// 대댓글인 경우 | ||
const parentComment = commentMap.get(parentId); | ||
if (parentComment == null) { | ||
return; | ||
} | ||
parentComment.replies.push(commentData); | ||
} | ||
}); | ||
|
||
const result = Array.from(commentMap.values()); | ||
|
||
return { | ||
result: "success" as const, | ||
comments: result, | ||
guestbookId: guestbook.id, | ||
}; | ||
} | ||
|
||
async createComment(props: { | ||
guestbookId: Comment["guestbookId"]; | ||
authorId: Comment["authorId"]; | ||
content: Comment["content"]; | ||
parentId: Comment["parentId"]; | ||
}) { | ||
const res = await this.commentRepository.createComment(props); | ||
return res; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Kysely } from "kysely"; | ||
import { DataBase } from "../../../types/database"; | ||
import { addTimeStamp } from "../../../utils/addTimeStamp"; | ||
import { Guestbook } from "./guestbook.schema"; | ||
|
||
export class GuestbookRepository { | ||
private db; | ||
constructor({ db }: { db: Kysely<DataBase> }) { | ||
this.db = db; | ||
} | ||
|
||
async getGuestbookByMinihomeId(minihomeId: number) { | ||
return await this.db | ||
.selectFrom("Guestbooks") | ||
.selectAll() | ||
.where("Guestbooks.minihomeId", "=", minihomeId) | ||
.executeTakeFirst(); | ||
} | ||
|
||
async createGuestbook(minihomeId: number) { | ||
return await this.db | ||
.insertInto("Guestbooks") | ||
.values(addTimeStamp({ minihomeId }) as Guestbook) | ||
.returningAll() | ||
.executeTakeFirstOrThrow(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Tekiter
파일 합쳤습니다~~
테이블 생성도 쉽고 훨씬 좋네요!