commit fd3426866dda07bcb1d1854f28aefba5b71cee8a
parent eb4d4dcc449faf0a6e79af02b74a458952893b03
Author: Yongbin Kim <iam@yongbin.kim>
Date: Thu, 19 Jan 2023 14:24:42 +0900
lint: 코드 정리
Signed-off-by: Yongbin Kim <iam@yongbin.kim>
Diffstat:
23 files changed, 132 insertions(+), 109 deletions(-)
diff --git a/components/contexts/TokenContext.tsx b/components/contexts/TokenContext.tsx
@@ -12,7 +12,8 @@ export interface AccessTokenPayload extends JwtPayload {
}
const TokenContext = createContext<AccessTokenPayload | null>(null)
-const TokenRefreshContext = createContext<() => void>(() => {})
+const TokenRefreshContext = createContext<() => void>(() => {
+})
export function TokenProvider ({ children }: { children: ReactNode }) {
const [accessToken, setAccessToken] = useState<AccessTokenPayload | null>(null)
@@ -40,7 +41,7 @@ export function TokenProvider ({ children }: { children: ReactNode }) {
console.log('TokenContext: refresh token')
fetch('/api/auth/refresh', {
- method: 'POST'
+ method: 'POST',
}).then(res => {
// 갱신하지 못했을 때에는 무시
if (res.status !== 200) {
@@ -66,7 +67,7 @@ export function TokenProvider ({ children }: { children: ReactNode }) {
const trigger = useCallback(
() => setCheckCounter(v => v + 1),
- []
+ [],
)
return (
@@ -94,7 +95,7 @@ function getCookie (name: string) {
name = name.toLowerCase()
let cookie = document.cookie
- for (;;) {
+ for (; ;) {
let caps = regexCookie.exec(cookie)
if (caps == null) {
return null
diff --git a/components/elements/Logo.tsx b/components/elements/Logo.tsx
@@ -1,11 +1,17 @@
import styles from './Logo.module.css'
-export default function Logo() {
+export default function Logo () {
return (
<svg className={styles.logo} width="56" height="24">
- <path d="M16.609-3.467h4.578c.906 0 1.582.038 2.027.114.445.075.844.233 1.195.472.352.24.645.559.879.957.235.399.352.846.352 1.34a2.67 2.67 0 0 1-.434 1.477 2.69 2.69 0 0 1-1.175 1.008c.698.203 1.234.549 1.609 1.039.375.489.562 1.065.562 1.726 0 .521-.121 1.028-.363 1.52a3.069 3.069 0 0 1-.992 1.18c-.419.294-.936.475-1.551.542-.385.042-1.315.068-2.789.079h-3.898V-3.467Zm2.312 1.907v2.648h1.516c.901 0 1.461-.013 1.68-.039.395-.047.707-.183.933-.41.227-.227.34-.525.34-.895 0-.354-.098-.641-.293-.863-.195-.221-.486-.355-.871-.402-.229-.026-.888-.039-1.977-.039h-1.328Zm0 4.554v3.063h2.141c.833 0 1.362-.024 1.586-.07.344-.063.623-.215.84-.457.216-.243.324-.567.324-.973 0-.344-.083-.635-.25-.875a1.418 1.418 0 0 0-.723-.524c-.315-.109-.999-.164-2.051-.164h-1.867ZM28.163 7.987V-3.467h4.868c1.224 0 2.113.103 2.668.309a2.64 2.64 0 0 1 1.332 1.098c.333.526.5 1.127.5 1.804 0 .86-.253 1.569-.758 2.129s-1.261.913-2.266 1.059c.5.292.913.612 1.238.961.326.349.765.969 1.317 1.859l1.398 2.235h-2.765l-1.672-2.493c-.594-.89-1-1.451-1.219-1.683a1.774 1.774 0 0 0-.695-.477c-.245-.086-.633-.129-1.164-.129h-.469v4.782h-2.313Zm2.313-6.61h1.711c1.109 0 1.802-.047 2.078-.14.276-.094.492-.256.648-.485.157-.229.235-.515.235-.859 0-.386-.103-.697-.309-.934-.206-.237-.496-.386-.871-.449-.187-.026-.75-.039-1.687-.039h-1.805v2.906Z" transform="translate(-16.511 9.74)"/>
- <path d="M50.038-3.467h-2.515l-1 2.602h-4.578l-.946-2.602h-2.453l4.461 11.454h2.445l4.586-11.454Zm-4.257 4.532-1.579 4.25-1.546-4.25h3.125Z" transform="matrix(1 0 0 1 -16.511 9.74)"/>
- <path d="M51.288 7.987V-3.467h2.25l4.688 7.649v-7.649h2.148V7.987h-2.32L53.437.518v7.469h-2.149ZM62.812-3.467h4.226c.954 0 1.68.073 2.18.219.672.198 1.247.55 1.727 1.055.479.505.843 1.124 1.093 1.855.25.732.375 1.635.375 2.707 0 .943-.117 1.756-.351 2.438-.287.833-.695 1.508-1.227 2.023-.401.391-.942.696-1.625.914-.51.162-1.192.243-2.047.243h-4.351V-3.467Zm2.312 1.938v7.586h1.727c.646 0 1.112-.037 1.398-.109.375-.094.687-.253.934-.477.247-.224.449-.592.605-1.105.157-.513.235-1.213.235-2.098 0-.886-.078-1.565-.235-2.039-.156-.474-.375-.844-.656-1.11a2.207 2.207 0 0 0-1.07-.539c-.323-.073-.956-.109-1.899-.109h-1.039Z" transform="translate(-16.511 9.74)"/>
+ <path
+ d="M16.609-3.467h4.578c.906 0 1.582.038 2.027.114.445.075.844.233 1.195.472.352.24.645.559.879.957.235.399.352.846.352 1.34a2.67 2.67 0 0 1-.434 1.477 2.69 2.69 0 0 1-1.175 1.008c.698.203 1.234.549 1.609 1.039.375.489.562 1.065.562 1.726 0 .521-.121 1.028-.363 1.52a3.069 3.069 0 0 1-.992 1.18c-.419.294-.936.475-1.551.542-.385.042-1.315.068-2.789.079h-3.898V-3.467Zm2.312 1.907v2.648h1.516c.901 0 1.461-.013 1.68-.039.395-.047.707-.183.933-.41.227-.227.34-.525.34-.895 0-.354-.098-.641-.293-.863-.195-.221-.486-.355-.871-.402-.229-.026-.888-.039-1.977-.039h-1.328Zm0 4.554v3.063h2.141c.833 0 1.362-.024 1.586-.07.344-.063.623-.215.84-.457.216-.243.324-.567.324-.973 0-.344-.083-.635-.25-.875a1.418 1.418 0 0 0-.723-.524c-.315-.109-.999-.164-2.051-.164h-1.867ZM28.163 7.987V-3.467h4.868c1.224 0 2.113.103 2.668.309a2.64 2.64 0 0 1 1.332 1.098c.333.526.5 1.127.5 1.804 0 .86-.253 1.569-.758 2.129s-1.261.913-2.266 1.059c.5.292.913.612 1.238.961.326.349.765.969 1.317 1.859l1.398 2.235h-2.765l-1.672-2.493c-.594-.89-1-1.451-1.219-1.683a1.774 1.774 0 0 0-.695-.477c-.245-.086-.633-.129-1.164-.129h-.469v4.782h-2.313Zm2.313-6.61h1.711c1.109 0 1.802-.047 2.078-.14.276-.094.492-.256.648-.485.157-.229.235-.515.235-.859 0-.386-.103-.697-.309-.934-.206-.237-.496-.386-.871-.449-.187-.026-.75-.039-1.687-.039h-1.805v2.906Z"
+ transform="translate(-16.511 9.74)"/>
+ <path
+ d="M50.038-3.467h-2.515l-1 2.602h-4.578l-.946-2.602h-2.453l4.461 11.454h2.445l4.586-11.454Zm-4.257 4.532-1.579 4.25-1.546-4.25h3.125Z"
+ transform="matrix(1 0 0 1 -16.511 9.74)"/>
+ <path
+ d="M51.288 7.987V-3.467h2.25l4.688 7.649v-7.649h2.148V7.987h-2.32L53.437.518v7.469h-2.149ZM62.812-3.467h4.226c.954 0 1.68.073 2.18.219.672.198 1.247.55 1.727 1.055.479.505.843 1.124 1.093 1.855.25.732.375 1.635.375 2.707 0 .943-.117 1.756-.351 2.438-.287.833-.695 1.508-1.227 2.023-.401.391-.942.696-1.625.914-.51.162-1.192.243-2.047.243h-4.351V-3.467Zm2.312 1.938v7.586h1.727c.646 0 1.112-.037 1.398-.109.375-.094.687-.253.934-.477.247-.224.449-.592.605-1.105.157-.513.235-1.213.235-2.098 0-.886-.078-1.565-.235-2.039-.156-.474-.375-.844-.656-1.11a2.207 2.207 0 0 0-1.07-.539c-.323-.073-.956-.109-1.899-.109h-1.039Z"
+ transform="translate(-16.511 9.74)"/>
</svg>
)
}
diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx
@@ -36,10 +36,10 @@ export default function Header () {
</>
) : (
<>
- <NavbarItem href={`/wiki/${userProfile.login_id}`}>
+ <NavbarItem href={`/wiki/`}>
내 위키
</NavbarItem>
- <NavbarItem href={`/users/${userProfile.login_id}`}>
+ <NavbarItem href={`/users/${userProfile.loginId}`}>
{userProfile.nickname}
</NavbarItem>
</>
diff --git a/lib/apierror.ts b/lib/apierror.ts
@@ -4,7 +4,7 @@ export interface ApiError {
field?: string
}
-export function isApiError(obj: any): obj is ApiError {
+export function isApiError (obj: any): obj is ApiError {
return obj.code != null
}
diff --git a/lib/classnames.ts b/lib/classnames.ts
@@ -1,10 +1,10 @@
-export default function classNames(...args: unknown[]): { className: string } {
+export default function classNames (...args: unknown[]): { className: string } {
return {
className: args.reduce<string>((prev, curr) => {
if (typeof curr !== 'string' || curr.length == 0) {
return prev
}
return `${prev} ${curr}`
- }, '')
+ }, '').substring(1),
}
}
diff --git a/lib/email.ts b/lib/email.ts
@@ -1,20 +0,0 @@
-import nodemailer from 'nodemailer'
-
-const transporter = nodemailer.createTransport({
- host: process.env.WIKI_SMTP_HOST ?? 'localhost',
- port: Number(process.env.WIKI_SMTP_PORT) ?? 25,
- secure: process.env.WIKI_SMTP_FORCE_TLS === 'true',
- auth: {
- user: process.env.WIKI_SMTP_USER ?? '',
- pass: process.env.WIKI_SMTP_PASS ?? '',
- }
-})
-
-export const sendEmail = async (to: string, subject: string, html: string) => {
- return await transporter.sendMail({
- from: process.env.WIKI_SMTP_FROM,
- to,
- subject,
- html
- })
-}
diff --git a/lib/email/index.ts b/lib/email/index.ts
@@ -0,0 +1,20 @@
+import nodemailer from 'nodemailer'
+
+const transporter = nodemailer.createTransport({
+ host: process.env.WIKI_SMTP_HOST ?? 'localhost',
+ port: Number(process.env.WIKI_SMTP_PORT) ?? 25,
+ secure: process.env.WIKI_SMTP_FORCE_TLS === 'true',
+ auth: {
+ user: process.env.WIKI_SMTP_USER ?? '',
+ pass: process.env.WIKI_SMTP_PASS ?? '',
+ },
+})
+
+export const sendEmail = async (to: string, subject: string, html: string) => {
+ return await transporter.sendMail({
+ from: process.env.WIKI_SMTP_FROM,
+ to,
+ subject,
+ html,
+ })
+}
diff --git a/lib/email/templates.ts b/lib/email/templates.ts
@@ -0,0 +1,30 @@
+import Handlebars from 'handlebars'
+
+export const emailTemplates = {
+ signupConfirmation: Handlebars.compile(`
+ <html>
+ <body>
+ <div style="max-width:500px;width:100%;margin:0 auto;padding:1rem;font-family:sans-serif;background-color:#fff;margin-top:40px;margin-bottom:60px;border-radius:16px;box-shadow:0px 1px 2px 0px #00000033, 0px 2px 6px 2px #0000001A">
+ <h1>
+ 계정 만들기
+ </h1>
+ <p>
+ 누군가 이 메일 주소로 회원가입을 요청했습니다.<br/>
+ 본인이 하신 요청이 아니라면 이 메일을 무시하셔도 괜찮습니다.
+ </p>
+ <p>
+ 계속하려면 아래 링크로 이동해주세요.<br/>
+ <a href="{{signupURL}}">{{signupURL}}</a>
+ </p>
+ <p>
+ 감사합니다.
+ </p>
+ </div>
+ </body>
+ </html>
+ `),
+}
+
+export function getSignupConfirmationEmailHTML (signupURL: string): string {
+ return emailTemplates.signupConfirmation({ signupURL })
+}
diff --git a/lib/email_templates.ts b/lib/email_templates.ts
@@ -1,30 +0,0 @@
-import Handlebars from 'handlebars'
-
-export const emailTemplates = {
- signupConfirmation: Handlebars.compile(`
- <html>
- <body>
- <div style="max-width:500px;width:100%;margin:0 auto;padding:1rem;font-family:sans-serif;background-color:#fff;margin-top:40px;margin-bottom:60px;border-radius:16px;box-shadow:0px 1px 2px 0px #00000033, 0px 2px 6px 2px #0000001A">
- <h1>
- 계정 만들기
- </h1>
- <p>
- 누군가 이 메일 주소로 회원가입을 요청했습니다.<br/>
- 본인이 하신 요청이 아니라면 이 메일을 무시하셔도 괜찮습니다.
- </p>
- <p>
- 계속하려면 아래 링크로 이동해주세요.<br/>
- <a href="{{signupURL}}">{{signupURL}}</a>
- </p>
- <p>
- 감사합니다.
- </p>
- </div>
- </body>
- </html>
- `),
-}
-
-export function getSignupConfirmationEmailHTML(signupURL: string): string {
- return emailTemplates.signupConfirmation({ signupURL })
-}
diff --git a/lib/env.ts b/lib/env.ts
@@ -1,7 +1,7 @@
-export function getAccessTokenCookieName() {
+export function getAccessTokenCookieName () {
return `${process.env.WIKI_JWT_COOKIE_PREFIX ?? 'wiki_jwt_'}access_token`
}
-export function getRefreshTokenCookieName() {
+export function getRefreshTokenCookieName () {
return `${process.env.WIKI_JWT_COOKIE_PREFIX ?? 'wiki_jwt_'}refresh_token`
}
diff --git a/lib/hooks/use_api.ts b/lib/hooks/use_api.ts
@@ -1,4 +1,4 @@
-import { DependencyList, useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useCallback, useState } from 'react'
export interface UseApiOptions<TBody> extends Omit<RequestInit, 'body'> {
body?: (() => TBody) | TBody
diff --git a/lib/models/login_info.ts b/lib/models/login_info.ts
@@ -10,10 +10,10 @@ export interface LoginInfo {
}
const SQL_GET_LOGIN_INFO = `
- SELECT id, email, password_hash, created_at, updated_at
- FROM logins
- WHERE id = ?
- LIMIT 1
+ SELECT id, email, password_hash, created_at, updated_at
+ FROM logins
+ WHERE id = ?
+ LIMIT 1
`
export async function getLoginInfo (id: number): Promise<LoginInfo | null> {
@@ -34,13 +34,13 @@ export async function getLoginInfo (id: number): Promise<LoginInfo | null> {
}
const SQL_GET_LOGIN_INFO_VIA_EMAIL = `
- SELECT id, email, password_hash, created_at, updated_at
- FROM logins
- WHERE email = ?
- LIMIT 1
+ SELECT id, email, password_hash, created_at, updated_at
+ FROM logins
+ WHERE email = ?
+ LIMIT 1
`
-export async function getLoginInfoViaEmail(email: string): Promise<LoginInfo | null> {
+export async function getLoginInfoViaEmail (email: string): Promise<LoginInfo | null> {
const [rows] = await db.query<RowDataPacket[]>({
sql: SQL_GET_LOGIN_INFO_VIA_EMAIL,
}, [email])
@@ -60,11 +60,11 @@ export async function getLoginInfoViaEmail(email: string): Promise<LoginInfo | n
}
const SQL_CREATE_LOGIN_INFO = `
- INSERT INTO logins (email, password_hash)
- VALUES (?, ?)
+ INSERT INTO logins (email, password_hash)
+ VALUES (?, ?)
`
-export async function createLoginInfo(email: string, passwordHash: string): Promise<number> {
+export async function createLoginInfo (email: string, passwordHash: string): Promise<number> {
const [result] = await db.query<OkPacket>({
sql: SQL_CREATE_LOGIN_INFO,
}, [email, passwordHash])
diff --git a/lib/models/user_profile.ts b/lib/models/user_profile.ts
@@ -2,10 +2,10 @@ import db from '@/lib/db'
import { OkPacket, RowDataPacket } from 'mysql2'
export interface UserProfile {
- login_id: number
+ loginId: number
nickname: string
bio: string
- updated_at: Date
+ updatedAt: Date
}
const SQL_CREATE_USER_PROFILE = `
@@ -42,9 +42,9 @@ export async function getUserProfile (loginId: number): Promise<UserProfile | nu
const row = rows[0]
return {
- login_id: row[0],
+ loginId: row[0],
nickname: row[1],
bio: row[2],
- updated_at: row[3],
+ updatedAt: row[3],
}
}
diff --git a/lib/security/token.ts b/lib/security/token.ts
@@ -14,7 +14,7 @@ export function getTokenSecret () {
* @returns [accessToken, refreshToken, tokenId]
*/
export function signToken (
- uid: number
+ uid: number,
): [string, string, string] {
const tokenId = nanoid()
diff --git a/pages/_document.tsx b/pages/_document.tsx
@@ -1,13 +1,13 @@
import Header from '@/components/layout/Header'
import { Html, Head, Main, NextScript } from 'next/document'
-export default function Document() {
+export default function Document () {
return (
<Html lang="en">
- <Head />
+ <Head/>
<body>
- <Main />
- <NextScript />
+ <Main/>
+ <NextScript/>
</body>
</Html>
)
diff --git a/pages/api/auth/refresh.ts b/pages/api/auth/refresh.ts
@@ -4,7 +4,7 @@ import { verifyToken } from '@/lib/security/token'
import { signAndSendToken } from '@/pages/api/auth/token'
import { NextApiRequest, NextApiResponse } from 'next'
-export default async function handler(
+export default async function handler (
req: NextApiRequest,
res: NextApiResponse,
) {
@@ -25,7 +25,7 @@ export default async function handler(
return
}
- const { tid, uid } = token as { tid: string, uid: number }
+ const { uid } = token as { tid: string, uid: number }
// TODO: Revoke old token
diff --git a/pages/api/auth/token.ts b/pages/api/auth/token.ts
@@ -53,7 +53,7 @@ export function signAndSendToken (
res: NextApiResponse<TokenResponse>,
uid: number,
) {
- const [accessToken, refreshToken, tokenId] = signToken(uid)
+ const [accessToken, refreshToken] = signToken(uid)
res.setHeader('Set-Cookie', [
`${getAccessTokenCookieName()}=${accessToken}; Path=/; SameSite=Strict; Max-Age=${TOKEN_EXPIRES_IN}`,
diff --git a/pages/api/users/signup/[id].ts b/pages/api/users/signup/[id].ts
@@ -7,7 +7,6 @@ import {
import { createUserProfile } from '@/lib/models/user_profile'
import bcrypt from 'bcrypt'
import { NextApiRequest, NextApiResponse } from 'next'
-import { useRouter } from 'next/router'
interface SignupRequest {
password: string
diff --git a/pages/api/users/signup/index.ts b/pages/api/users/signup/index.ts
@@ -1,6 +1,6 @@
import { ApiError, ERR_METHOD_NOT_ALLOWED } from '@/lib/apierror'
import { sendEmail } from '@/lib/email'
-import { getSignupConfirmationEmailHTML } from '@/lib/email_templates'
+import { getSignupConfirmationEmailHTML } from '@/lib/email/templates'
import { createSignupRequest } from '@/lib/models/signup_request'
import { NextApiRequest, NextApiResponse } from 'next'
@@ -32,7 +32,7 @@ export default async function handler (
res.status(200).json({ status: 'ok' })
}
-function getIPAddress(req: NextApiRequest): string {
+function getIPAddress (req: NextApiRequest): string {
const result = req.headers['x-forwarded-for'] ?? req.socket.remoteAddress
if (result == null) {
return ''
diff --git a/pages/index.tsx b/pages/index.tsx
@@ -1,12 +0,0 @@
-import Container from '@/components/layout/Container'
-import Section from '@/components/layout/Section'
-
-export default function Home () {
- return (
- <Container>
- <Section>
- <p>Hello, World!</p>
- </Section>
- </Container>
- )
-}
diff --git a/pages/users/login.tsx b/pages/users/login.tsx
@@ -20,7 +20,7 @@ export default function LoginPage () {
<Section>
<Container>
- <LoginForm />
+ <LoginForm/>
</Container>
</Section>
</>
@@ -38,7 +38,7 @@ function LoginForm () {
{
method: 'POST',
credentials: 'same-origin',
- body: () => ({ email, password })
+ body: () => ({ email, password }),
},
)
diff --git a/pages/users/signup/index.tsx b/pages/users/signup/index.tsx
@@ -34,7 +34,7 @@ function SignUpForm () {
'/api/users/signup',
{
method: 'POST',
- body: () => ({ email })
+ body: () => ({ email }),
},
)
diff --git a/sql/0001_base.sql b/sql/0001_base.sql
@@ -17,7 +17,6 @@ create table signup_requests
confirmed_at datetime null
);
-drop table if exists user_profiles;
create table user_profiles
(
login_id int not null primary key references logins (id)
@@ -26,3 +25,33 @@ create table user_profiles
bio text null,
updated_at datetime null on update current_timestamp
);
+
+create table wikis
+(
+ id int not null auto_increment primary key,
+ owner_id int not null references logins (id)
+ on delete restrict on update cascade,
+ slug varchar(48) not null unique check (slug <> ''),
+ title varchar(255) not null,
+ description text null,
+ created_at datetime not null default current_timestamp,
+ updated_at datetime null on update current_timestamp
+);
+
+create table wiki_pages
+(
+ wiki_id int not null references wikis (id)
+ on delete cascade on update cascade,
+ slug varchar(255) not null check (slug <> ''),
+ title varchar(255) not null check ( title <> '' ),
+ html text not null,
+ created_at datetime not null default current_timestamp,
+ updated_at datetime null on update current_timestamp,
+
+ primary key (wiki_id, slug)
+);
+
+create table wiki_changes
+(
+
+);