Skip to content

Commit

Permalink
ci: 2023-01-21 비정기 배포 (#815)
Browse files Browse the repository at this point in the history
* ci: 타입오류 메시지 상대경로 명시적으로 해소 (#643)

참고: actions/runner#659

* feat: v2 book api (#746)

* refactor: v1 api와 다른 부분 수정

* refactor: books API schema default sort value 추가 및 callSign 오타 수정

* refactor: status: zod.enum -> zod.nativeEnum 변경

z.enum으로 했을때 number 반환값에 대한 처리가 되지 않아 enumStatus 생성 후 nativeEnum으로 변경하였습니다.

* feat: books//:id v2 구현

* feat: books/search v2 구현

* feat: book/update v2 구현

* feat: books/info/:id v2 구현

* refactor: BookNotFoundError import 오타 수정

* feat: books/info/sorted v2 구현

* feat: books/info/tag v2 구현

book_info.id 로 distinct가 되지 않는 오류가 있어요

* refactor: 의미에 맞는 변수명으로 수정 및 코드 간략화

Co-authored-by: scarf <[email protected]>

* refactor: book_info 중복 select 부분 처리

Co-authored-by: scarf <[email protected]>

* feat: books/donator v2 구현

v1 구현 시 얘기했었던 기부자가 유저가 아니더라도 수정될 수 있도록 수정 & email은 더이상 관리하지 않기 때문에 변수명에서 제거

* refactor: 대출 가능 여부 boolean 반환값으로 수정

kysely 사용중 select가 없을 경우 sql syntax 에러로 해당 부분 수정 및 boolean 값 반환되도록 수정

* feat: [get] books/create v2 구현

axios.get 동작 중 발생하는 에러(catch 영역)에 대한 처리를 어떻게 해야할지 모르겠어요

* fix: 임시로 타입 오류 무시

---------

Co-authored-by: scarf <[email protected]>

* style: prettier 적용 (#761)

* build: prettier 설정

* style: prettier 적용

---------

Co-authored-by: nocontribute <>

* [fix] backend dockerfile error (#764)

Co-authored-by: kylee <[email protected]>

* fix: `positiveInt` -> `nonNegativeInt` (#766)

* refactor: v2 라우트 정리 적용 (#771)

* refactor: `/stock` 제거

#767 (comment)

* refactor: contracts에서 500번대 오류 제거

백엔드에서 복구 불가능한 오류일 시 반환하기 때문에, 프론트엔드에서 따로 처리하는 것이 좋을 것 같습니다.

* refactor: `/history` -> `/lendings`

#767 (comment)

Co-authored-by: jwoo <[email protected]>

* refactor: `/users ` 정리

#767 (comment)

Co-authored-by: honeyl3ee <[email protected]>

* refactor: `/tag` 서비스 임시 제거

고도화를 하기 위해서는 내부 구현을 바꾸어야 하는 문제가 있어 우선순위를 낮추었습니다.

주석처리를 할까 고민했으나 제거 이전 커밋(1565441)으로 체크아웃시 전체 코드를 확인 가능하기 때문에 복잡도 감소를 위해 코드를 제거하였습니다.

* refactor: `/books` 경로 정리

#767 (comment)
#767 (comment)

Co-authored-by: Jeong Jihwan <[email protected]>
Co-authored-by: jwoo <[email protected]>

* feat: swagger에서 1줄 요약 표시

---------

Co-authored-by: jwoo <[email protected]>
Co-authored-by: honeyl3ee <[email protected]>
Co-authored-by: Jeong Jihwan <[email protected]>

* feat: add mydata service

토큰에서 id 정보 찾아서 유저 정보 반환하는 controller

* feat: 유저 search 할 때 id 가 undefined  인 경우 핸들링

* feat: add swagger && /me endpoint && apply authValidate

* fix: searchUsersById 타입을 이전과 같이 리턴하도록 변경

searchUsersById 서비스 함수의 종속성이 생각보다 많음.controller 에서 items 의 length 를 확인하도록

* fix: add librarian validate in search endpoint

* feat: 로그인한 유저만 본인 정보를 찾을 수 있도록 middleware 에서 권한 체크

* chore: console.log 제거

* User API 경로 정리 (#777)

* refactor: 400번대 에러 반환 제거

* refactor: overDueDay 반환 값에서 제거

* fix(cursus): Access-Control-Allow-Origin 설정 (#790)

* chore: dependencies 업데이트 (#796)

* chore(deps): contracts의 pnpm-lock 업데이트

* chore(deps): @mapbox/npm-pre-gyp 설치

* chore(deps): npm-pre-gyp 설치

* Revert "chore(deps): npm-pre-gyp 설치"

This reverts commit 8922c38

* chore(deps): package.json과 pnpm-lock.yaml 동기화

* fix: users/me 유저권한 all 로 변경

* fix: 반납 3일 전 알림이 여러 번 전송됨 (#801)

* fix(notification): 3일 전 반납 알림을 중복 사용한 부분 삭제

- 3일 전 반납 알림을 보내는 함수가 notifyReturningReminder(), notifyOverdueManager() 인데, 후자가 유연한 동작을 지원하므로 전자 함수의 동작을 제거함

* fix(notification): 슬랙 연체 알림 보내는 함수 스케줄러에 추가

* Update backend/src/v1/notifications/notifications.service.ts

Co-authored-by: Ji-Hyuck, Min <[email protected]>

* refactor: console.log 제거

---------

Co-authored-by: Ji-Hyuck, Min <[email protected]>

* fix: `dev/v2` 경로 복구 (#808)

* security: 보안 취약점 해결 (#818)

* feat(utils): rate limit 모듈 추가

- R, CUD에 해당하는 rate limit 모듈 추가

* refactor(cursus): rate limit 모듈 import해서 사용하도록 변경

* feat: getRateLimiter 적용

* feat(books): 유효한 ISBN인지 검사하는 로직 추가

* feat(tags): tags router에 rate limit 추가

* feat(routes): router에서 authValidate를 미들웨어로 쓰는 곳에 rate limiter 추가

* feat(auth): /get/me에 rate limiter 추가

* feat(users): 유저 생성 후 created 문장 출력 시, db에 저장된 email 값을 사용

* build: csrf 방지를 위한 lusca 패키지 추가

* feat(app): csrf 방지 로직 추가

* feat(app): csrf 방지 옵션 수정

* build: express-session 패키지 추가

* feat(app): lusca 상태 유지를 위한 세션 추가

* feat(app): cookie에도 secret 추가

* feat(app): session에서 cookie 설정 및 lusca에서 부가적인 설정 제거

* fix(auth): /get/me시, id가 null이면 400 status code 반환 (#816)

- /get/me 시, id가 null이면 400 status code, errorCode.NO_USER 반환
- catch 로직 수정

---------

Co-authored-by: scarf <[email protected]>
Co-authored-by: Jeong Jihwan <[email protected]>
Co-authored-by: gilee <[email protected]>
Co-authored-by: kylee <[email protected]>
Co-authored-by: jwoo <[email protected]>
Co-authored-by: honeyl3ee <[email protected]>
Co-authored-by: jimin <[email protected]>
Co-authored-by: Ji-Hyuck, Min <[email protected]>
  • Loading branch information
9 people authored Jan 28, 2024
1 parent d72f526 commit a0af0e7
Show file tree
Hide file tree
Showing 163 changed files with 4,947 additions and 4,726 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"jest": true
},
"root": true,
"extends": ["airbnb-base", "plugin:@typescript-eslint/recommended"],
"extends": ["airbnb-base", "plugin:@typescript-eslint/recommended", "prettier"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"parserOptions": {
Expand Down
19 changes: 10 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ name: Test
on:
pull_request:

defaults:
run:
shell: bash

jobs:
test:
name: Test PR
runs-on: ubuntu-latest
environment: development

steps:
- uses: reviewdog/action-setup@v1

- name: Checkout
uses: actions/checkout@v3

Expand Down Expand Up @@ -40,12 +46,7 @@ jobs:
pnpm install --frozen-lockfile
pnpm --filter='@jiphyeonjeon-42/contracts' build
- if: always()
name: check types (backend)
working-directory: backend
run: pnpm check

- if: always()
name: check types (contracts)
working-directory: contracts
run: pnpm check
- name: check types
if: always()
run: |
pnpm -r --no-bail --parallel run check | sed -r 's|(.*)( check: )(.*)|\1/\3|'
7 changes: 7 additions & 0 deletions .prettierrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
semi: true
singleQuote: true
useTabs: false
tabWidth: 2
trailingComma: all
printWidth: 100
arrowParens: always
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ FROM node:18-alpine as pnpm-installed
# https://github.com/pnpm/pnpm/issues/4495#issuecomment-1317831712
ENV PNPM_HOME="/root/.local/share/pnpm"
ENV PATH="${PATH}:${PNPM_HOME}"
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools
RUN apk add --no-cache make
RUN apk add build-base
RUN npm install --global pnpm
RUN pnpm config set store-dir .pnpm-store
RUN pnpm install --global node-pre-gyp
Expand Down
5 changes: 5 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
"@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/express-session": "^1.17.10",
"@types/http-errors": "^2.0.1",
"@types/jest": "^29.5.2",
"@types/jsonwebtoken": "^9.0.2",
"@types/lusca": "^1.7.4",
"@types/morgan": "^1.9.4",
"@types/node-schedule": "^2.1.0",
"@types/passport": "^1.0.12",
Expand All @@ -49,6 +51,7 @@
},
"dependencies": {
"@jiphyeonjeon-42/contracts": "workspace:*",
"@mapbox/node-pre-gyp": "^1.0.11",
"@slack/web-api": "^6.7.1",
"@ts-rest/express": "^3.28.0",
"@ts-rest/open-api": "^3.28.0",
Expand All @@ -60,13 +63,15 @@
"dotenv": "^16.0.0",
"express": "^4.17.2",
"express-rate-limit": "^6.9.0",
"express-session": "^1.17.3",
"hangul-js": "^0.2.6",
"http-errors": "^2.0.0",
"http-status": "^1.5.0",
"http-terminator": "^3.2.0",
"jsonwebtoken": "^8.5.1",
"kysely": "^0.26.1",
"kysely-paginate": "^0.2.0",
"lusca": "^1.7.0",
"morgan": "^1.10.0",
"mysql2": "^2.3.3",
"node-schedule": "^2.1.0",
Expand Down
19 changes: 18 additions & 1 deletion backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,29 @@ import { createExpressEndpoints } from '@ts-rest/express';

import router from '~/v1/routes';
import routerV2 from '~/v2/routes';
import lusca from "lusca";
import session from 'express-session';
import * as crypto from "crypto";
import { morganMiddleware } from './logger';

const app: express.Application = express();
const secret = crypto.randomBytes(42).toString('hex');

app.use(session({
secret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
sameSite: 'strict',
secure: true,
}
}));
app.use(morganMiddleware);
app.use(cookieParser());
app.use(cookieParser(
secret,
));
app.use(lusca.csrf());
app.use(passport.initialize());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
Expand Down
24 changes: 13 additions & 11 deletions backend/src/config/JwtOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import { Mode } from './modeOption';
import { match } from 'ts-pattern';

type getJwtOption = (mode: Mode) => (option: OauthUrlOption) => JwtOption;
export const getJwtOption: getJwtOption = (mode) => ({ redirectURL, clientURL }) => {
const redirectDomain = new URL(redirectURL).hostname;
const clientDomain = new URL(clientURL).hostname;
const secure = mode === 'prod' || mode === 'https';
export const getJwtOption: getJwtOption =
(mode) =>
({ redirectURL, clientURL }) => {
const redirectDomain = new URL(redirectURL).hostname;
const clientDomain = new URL(clientURL).hostname;
const secure = mode === 'prod' || mode === 'https';

const issuer = secure ? redirectDomain : 'localhost';
const domain = match(mode)
.with('prod', () => clientDomain)
.with('https', () => undefined)
.otherwise(() => 'localhost');
const issuer = secure ? redirectDomain : 'localhost';
const domain = match(mode)
.with('prod', () => clientDomain)
.with('https', () => undefined)
.otherwise(() => 'localhost');

return { issuer, domain, secure };
};
return { issuer, domain, secure };
};

export const jwtSecretSchema = z.object({ JWT_SECRET: nonempty }).transform((v) => v.JWT_SECRET);

Expand Down
10 changes: 5 additions & 5 deletions backend/src/config/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type NaverBookApiOption = {

/** 네이버 도서 검색 API 시크릿 */
secret: string;
}
};

/** DB 연결 옵션 */
export type ConnectOption = {
Expand All @@ -34,7 +34,7 @@ export type ConnectOption = {

/** DB 이름 */
database: string;
}
};

/** OAuth URL 옵션 */
export type OauthUrlOption = {
Expand All @@ -43,7 +43,7 @@ export type OauthUrlOption = {

/** 집현전 프론트엔드 URL */
clientURL: string;
}
};

/** 42 API OAuth 클라이언트 인증 정보 */
export type Oauth42ApiOption = {
Expand All @@ -52,7 +52,7 @@ export type Oauth42ApiOption = {

/** 42 API OAuth 클라이언트 시크릿 */
secret: string;
}
};

/** npm 로깅 레벨 */
export type LogLevel = keyof typeof levels;
Expand All @@ -64,4 +64,4 @@ export type LogLevelOption = {

/** 콘솔 로깅 레벨 */
readonly consoleLogLevel: 'error' | 'debug';
}
};
18 changes: 11 additions & 7 deletions backend/src/config/dbSchema.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { envObject, nonempty } from './envObject';

/** RDS 연결 옵션 파싱을 위한 스키마 */
export const rdsSchema = envObject('RDS_HOSTNAME', 'RDS_USERNAME', 'RDS_PASSWORD', 'RDS_DB_NAME')
.transform((v) => ({
host: v.RDS_HOSTNAME,
username: v.RDS_USERNAME,
password: v.RDS_PASSWORD,
database: v.RDS_DB_NAME,
}));
export const rdsSchema = envObject(
'RDS_HOSTNAME',
'RDS_USERNAME',
'RDS_PASSWORD',
'RDS_DB_NAME',
).transform((v) => ({
host: v.RDS_HOSTNAME,
username: v.RDS_USERNAME,
password: v.RDS_PASSWORD,
database: v.RDS_DB_NAME,
}));

/** MYSQL 연결 옵션 파싱을 위한 스키마 */
const mysqlSchema = envObject('MYSQL_USER', 'MYSQL_PASSWORD', 'MYSQL_DATABASE')
Expand Down
2 changes: 1 addition & 1 deletion backend/src/config/envObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const url = z.string().trim().url();
* @param keys 환경변수 키 목록
*/
export const envObject = <T extends readonly string[]>(...keys: T) => {
type Keys = T[ number ];
type Keys = T[number];
const env = Object.fromEntries(keys.map((key) => [key, nonempty]));

return z.object(env as Record<Keys, typeof nonempty>);
Expand Down
10 changes: 6 additions & 4 deletions backend/src/config/getConnectOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ const getConnectOptionSchema = (mode: Mode) => {
/**
* 환경변수에서 DB 연결 옵션을 파싱하는 함수
*/
export const getConnectOption = (mode: Mode) => (processEnv: NodeJS.ProcessEnv): ConnectOption => {
const connectOptionSchema = getConnectOptionSchema(mode);
export const getConnectOption =
(mode: Mode) =>
(processEnv: NodeJS.ProcessEnv): ConnectOption => {
const connectOptionSchema = getConnectOptionSchema(mode);

return connectOptionSchema.parse(processEnv);
};
return connectOptionSchema.parse(processEnv);
};
4 changes: 2 additions & 2 deletions backend/src/config/logOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const colors: Record<LogLevel, string> = {
} as const;

export const getLogLevelOption = (mode: RuntimeMode): LogLevelOption => {
const logLevel = (mode === 'production' ? 'http' : 'debug');
const consoleLogLevel = (mode === 'production' ? 'error' : 'debug');
const logLevel = mode === 'production' ? 'http' : 'debug';
const consoleLogLevel = mode === 'production' ? 'error' : 'debug';

return { logLevel, consoleLogLevel } as const;
};
12 changes: 7 additions & 5 deletions backend/src/config/naverBookApiOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import { NaverBookApiOption } from './config.type';
import { envObject } from './envObject';

const naverBookApiSchema = envObject('NAVER_BOOK_SEARCH_CLIENT_ID', 'NAVER_BOOK_SEARCH_SECRET')
.transform((v) => ({
client: v.NAVER_BOOK_SEARCH_CLIENT_ID,
secret: v.NAVER_BOOK_SEARCH_SECRET,
}));
const naverBookApiSchema = envObject(
'NAVER_BOOK_SEARCH_CLIENT_ID',
'NAVER_BOOK_SEARCH_SECRET',
).transform((v) => ({
client: v.NAVER_BOOK_SEARCH_CLIENT_ID,
secret: v.NAVER_BOOK_SEARCH_SECRET,
}));

export const getNaverBookApiOption = (processEnv: NodeJS.ProcessEnv): NaverBookApiOption => {
const option = naverBookApiSchema.parse(processEnv);
Expand Down
24 changes: 14 additions & 10 deletions backend/src/config/oauthOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ export const oauth42Schema = z.object({
CLIENT_SECRET: nonempty,
});

export const getOauthUrlOption = (processEnv: NodeJS.ProcessEnv): OauthUrlOption => oauthUrlSchema
.transform((v) => ({
redirectURL: v.REDIRECT_URL,
clientURL: v.CLIENT_URL,
})).parse(processEnv);
export const getOauthUrlOption = (processEnv: NodeJS.ProcessEnv): OauthUrlOption =>
oauthUrlSchema
.transform((v) => ({
redirectURL: v.REDIRECT_URL,
clientURL: v.CLIENT_URL,
}))
.parse(processEnv);

// eslint-disable-next-line max-len
export const getOauth42ApiOption = (processEnv: NodeJS.ProcessEnv): Oauth42ApiOption => oauth42Schema
.transform((v) => ({
id: v.CLIENT_ID,
secret: v.CLIENT_SECRET,
})).parse(processEnv);
export const getOauth42ApiOption = (processEnv: NodeJS.ProcessEnv): Oauth42ApiOption =>
oauth42Schema
.transform((v) => ({
id: v.CLIENT_ID,
secret: v.CLIENT_SECRET,
}))
.parse(processEnv);
33 changes: 19 additions & 14 deletions backend/src/entity/entities/Book.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import {
Column, Entity, Index, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn,
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { BookInfo } from './BookInfo';
import { User } from './User';
Expand All @@ -8,55 +14,54 @@ import { Reservation } from './Reservation';

@Index('FK_donator_id_from_user', ['donatorId'], {})
@Entity('book')

export class Book {
@PrimaryGeneratedColumn({ type: 'int', name: 'id' })
id?: number;
id?: number;

@Column('varchar', { name: 'donator', nullable: true, length: 255 })
donator: string | null;
donator: string | null;

@Column('varchar', { name: 'callSign', length: 255 })
callSign: string;
callSign: string;

@Column('int', { name: 'status' })
status: number;
status: number;

@Column('datetime', {
name: 'createdAt',
default: () => "'CURRENT_TIMESTAMP(6)'",
})
createdAt?: Date;
createdAt?: Date;

@Column('int')
infoId: number;
infoId: number;

@Column('datetime', {
name: 'updatedAt',
default: () => "'CURRENT_TIMESTAMP(6)'",
})
updatedAt?: Date;
updatedAt?: Date;

@Column('int', { name: 'donatorId', nullable: true })
donatorId: number | null;
donatorId: number | null;

@ManyToOne(() => BookInfo, (bookInfo) => bookInfo.books, {
onDelete: 'NO ACTION',
onUpdate: 'NO ACTION',
})
@JoinColumn([{ name: 'infoId', referencedColumnName: 'id' }])
info?: BookInfo;
info?: BookInfo;

@ManyToOne(() => User, (user) => user.books, {
onDelete: 'NO ACTION',
onUpdate: 'NO ACTION',
})
@JoinColumn([{ name: 'donatorId', referencedColumnName: 'id' }])
donator2?: User;
donator2?: User;

@OneToMany(() => Lending, (lending) => lending.book)
lendings?: Lending[];
lendings?: Lending[];

@OneToMany(() => Reservation, (reservation) => reservation.book)
reservations?: Reservation[];
reservations?: Reservation[];
}
Loading

0 comments on commit a0af0e7

Please sign in to comment.