Skip to content

Commit 1760db7

Browse files
committed
feat(project): add all features
1 parent 37784a1 commit 1760db7

35 files changed

+1280
-16
lines changed

package.json

+24-4
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,43 @@
55
"author": "kraccoon-dev <[email protected]>",
66
"license": "MIT",
77
"scripts": {
8-
"dev": "nodemon src/server.ts",
9-
"lint": "eslint src/**/*.ts"
8+
"dev": "nodemon -r tsconfig-paths/register src/server.ts",
9+
"lint": "eslint src/**/*.ts",
10+
"db:seed": "npx prisma db seed",
11+
"db:push": "npx prisma db push && yarn db:pull",
12+
"db:pull": "npx prisma db pull && npx prisma generate"
13+
},
14+
"prisma": {
15+
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} -r tsconfig-paths/register prisma/seed.ts"
1016
},
1117
"dependencies": {
18+
"@prisma/client": "^4.2.1",
19+
"axios": "^0.27.2",
20+
"cors": "^2.8.5",
1221
"dotenv": "^16.0.1",
13-
"express": "^4.18.1"
22+
"express": "^4.18.1",
23+
"express-bearer-token": "^2.4.0",
24+
"helmet": "^5.1.1",
25+
"http-errors": "^2.0.0",
26+
"joi": "^17.6.0",
27+
"jsonwebtoken": "^8.5.1",
28+
"moment": "^2.29.4"
1429
},
1530
"devDependencies": {
16-
"@tsconfig/node16-strictest": "^1.0.3",
31+
"@tsconfig/node16": "^1.0.3",
32+
"@types/cors": "^2.8.12",
1733
"@types/express": "^4.17.13",
34+
"@types/http-errors": "^1.8.2",
35+
"@types/jsonwebtoken": "^8.5.8",
1836
"@types/node": "^18.7.2",
1937
"@typescript-eslint/eslint-plugin": "^5.33.0",
2038
"@typescript-eslint/parser": "^5.33.0",
2139
"eslint": "^8.21.0",
2240
"nodemon": "^2.0.19",
2341
"prettier": "^2.7.1",
42+
"prisma": "^4.2.1",
2443
"ts-node": "^10.9.1",
44+
"tsconfig-paths": "^4.1.0",
2545
"typescript": "^4.7.4"
2646
}
2747
}

prisma/schema.prisma

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
generator client {
2+
provider = "prisma-client-js"
3+
}
4+
5+
datasource db {
6+
provider = "postgresql"
7+
url = env("DATABASE_URL")
8+
}
9+
10+
model User {
11+
id String @id @default(cuid())
12+
email String @unique
13+
password String
14+
createdAt DateTime @default(now())
15+
updatedAt DateTime @updatedAt
16+
card Card[]
17+
transaction Transaction[]
18+
19+
@@unique([email, password])
20+
}
21+
22+
model Card {
23+
id String @id @default(cuid())
24+
userId String
25+
billKey String
26+
createdAt DateTime @default(now())
27+
updatedAt DateTime @updatedAt
28+
name String
29+
type String
30+
hash String
31+
USER User @relation(fields: [userId], references: [id])
32+
33+
@@unique([id, userId])
34+
}
35+
36+
model Products {
37+
id String @id @default(cuid())
38+
name String
39+
price Int
40+
detail String?
41+
createdAt DateTime @default(now())
42+
updatedAt DateTime @updatedAt
43+
TRANSACTION Transaction[]
44+
}
45+
46+
model Transaction {
47+
id String @unique
48+
userId String
49+
productId String
50+
productName String
51+
price Int
52+
TID String
53+
createdAt DateTime @default(now())
54+
updatedAt DateTime @updatedAt
55+
canceled Boolean @default(false)
56+
PRODUCT Products @relation(fields: [productId], references: [id])
57+
USER User @relation(fields: [userId], references: [id])
58+
59+
@@unique([id, userId])
60+
}

prisma/seed.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import prisma from 'lib/prisma'
2+
3+
const seed = async () => {
4+
await prisma.products.createMany({
5+
data: [
6+
{
7+
name: '프린터 무제한 이용권',
8+
price: 8000,
9+
detail: '한달 동안 프린터를 제한 없이 이용해보세요.',
10+
},
11+
{
12+
name: '아이스크림 무제한 이용권',
13+
price: 9000,
14+
detail: '한달 동안 원하는 아이스크림은 마음껏 골라보세요.',
15+
},
16+
{
17+
name: '카드결제 안되는 상품',
18+
price: 10,
19+
detail: '10원은 카드결제가 되지 않습니다.',
20+
},
21+
],
22+
})
23+
}
24+
25+
seed()

readme.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## 기능
2+
- [x] 로그인
3+
- [x] 회원가입
4+
- [x] 빌링키 발행
5+
- [x] 상품 목록
6+
- [x] 상품 상세
7+
- [x] 결제하기 (빌링키)
8+
- [x] 결제 목록 보기
9+
10+
## 문서
11+
- [ ] 로그인
12+
- [ ] 회원가입
13+
- [ ] 빌링키 발행
14+
- [ ] 상품 목록
15+
- [ ] 상품 상세
16+
- [ ] 결제하기 (빌링키)
17+
- [ ] 결제 목록 보기

src/app.ts

+73
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,86 @@
1+
import cors from 'cors'
2+
import helmet from 'helmet'
13
import express from 'express'
24
import config from './config'
5+
import { readdirSync } from 'fs'
6+
import { join } from 'path/posix'
7+
import bearerToken from 'express-bearer-token'
8+
9+
import identity from './middlewares/identity'
10+
import errorHandler from './middlewares/error'
11+
import validator from './middlewares/validator'
312

413
import type { Application } from 'express'
14+
import type { Routers } from './interface/app'
15+
import type { Request, Response, NextFunction } from 'express'
516

617
export default class {
718
public app: Application
819

920
constructor() {
1021
this.app = express()
22+
this.initializeMiddlewares()
23+
this.initializeRouters()
24+
this.initializeErrorHandler()
25+
}
26+
27+
private errorWrapper(
28+
handler: (
29+
req: Request,
30+
res: Response,
31+
next: NextFunction
32+
) => Promise<void> | void
33+
) {
34+
return (req: Request, res: Response, next: NextFunction) => {
35+
Promise.resolve(handler(req, res, next)).catch(next)
36+
}
37+
}
38+
39+
private initializeMiddlewares(): void {
40+
this.app.use(cors())
41+
this.app.use(helmet())
42+
this.app.use(
43+
bearerToken({
44+
reqKey: 'token',
45+
headerKey: 'Bearer',
46+
})
47+
)
48+
this.app.use(express.json())
49+
this.app.use(express.urlencoded({ extended: true }))
50+
}
51+
52+
private initializeRouters(): void {
53+
const routerPath = join(__dirname, 'routers')
54+
const servicePath = (name: string) => join(routerPath, name, 'index.ts')
55+
56+
const roots = readdirSync(routerPath, { withFileTypes: true })
57+
58+
for (const root of roots) {
59+
if (!root.isDirectory()) {
60+
continue
61+
}
62+
63+
const { default: service } = require(servicePath(root.name))
64+
for (const {
65+
method,
66+
path,
67+
middlewares = [],
68+
handler,
69+
needAuth,
70+
validation,
71+
} of (service as Routers).routers) {
72+
this.app[method](join(service.root, path), [
73+
...(needAuth ? [this.errorWrapper(identity)] : []),
74+
...(validation ? [this.errorWrapper(validator(validation))] : []),
75+
...middlewares.map(this.errorWrapper),
76+
this.errorWrapper(handler),
77+
])
78+
}
79+
}
80+
}
81+
82+
private initializeErrorHandler(): void {
83+
this.app.use(errorHandler)
1184
}
1285

1386
public listen(port = config.port): void {

src/config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,11 @@ const check = (key: string, defaultValue?: string): string => {
1212

1313
export default {
1414
port: check('PORT', '3000'),
15+
16+
jwtSecret: check('JWT_SECRET'),
17+
jwtAccessLife: check('JWT_ACCESS_LIFE', '1h'),
18+
jwtRefreshLife: check('JWT_REFRESH_LIFE', '1m'),
19+
20+
storeId: check('STORE_ID'),
21+
storeKey: check('STORE_KEY'),
1522
}

src/interface/app.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { RequestHandler, Request } from 'express'
2+
3+
export type Validation = {
4+
type: 'path' | 'body' | 'query' | 'params',
5+
body: { [key: string]: any }
6+
}
7+
8+
export type Routers = {
9+
root: string
10+
routers: {
11+
path: string
12+
method:
13+
| 'all'
14+
| 'get'
15+
| 'post'
16+
| 'put'
17+
| 'delete'
18+
| 'patch'
19+
| 'options'
20+
| 'head'
21+
needAuth: boolean
22+
middlewares?: RequestHandler[]
23+
handler: RequestHandler
24+
validation?: Validation
25+
}[]
26+
}

src/interface/body.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { Request } from 'express'
2+
import type * as core from 'express-serve-static-core'
3+
4+
export type RequestBody<T> = Request<core.ParamsDictionary, any, T>
5+
6+
export type SignInRequest = RequestBody<{
7+
email: string
8+
password: string
9+
}>
10+
11+
export type SignUpRequest = SignInRequest
12+
13+
export type KeyRequest = RequestBody<{
14+
IDNo: string
15+
CardNo: string
16+
CardPw: string
17+
ExpYear: string
18+
ExpMonth: string
19+
CardName: string
20+
}>
21+
22+
export type PaymentRequest = RequestBody<{
23+
productId: string
24+
cardId: string
25+
}>
26+
27+
export type CancelRequest = RequestBody<{
28+
transactionId: string
29+
}>

src/interface/nicepay.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
type MapString<T extends string> = Record<T, string>
2+
3+
export type BillKeyRes = MapString<
4+
| 'ResultCode'
5+
| 'ResultMsg'
6+
| 'TID'
7+
| 'BID'
8+
| 'AuthDate'
9+
| 'CardCode'
10+
| 'CardName'
11+
| 'CardCl'
12+
| 'AcquCardCode'
13+
| 'AcquCardName'
14+
>
15+
16+
export type BillkeyApprovalRes = MapString<
17+
| 'ResultCode'
18+
| 'ResultMsg'
19+
| 'TID'
20+
| 'Moid'
21+
| 'Amt'
22+
| 'AuthCode'
23+
| 'AuthDate'
24+
| 'AcquCardCode'
25+
| 'AcquCardName'
26+
| 'CardNo'
27+
| 'CardCode'
28+
| 'CardName'
29+
| 'CardQuota'
30+
| 'CardCl'
31+
| 'CardInterest'
32+
| 'CardCl'
33+
| 'CardInterest'
34+
| 'CcPartCl'
35+
| 'MallReserved'
36+
>
37+
38+
export type BillkeyCancelRes = MapString<
39+
| 'ResultCode'
40+
| 'ResultMsg'
41+
| 'ErrorCD'
42+
| 'ErrorMsg'
43+
| 'CancelAmt'
44+
| 'MID'
45+
| 'Moid'
46+
| 'Signature'
47+
>

src/lib/checkRouter.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import type { Routers as t } from '../interface/app'
2+
export default (r: t): t => r

src/lib/error.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export class HttpError extends Error {
2+
public status: number
3+
public message: string
4+
public code?: string
5+
6+
constructor(status: number, message: string, code?: string) {
7+
super(message)
8+
this.status = status
9+
this.message = message
10+
this.code = code
11+
}
12+
}

src/lib/prisma.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { PrismaClient } from "@prisma/client";
2+
3+
const prisma = new PrismaClient()
4+
5+
export default prisma

src/lib/random.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import crypto from 'crypto'
2+
3+
export default (b: number) => crypto.randomBytes(b).toString('hex')

0 commit comments

Comments
 (0)