Skip to content

Commit

Permalink
feat: kysely 적용 (#670)
Browse files Browse the repository at this point in the history
* build: ts-rest와 zod 버전 업데이트

* build: kysely 추가

* feat: db 타입 생성 및 kysely 연결

* feat: 타입 안전한 date 함수 래퍼

* refactor: `v2/reviews`에 kysely 적용

---------

Co-authored-by: nocontribute <>
  • Loading branch information
scarf005 authored Aug 24, 2023
1 parent 6132e3c commit 7c57107
Show file tree
Hide file tree
Showing 11 changed files with 631 additions and 118 deletions.
10 changes: 7 additions & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"standalone": "npm run build && npm run start",
"lint": "eslint --fix --ext .js,.ts src",
"check": "tsc --project ./tsconfig.prod.json --noEmit",
"test": "jest"
"test": "jest",
"schema": ". ./.env && DATABASE_URL=mysql://$RDS_USERNAME:$RDS_PASSWORD@$RDS_HOSTNAME:3306/$RDS_DB_NAME kysely-codegen --dialect=mysql --out-file=src/kysely/generated.ts"
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
Expand All @@ -40,6 +41,7 @@
"eslint-plugin-import": "^2.27.5",
"jest": "^29.5.0",
"jest-mock-extended": "^3.0.4",
"kysely-codegen": "^0.10.1",
"nodemon": "^3.0.1",
"prettier": "^2.8.8",
"ts-jest": "^29.1.0",
Expand All @@ -48,8 +50,8 @@
"dependencies": {
"@jiphyeonjeon-42/contracts": "workspace:*",
"@slack/web-api": "^6.7.1",
"@ts-rest/express": "^3.26.4",
"@ts-rest/open-api": "^3.26.4",
"@ts-rest/express": "^3.28.0",
"@ts-rest/open-api": "^3.28.0",
"axios": "^0.27.2",
"bcrypt": "^5.0.1",
"cookie-parser": "^1.4.6",
Expand All @@ -61,6 +63,8 @@
"http-status": "^1.5.0",
"http-terminator": "^3.2.0",
"jsonwebtoken": "^8.5.1",
"kysely": "^0.26.1",
"kysely-paginate": "^0.2.0",
"morgan": "^1.10.0",
"mysql2": "^2.3.3",
"node-schedule": "^2.1.0",
Expand Down
286 changes: 286 additions & 0 deletions backend/src/kysely/generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
import type { ColumnType, SqlBool } from "kysely";

export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;

export interface Book {
id: Generated<number>;
donator: Generated<string | null>;
callSign: string;
status: number;
createdAt: Generated<Date>;
infoId: number;
updatedAt: Generated<Date>;
donatorId: Generated<number | null>;
}

export interface BookInfo {
id: Generated<number>;
title: string;
author: string;
publisher: string;
isbn: Generated<string | null>;
image: Generated<string | null>;
publishedAt: Generated<Date | null>;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
categoryId: number;
}

export interface Category {
id: Generated<number>;
name: string;
}

export interface Lending {
id: Generated<number>;
lendingLibrarianId: number;
lendingCondition: string;
returningLibrarianId: Generated<number | null>;
returningCondition: Generated<string | null>;
returnedAt: Generated<Date | null>;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
bookId: number;
userId: number;
}

export interface Likes {
id: Generated<number>;
userId: number;
bookInfoId: number;
isDeleted: Generated<SqlBool>;
}

export interface Reservation {
id: Generated<number>;
endAt: Generated<Date | null>;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
status: Generated<number>;
bookInfoId: number;
bookId: Generated<number | null>;
userId: number;
}

export interface Reviews {
id: Generated<number>;
userId: number;
bookInfoId: number;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
updateUserId: number;
isDeleted: Generated<SqlBool>;
deleteUserId: Generated<number | null>;
content: string;
disabled: Generated<SqlBool>;
disabledUserId: Generated<number | null>;
}

export interface SubTag {
id: Generated<number>;
userId: number;
superTagId: number;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
isDeleted: Generated<SqlBool>;
updateUserId: number;
content: string;
isPublic: Generated<number>;
}

export interface SuperTag {
id: Generated<number>;
userId: number;
bookInfoId: number;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
isDeleted: Generated<SqlBool>;
updateUserId: number;
content: string;
}

export interface TypeormMetadata {
type: string;
database: Generated<string | null>;
schema: Generated<string | null>;
table: Generated<string | null>;
name: Generated<string | null>;
value: Generated<string | null>;
}

export interface User {
id: Generated<number>;
email: string;
password: string;
nickname: Generated<string | null>;
intraId: Generated<number | null>;
slack: Generated<string | null>;
penaltyEndDate: Generated<Date>;
role: Generated<number>;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
}

export interface UserReservation {
reservationId: Generated<number>;
endAt: Generated<Date | null>;
reservationDate: Generated<Date>;
reservedBookInfoId: number;
userId: number;
title: string | null;
author: string | null;
image: Generated<string | null>;
ranking: Generated<number | null>;
}

export interface VHistories {
id: Generated<number>;
returningCondition: Generated<string | null>;
login: Generated<string | null>;
callSign: string;
bookInfoId: Generated<number | null>;
title: string | null;
image: Generated<string | null>;
lendingCondition: string;
updatedAt: Generated<Date>;
penaltyDays: Generated<number | null>;
createdAt: Generated<string | null>;
returnedAt: Generated<string | null>;
dueDate: Generated<string | null>;
lendingLibrarianNickName: Generated<string | null>;
returningLibrarianNickname: Generated<string | null>;
}

export interface VLending {
id: Generated<number>;
lendingCondition: string;
login: Generated<string | null>;
bookId: Generated<number | null>;
callSign: string | null;
title: string | null;
image: Generated<string | null>;
penaltyDays: Generated<number | null>;
createdAt: Generated<string | null>;
returnedAt: Generated<string | null>;
dueDate: Generated<string | null>;
}

export interface VLendingForSearchUser {
lendingCondition: string;
lendDate: Generated<Date>;
userId: Generated<number>;
bookInfoId: Generated<number | null>;
title: string | null;
author: string | null;
image: Generated<string | null>;
duedate: Generated<Date | null>;
overDueDay: Generated<number | null>;
reservedNum: Generated<number | null>;
}

export interface VSearchBook {
bookId: Generated<number>;
donator: Generated<string | null>;
callSign: string;
status: number;
bookInfoId: number;
title: string | null;
author: string | null;
publisher: string | null;
isbn: Generated<string | null>;
image: Generated<string | null>;
categoryId: number | null;
category: string | null;
publishedAt: Generated<string | null>;
isLendable: Generated<number | null>;
}

export interface VSearchBookByTag {
id: Generated<number>;
title: string;
author: string;
isbn: Generated<string | null>;
image: Generated<string | null>;
publishedAt: Generated<Date | null>;
createdAt: Generated<Date>;
updatedAt: Generated<Date>;
category: string;
superTagContent: string;
subTagContent: string | null;
lendingCnt: Generated<number | null>;
}

export interface VStock {
bookId: Generated<number>;
donator: Generated<string | null>;
callSign: string;
status: number;
bookInfoId: number;
title: string | null;
author: string | null;
publisher: string | null;
isbn: Generated<string | null>;
image: Generated<string | null>;
categoryId: number | null;
category: string | null;
publishedAt: Generated<string | null>;
updatedAt: Generated<string | null>;
}

export interface VTagsSubDefault {
bookInfoId: number;
title: string;
id: Generated<number>;
createdAt: Generated<string | null>;
login: Generated<string | null>;
content: string;
superTagId: Generated<number>;
superContent: string;
isPublic: Generated<number>;
isDeleted: Generated<number>;
visibility: Generated<string>;
}

export interface VTagsSuperDefault {
content: Generated<string>;
count: Generated<number | null>;
type: Generated<string>;
createdAt: Generated<string | null>;
}

export interface VUserLending {
lendingCondition: string;
userId: number;
bookInfoId: Generated<number | null>;
title: string | null;
image: Generated<string | null>;
lendDate: Generated<string | null>;
duedate: Generated<string | null>;
overDueDay: Generated<number | null>;
}

export interface DB {
book: Book;
book_info: BookInfo;
category: Category;
lending: Lending;
likes: Likes;
reservation: Reservation;
reviews: Reviews;
sub_tag: SubTag;
super_tag: SuperTag;
typeorm_metadata: TypeormMetadata;
user: User;
user_reservation: UserReservation;
v_histories: VHistories;
v_lending: VLending;
v_lending_for_search_user: VLendingForSearchUser;
v_search_book: VSearchBook;
v_search_book_by_tag: VSearchBookByTag;
v_stock: VStock;
v_tags_sub_default: VTagsSubDefault;
v_tags_super_default: VTagsSuperDefault;
v_user_lending: VUserLending;
}
24 changes: 24 additions & 0 deletions backend/src/kysely/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Kysely, MysqlDialect } from 'kysely';
import { createPool } from 'mysql2';
import type { DB } from './generated.ts';
import { connectOption } from '~/config/index.ts';

const { database, host, password, username: user } = connectOption;

const dialect = new MysqlDialect({
pool: createPool({
port: 3306,
connectionLimit: 10,
host,
database,
user,
password,
}),
});

export const db = new Kysely<DB>({
dialect,
log: event => console.log('kysely:', event.query.sql, event.query.parameters),
});

export type Database = typeof db;
22 changes: 22 additions & 0 deletions backend/src/kysely/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { z } from 'zod';

/** 반환값이 조건을 만족하지 않으면 오류 throw */
const throwIf = <T>(value: T, ok: (v: T) => boolean) => {
if (ok(value)) {
return value;
}
throw new Error(`값이 예상과 달리 ${value}입니다`);
};

export type Visibility = 'public' | 'private' | 'all'
const roles = ['user', 'cadet', 'librarian', 'staff'] as const;
export type Role = typeof roles[number]

const fromEnum = (role: number): Role =>
throwIf(roles[role], (v) => v === undefined);

export const toRole = (role: Role): number =>
throwIf(roles.indexOf(role), (v) => v === -1);

export const roleSchema = z.number().int().min(0).max(3)
.transform(fromEnum);
Loading

0 comments on commit 7c57107

Please sign in to comment.