From 82e198836a176f48232898afff4e6887f68c49c2 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 7 May 2025 10:18:28 +0900 Subject: [PATCH 1/7] refactor: remove suitable and surveySales API modules to streamline codebase --- src/api/suitable.ts | 95 ------------------------------------------ src/api/surveySales.ts | 71 ------------------------------- 2 files changed, 166 deletions(-) delete mode 100644 src/api/suitable.ts delete mode 100644 src/api/surveySales.ts diff --git a/src/api/suitable.ts b/src/api/suitable.ts deleted file mode 100644 index e8458c4..0000000 --- a/src/api/suitable.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { database } from '@/data' -import { axiosInstance } from '@/libs/axios' - -export interface Suitable { - id?: number - product_name: string - manufacturer: string - roof_material: string - shape: string - support_roof_tile: string - support_roof_tile_memo: string - support_roof_bracket: string - support_roof_bracket_memo: string - yg_anchor: string - yg_anchor_memo: string - rg_roof_tile_part: string - rg_roof_tile_part_memo: string - dido_hunt_support_tile_2: string - dido_hunt_support_tile_2_memo: string - takashima_power_base: string - takashima_power_base_memo: string - takashima_tile_bracket: string - takashima_tile_bracket_memo: string - slate_bracket_4: string - slate_bracket_4_memo: string - slate_single_metal_bracket: string - slate_single_metal_bracket_memo: string - dido_hunt_short_rack_4: string - dido_hunt_short_rack_4_memo: string - takashima_slate_bracket_slate_single: string - takashima_slate_bracket_slate_single_memo: string - df_metal_bracket: string - df_metal_bracket_memo: string - slate_metal_bracket: string - slate_metal_bracket_memo: string - takashima_slate_bracket_metal_roof: string - takashima_slate_bracket_metal_roof_memo: string -} - -export const suitableApi = { - getList: async (): Promise => { - const response = await axiosInstance.get('/api/suitable/list') - console.log('๐Ÿš€ ~ getList: ~ response:', response) - return response.data - }, - - getDetails: async (roofMaterial: string): Promise => { - const response = await axiosInstance.get(`/api/suitable/details?roof-material=${roofMaterial}`) - return response.data - }, - - create: async () => { - const suitableData: Suitable[] = [] - - database.forEach((item) => { - suitableData.push({ - product_name: item[0], - manufacturer: item[1], - roof_material: item[2], - shape: item[3], - support_roof_tile: item[4], - support_roof_tile_memo: item[5], - support_roof_bracket: item[6], - support_roof_bracket_memo: item[7], - yg_anchor: item[8], - yg_anchor_memo: item[9], - rg_roof_tile_part: item[10], - rg_roof_tile_part_memo: item[11], - dido_hunt_support_tile_2: item[12], - dido_hunt_support_tile_2_memo: item[13], - takashima_power_base: item[14], - takashima_power_base_memo: item[15], - takashima_tile_bracket: item[16], - takashima_tile_bracket_memo: item[17], - slate_bracket_4: item[18], - slate_bracket_4_memo: item[19], - slate_single_metal_bracket: item[20], - slate_single_metal_bracket_memo: item[21], - dido_hunt_short_rack_4: item[22], - dido_hunt_short_rack_4_memo: item[23], - takashima_slate_bracket_slate_single: item[24], - takashima_slate_bracket_slate_single_memo: item[25], - df_metal_bracket: item[26], - df_metal_bracket_memo: item[27], - slate_metal_bracket: item[28], - slate_metal_bracket_memo: item[29], - takashima_slate_bracket_metal_roof: item[30], - takashima_slate_bracket_metal_roof_memo: item[31], - }) - }) - - const response = await axiosInstance.post('/api/suitable', suitableData) - return response.data - }, -} diff --git a/src/api/surveySales.ts b/src/api/surveySales.ts deleted file mode 100644 index e54f856..0000000 --- a/src/api/surveySales.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { axiosInstance } from '@/libs/axios' - -export interface SurveySalesBasicInfo { - id?: number - representative: String - store: String | null - construction_point: String | null - investigation_date: String | null - building_name: String | null - customer_name: String | null - post_code: String | null - address: String | null - address_detail: String | null - submission_status: Boolean - submission_date?: String | null - detail_info?: SurveySalesDetailInfo | null -} - -export interface SurveySalesDetailInfo { - id?: number - contract_capacity: String | null - retail_company: String | null - supplementary_facilities: Number | null - supplementary_facilities_etc: String | null - installation_system: Number | null - installation_system_etc: String | null - construction_year: Number | null - construction_year_etc: String | null - roof_material: Number | null - roof_material_etc: String | null - roof_shape: Number | null - roof_shape_etc: String | null - roof_slope: String | null - house_structure: Number | null - house_structure_etc: String | null - rafter_material: Number | null - rafter_material_etc: String | null - rafter_size: Number | null - rafter_size_etc: String | null - rafter_pitch: Number | null - rafter_pitch_etc: String | null - rafter_direction: Number | null - open_field_plate_kind: Number | null - open_field_plate_kind_etc: String | null - open_field_plate_thickness: String | null - leak_trace: Boolean | null - waterproof_material: Number | null - waterproof_material_etc: String | null - insulation_presence: Number | null - insulation_presence_etc: String | null - structure_order: Number | null - structure_order_etc: String | null - installation_availability: Number | null - installation_availability_etc: String | null - memo: String | null -} - -export const surveySalesApi = { - create: async (data: SurveySalesBasicInfo): Promise => { - const response = await axiosInstance.post('/api/survey-sales', data) - return response.data - }, - getList: async (): Promise => { - const response = await axiosInstance.get('/api/survey-sales') - return response.data - }, - update: async (data: SurveySalesBasicInfo): Promise => { - const response = await axiosInstance.put(`/api/survey-sales`, data) - return response.data - }, -} From 2166b7836e6b67aef8e14d7f43fa313969b98980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Wed, 7 May 2025 15:08:33 +0900 Subject: [PATCH 2/7] =?UTF-8?q?snake=20case=20->=20camel=20case=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libs/axios.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/libs/axios.ts b/src/libs/axios.ts index 4fa4554..9f3aa8d 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -22,9 +22,43 @@ axiosInstance.interceptors.request.use( // Response interceptor axiosInstance.interceptors.response.use( - (response) => response, + (response) => transferResponse(response), (error) => { // ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง return Promise.reject(error) }, ) + +// response๋ฐ์ดํ„ฐ๊ฐ€ array, object์— ๋”ฐ๋ผ ๋ถ„๊ธฐํ•˜์—ฌ ํ‚ค ๋ณ€ํ™˜ +const transferResponse = (response: any) => { + if (!response.data) return response.data + + // ๋ฐฐ์—ด์ธ ๊ฒฝ์šฐ ๊ฐ ๊ฐ์ฒด์˜ ํ‚ค๋ฅผ ๋ณ€ํ™˜ + if (Array.isArray(response.data)) { + return response.data.map((item: any) => transformObjectKeys(item)) + } + + // ๋‹จ์ผ ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ + return transformObjectKeys(response.data) +} + +// camel case object ๋ฐ˜ํ™˜ +const transformObjectKeys = (obj: any): any => { + if (Array.isArray(obj)) { + return obj.map(transformObjectKeys) + } + + if (obj !== null && typeof obj === 'object') { + return Object.keys(obj).reduce((acc: any, key: string) => { + const camelKey = snakeToCamel(key) + acc[camelKey] = transformObjectKeys(obj[key]) + return acc + }, {}) + } + + return obj +} +// snake case to camel case +const snakeToCamel = (str: string): string => { + return str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', '')) +} From 5048fe1c0831b9f81f712693ddab22ec0927d9b1 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 7 May 2025 15:17:01 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20axios=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 6 +++- .env.production | 6 +++- next.config.ts | 8 +++-- src/app/api/auth/route.ts | 14 ++++++++ src/components/Login.tsx | 72 +++++++++++++++++++++++++++++++++++---- src/libs/axios.ts | 19 ++++++----- 6 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 src/app/api/auth/route.ts diff --git a/.env.development b/.env.development index 53e90fd..c8a3ab5 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,7 @@ # ๋ชจ๋ฐ”์ผ ๋””๋ฐ”์ด์Šค๋กœ ๋กœ์ปฌ ์„œ๋ฒ„ ํ™•์ธํ•˜๋ ค๋ฉด ์ž์‹  IP ์ฃผ์†Œ๋กœ ๋ณ€๊ฒฝ # ๋‹ค์‹œ ๋กœ์ปฌ์—์„œ ๊ฐœ๋ฐœํ• ๋•Œ๋Š” localhost๋กœ ๋ณ€๊ฒฝ -NEXT_PUBLIC_API_URL=http://localhost:3000 \ No newline at end of file +#route handler +NEXT_PUBLIC_API_URL=http://localhost:3000 + +#qsp ๋กœ๊ทธ์ธ api +NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 \ No newline at end of file diff --git a/.env.production b/.env.production index f5f1630..1d169db 100644 --- a/.env.production +++ b/.env.production @@ -1 +1,5 @@ -NEXT_PUBLIC_API_URL=http://172.30.1.35:3000 \ No newline at end of file +#route handler +NEXT_PUBLIC_API_URL=http://172.30.1.35:3000 + +#qsp ๋กœ๊ทธ์ธ api +NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index 1ff5ba6..bc718b5 100644 --- a/next.config.ts +++ b/next.config.ts @@ -9,9 +9,13 @@ const nextConfig: NextConfig = { async rewrites() { return [ { - source: '/api/:path*', - destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, + source: '/api/user/login', + destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`, }, + // { + // source: '/api/:path*', + // destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, + // }, ] }, } diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts new file mode 100644 index 0000000..f9fc859 --- /dev/null +++ b/src/app/api/auth/route.ts @@ -0,0 +1,14 @@ +import { axiosInstance } from '@/libs/axios' +import { NextResponse } from 'next/server' + +export async function POST(request: Request) { + const { loginId, pwd } = await request.json() + + const result = await axiosInstance(`${process.env.NEXT_PUBLIC_QSP_API_URL}`).post(`/api/user/login`, { + loginId, + pwd, + }) + console.log('๐Ÿš€ ~ result ~ result:', result) + + return NextResponse.json('ok') +} diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 8f6e373..c17373f 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -1,16 +1,68 @@ 'use client' -import { useState } from 'react' +import { axiosInstance } from '@/libs/axios' +import { useQuery } from '@tanstack/react-query' +import { useEffect, useReducer, useState } from 'react' +import { AxiosResponse } from 'axios' + +interface AccountState { + loginId: string + pwd: string +} export default function Login() { - const [pwShow, setPwShow] = useState(false) //๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณด์ด๊ธฐ ์ˆจ๊ธฐ๊ธฐ + //๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณด์ด๊ธฐ ์ˆจ๊ธฐ๊ธฐ + const [pwShow, setPwShow] = useState(false) + //ID ์ €์žฅ ์ฒดํฌ๋ฐ•์Šค + const [idSave, setIdSave] = useState(false) + //Q.PARTNERS ์ฒดํฌ๋ฐ•์Šค + const [isPartners, setIsPartners] = useState(false) + //๋กœ๊ทธ์ธ ์ƒํƒœ + const [isLogin, setIsLogin] = useState(false) + + const reducer = (state: AccountState, newState: Partial) => ({ ...state, ...newState }) + const [account, setAccount] = useReducer(reducer, { + loginId: '', + pwd: '', + }) + + const { + data: loginData, + isPending, + error, + } = useQuery({ + queryKey: ['login', 'account'], + queryFn: () => { + const result = axiosInstance('').post(`/api/auth`, { + loginId: account.loginId, + pwd: account.pwd, + }) + return result + }, + enabled: isLogin, + staleTime: 0, + retry: false, + }) + + useEffect(() => { + if (loginData) { + console.log('๐Ÿš€ ~ Login ~ loginData:', loginData) + setIsLogin(false) + } + }, [loginData]) return ( <>
- + setAccount({ loginId: e.target.value })} + /> @@ -18,7 +70,13 @@ export default function Login() {
- + setAccount({ pwd: e.target.value })} + /> @@ -26,19 +84,19 @@ export default function Login() {
- + setIdSave(!idSave)} />
Q.PARTNERS
-
diff --git a/src/libs/axios.ts b/src/libs/axios.ts index 9f3aa8d..db7a625 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -1,16 +1,17 @@ import axios from 'axios' -const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000' - -export const axiosInstance = axios.create({ - baseURL, - headers: { - 'Content-Type': 'application/json', - }, -}) +export const axiosInstance = (url: string) => { + let baseURL = url !== '' || url === undefined ? url : process.env.NEXT_PUBLIC_API_URL + return axios.create({ + baseURL, + headers: { + Accept: 'application/json', + }, + }) +} // Request interceptor -axiosInstance.interceptors.request.use( +axios.interceptors.request.use( (config) => { // ์—ฌ๊ธฐ์— ํ† ํฐ ์ถ”๊ฐ€ ๋“ฑ์˜ ๊ณตํ†ต ๋กœ์ง์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค return config From 05f4fe4e941da4bbae640ad47ebe9ed4041cd6e9 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 7 May 2025 17:16:26 +0900 Subject: [PATCH 4/7] refactor: enhance session management and routing in middleware and API authentication --- next.config.ts | 8 ++--- src/app/@header/default.tsx | 2 +- src/app/api/auth/logout/route.ts | 15 +++++++++ src/app/api/auth/route.ts | 48 +++++++++++++++++++++++++++-- src/app/page.tsx | 8 +++++ src/components/Login.tsx | 29 ++++++++--------- src/components/ui/Main.tsx | 17 ++++++++-- src/components/ui/common/Header.tsx | 28 +++++++++-------- src/libs/axios.ts | 7 +++-- src/libs/session.ts | 45 ++++++++++++++++++++++----- src/middleware.ts | 9 +++--- src/types/Auth.ts | 30 ++++++++++++++++++ 12 files changed, 194 insertions(+), 52 deletions(-) create mode 100644 src/app/api/auth/logout/route.ts create mode 100644 src/types/Auth.ts diff --git a/next.config.ts b/next.config.ts index bc718b5..1a3917a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -12,10 +12,10 @@ const nextConfig: NextConfig = { source: '/api/user/login', destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`, }, - // { - // source: '/api/:path*', - // destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, - // }, + { + source: '/api/:path*', + destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, + }, ] }, } diff --git a/src/app/@header/default.tsx b/src/app/@header/default.tsx index 33c708b..aff8409 100644 --- a/src/app/@header/default.tsx +++ b/src/app/@header/default.tsx @@ -1,5 +1,5 @@ import Header from '@/components/ui/common/Header' -export default function page() { +export default async function page() { return
} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..390f8d1 --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,15 @@ +import { sessionOptions } from '@/libs/session' +import { SessionData } from '@/types/Auth' +import { getIronSession } from 'iron-session' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + session.destroy() + // return redirect('/login') + + return NextResponse.json({ code: 200, message: 'Logout successful' }) +} diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index f9fc859..8cbbc1a 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -1,6 +1,12 @@ -import { axiosInstance } from '@/libs/axios' +import { cookies } from 'next/headers' import { NextResponse } from 'next/server' +import { getIronSession } from 'iron-session' +import { axiosInstance } from '@/libs/axios' +import { sessionOptions } from '@/libs/session' + +import type { SessionData } from '@/types/Auth' + export async function POST(request: Request) { const { loginId, pwd } = await request.json() @@ -10,5 +16,43 @@ export async function POST(request: Request) { }) console.log('๐Ÿš€ ~ result ~ result:', result) - return NextResponse.json('ok') + if (result.data.result.code === 200) { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + console.log('start session edit!') + session.langCd = result.data.data.langCd + session.currPage = result.data.data.currPage + session.rowCount = result.data.data.rowCount + session.startRow = result.data.data.startRow + session.endRow = result.data.data.endRow + session.compCd = result.data.data.compCd + session.agencyStoreId = result.data.data.agencyStoreId + session.storeId = result.data.data.storeId + session.userId = result.data.data.userId + session.category = result.data.data.category + session.userNm = result.data.data.userNm + session.userNmKana = result.data.data.userNmKana + session.telNo = result.data.data.telNo + session.fax = result.data.data.fax + session.email = result.data.data.email + session.lastEditUser = result.data.data.lastEditUser + session.storeGubun = result.data.data.storeGubun + session.pwCurr = result.data.data.pwCurr + session.pwdInitYn = result.data.data.pwdInitYn + session.apprStatCd = result.data.data.apprStatCd + session.loginFailCnt = result.data.data.loginFailCnt + session.loginFailMinYn = result.data.data.loginFailMinYn + session.priceViewStatCd = result.data.data.priceViewStatCd + session.groupId = result.data.data.groupId + session.storeLvl = result.data.data.storeLvl + session.custCd = result.data.data.custCd + session.builderNo = result.data.data.builderNo + session.isLoggedIn = true + console.log('end session edit!') + + await session.save() + } + + return NextResponse.json({ code: 200, message: 'Login is Succecss!!' }) } diff --git a/src/app/page.tsx b/src/app/page.tsx index ac97ef3..b803534 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,14 @@ import Main from '@/components/ui/Main' +import { sessionOptions } from '@/libs/session' +import type { SessionData } from '@/types/Auth' +import { getIronSession } from 'iron-session' +import { cookies } from 'next/headers' export default async function Home() { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + console.log('๐Ÿš€ ~ Home ~ session:', session) + return ( <>
diff --git a/src/components/Login.tsx b/src/components/Login.tsx index c17373f..66a3f0c 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -1,9 +1,9 @@ 'use client' -import { axiosInstance } from '@/libs/axios' +import { useReducer, useState } from 'react' +import { useRouter } from 'next/navigation' import { useQuery } from '@tanstack/react-query' -import { useEffect, useReducer, useState } from 'react' -import { AxiosResponse } from 'axios' +import { axiosInstance } from '@/libs/axios' interface AccountState { loginId: string @@ -11,6 +11,7 @@ interface AccountState { } export default function Login() { + const router = useRouter() //๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณด์ด๊ธฐ ์ˆจ๊ธฐ๊ธฐ const [pwShow, setPwShow] = useState(false) //ID ์ €์žฅ ์ฒดํฌ๋ฐ•์Šค @@ -26,31 +27,31 @@ export default function Login() { pwd: '', }) + interface LoginData { + code: number + message: string | null + } + const { data: loginData, isPending, error, - } = useQuery({ + } = useQuery({ queryKey: ['login', 'account'], - queryFn: () => { - const result = axiosInstance('').post(`/api/auth`, { + queryFn: async () => { + const { data } = await axiosInstance('').post(`/api/auth`, { loginId: account.loginId, pwd: account.pwd, }) - return result + router.push('/') + + return data }, enabled: isLogin, staleTime: 0, retry: false, }) - useEffect(() => { - if (loginData) { - console.log('๐Ÿš€ ~ Login ~ loginData:', loginData) - setIsLogin(false) - } - }, [loginData]) - return ( <>
diff --git a/src/components/ui/Main.tsx b/src/components/ui/Main.tsx index a9eb336..37de8e7 100644 --- a/src/components/ui/Main.tsx +++ b/src/components/ui/Main.tsx @@ -1,16 +1,27 @@ 'use client' import { useHeaderStore } from '@/store/header' -import { useRouter } from 'next/navigation' +import { useSideNavState } from '@/store/sideNavState' +import { usePathname, useRouter } from 'next/navigation' import { useEffect } from 'react' export default function Main() { const router = useRouter() + const pathname = usePathname() const { setBackBtn } = useHeaderStore() + const { reset } = useSideNavState() + /** + * ํ—ค๋” ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ ์ปจํŠธ๋กค + * ์‚ฌ์ด๋“œ๋ฐ” ์ดˆ๊ธฐํ™” ์ปจํŠธ๋กค + */ useEffect(() => { - setBackBtn(false) - }, []) + if (pathname === '/') { + setBackBtn(false) + } + //์‚ฌ์ด๋“œ๋ฐ” ์ดˆ๊ธฐํ™” + reset() + }, [pathname]) return ( <> diff --git a/src/components/ui/common/Header.tsx b/src/components/ui/common/Header.tsx index 580545d..443e078 100644 --- a/src/components/ui/common/Header.tsx +++ b/src/components/ui/common/Header.tsx @@ -1,16 +1,17 @@ 'use client' -import { useEffect, useState } from 'react' - import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import { Swiper, SwiperSlide } from 'swiper/react' +import { useSideNavState } from '@/store/sideNavState' +import { useHeaderStore } from '@/store/header' + import type { HeaderProps } from '@/types/Header' import 'swiper/css' -import { useSideNavState } from '@/store/sideNavState' +import { axiosInstance } from '@/libs/axios' // type HeaderProps = { // name: string //header ์ด๋ฆ„ @@ -20,27 +21,26 @@ import { useSideNavState } from '@/store/sideNavState' export default function Header({ name }: HeaderProps) { const router = useRouter() const pathname = usePathname() - const { sideNavIsOpen, setSideNavIsOpen, reset } = useSideNavState() - const [isShowBackBtn, setIsShowBackBtn] = useState(false) + const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState() + const { backBtn } = useHeaderStore() if (pathname === '/login') { return null } - useEffect(() => { - if (pathname !== '/') { - setIsShowBackBtn(true) + const handleLogout = async () => { + const { data } = await axiosInstance(null).get('/api/auth/logout') + if (data.code === 200) { + router.push('/login') } - //์‚ฌ์ด๋“œ๋ฐ” ์ดˆ๊ธฐํ™” - reset() - }, [pathname]) + } return ( <>
- {isShowBackBtn && ( + {backBtn && (
@@ -112,7 +112,9 @@ export default function Header({ name }: HeaderProps) {
  • - +
  • diff --git a/src/libs/axios.ts b/src/libs/axios.ts index db7a625..5f1b076 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -1,7 +1,8 @@ import axios from 'axios' -export const axiosInstance = (url: string) => { - let baseURL = url !== '' || url === undefined ? url : process.env.NEXT_PUBLIC_API_URL +export const axiosInstance = (url: string | null | undefined) => { + const baseURL = url || process.env.NEXT_PUBLIC_API_URL + return axios.create({ baseURL, headers: { @@ -22,7 +23,7 @@ axios.interceptors.request.use( ) // Response interceptor -axiosInstance.interceptors.response.use( +axios.interceptors.response.use( (response) => transferResponse(response), (error) => { // ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง diff --git a/src/libs/session.ts b/src/libs/session.ts index a61c755..21a0479 100644 --- a/src/libs/session.ts +++ b/src/libs/session.ts @@ -1,5 +1,7 @@ +import type { SessionData } from '@/types/Auth' import { getIronSession, SessionOptions } from 'iron-session' import { cookies } from 'next/headers' +import { redirect } from 'next/navigation' export const sessionOptions: SessionOptions = { cookieName: 'onsitesurvey_session', @@ -14,15 +16,34 @@ export const sessionOptions: SessionOptions = { }, } -export interface SessionData { - username: string | null - email: string | null - isLoggedIn: boolean -} - export const defaultSession: SessionData = { - username: '', - email: '', + langCd: null, + currPage: 0, + rowCount: 0, + startRow: 0, + endRow: 0, + compCd: null, + agencyStoreId: null, + storeId: null, + userId: null, + category: null, + userNm: null, + userNmKana: null, + telNo: null, + fax: null, + email: null, + lastEditUser: null, + storeGubun: null, + pwCurr: null, + pwdInitYn: null, + apprStatCd: null, + loginFailCnt: null, + loginFailMinYn: null, + priceViewStatCd: null, + groupId: null, + storeLvl: null, + custCd: null, + builderNo: null, isLoggedIn: false, } @@ -31,3 +52,11 @@ export const getSession = async () => { const session = await getIronSession(cookieStore, sessionOptions) return session } + +export const logout = async () => { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + session.destroy() + return redirect('/login') +} diff --git a/src/middleware.ts b/src/middleware.ts index 5fe31f3..115fd8a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,16 +2,17 @@ import { NextResponse } from 'next/server' import { cookies } from 'next/headers' import type { NextRequest } from 'next/server' import { getIronSession } from 'iron-session' -import { SessionData, sessionOptions } from './libs/session' +import { sessionOptions } from './libs/session' +import type { SessionData } from './types/Auth' export async function middleware(request: NextRequest) { const cookieStore = await cookies() const session = await getIronSession(cookieStore, sessionOptions) // todo: ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ ์ฃผ์„ ํ•ด์ œ - // if (!session.isLoggedIn) { - // return NextResponse.redirect(new URL('/login', request.url)) - // } + if (!session.isLoggedIn) { + return NextResponse.redirect(new URL('/login', request.url)) + } return NextResponse.next() } diff --git a/src/types/Auth.ts b/src/types/Auth.ts new file mode 100644 index 0000000..8920afc --- /dev/null +++ b/src/types/Auth.ts @@ -0,0 +1,30 @@ +export interface SessionData { + langCd: null + currPage: Number | null + rowCount: Number | null + startRow: Number | null + endRow: Number | null + compCd: null + agencyStoreId: null + storeId: null + userId: null + category: null + userNm: null + userNmKana: null + telNo: null + fax: null + email: null + lastEditUser: null + storeGubun: null + pwCurr: null + pwdInitYn: null + apprStatCd: null + loginFailCnt: Number | null + loginFailMinYn: null + priceViewStatCd: null + groupId: null + storeLvl: null + custCd: null + builderNo: null + isLoggedIn: boolean +} From 692d2b7d8427776d4e4b5387fb60b86ac48a8634 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 7 May 2025 17:37:09 +0900 Subject: [PATCH 5/7] refactor: comment out login redirection in middleware for future implementation --- src/middleware.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 115fd8a..1bcfd96 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -10,9 +10,9 @@ export async function middleware(request: NextRequest) { const session = await getIronSession(cookieStore, sessionOptions) // todo: ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ ์ฃผ์„ ํ•ด์ œ - if (!session.isLoggedIn) { - return NextResponse.redirect(new URL('/login', request.url)) - } + // if (!session.isLoggedIn) { + // return NextResponse.redirect(new URL('/login', request.url)) + // } return NextResponse.next() } From e355ad537879d6ef9eb344ad0e8b1828d866bccb Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Thu, 8 May 2025 15:41:12 +0900 Subject: [PATCH 6/7] refactor: clean up User and MS_SUITABLE models in Prisma schema by removing unnecessary comments and optimizing field definitions --- prisma/schema.prisma | 122 +++++++------------------------------------ 1 file changed, 18 insertions(+), 104 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a58b277..af610d0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,7 +7,6 @@ datasource db { url = env("DATABASE_URL") } -// ์‚ฌ์šฉ์ž ์ •๋ณด model User { id Int @id @default(autoincrement()) username String @unique @@ -20,186 +19,101 @@ model User { updated_at DateTime @updatedAt } -// ์ง€๋ถ•์žฌ ์ ํ•ฉ์„ฑ ์ •๋ณด model MS_SUITABLE { - //์ผ๋ จ๋ฒˆํ˜ธ id Int @id @default(autoincrement()) - //์ œํ’ˆ๋ช… product_name String @db.VarChar(200) - //์ œ์กฐ์—…์ฒด๋ช… manufacturer String? @db.VarChar(200) - //์ง€๋ถ•์žฌ roof_material String? @db.VarChar(100) - //๊ธˆ๊ตฌํ˜•ํƒœ(์‡ ๋ถ™์ดํ˜•) shape String? @db.VarChar(200) - //์ง€์ง€ ๊ธฐ์™€ - support_roof_tile String? @db.VarChar(1) - //์ง€์ง€ ๊ธฐ์™€ ๋ฉ”๋ชจ + support_roof_tile String? @db.VarChar(2) support_roof_tile_memo String? @db.VarChar(500) - //์ง€์ง€ ๊ธˆ๊ตฌ support_roof_bracket String? @db.VarChar(200) - //์ง€์ง€ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ support_roof_bracket_memo String? @db.VarChar(500) - //yg ์•ต์ปค yg_anchor String? @db.VarChar(200) - //yg ์•ต์ปค ๋ฉ”๋ชจ yg_anchor_memo String? @db.VarChar(500) - //rg ์ง€๋ถ•ํŒ rg_roof_tile_part String? @db.VarChar(200) - //rg ์ง€๋ถ•ํŒ ๋ฉ”๋ชจ rg_roof_tile_part_memo String? @db.VarChar(500) - //๋‹ค์ด๋„ํ—ŒํŠธ ์ง€์ง€ ๊ธฐ์™€2 dido_hunt_support_tile_2 String? @db.VarChar(200) - //๋‹ค์ด๋„ํ—ŒํŠธ ์ง€์ง€ ๊ธฐ์™€2 ๋ฉ”๋ชจ dido_hunt_support_tile_2_memo String? @db.VarChar(500) - //ํƒ€์นด์‹œ๋งˆ ํŒŒ์›Œ ๋ฒ ์ด์Šค takashima_power_base String? @db.VarChar(200) - //ํƒ€์นด์‹œ๋งˆ ํŒŒ์›Œ ๋ฒ ์ด์Šค ๋ฉ”๋ชจ takashima_power_base_memo String? @db.VarChar(500) - //ํƒ€์นด์‹œ๋งˆ์šฉ ๊ธˆ๊ตฌ takashima_tile_bracket String? @db.VarChar(200) - //ํƒ€์นด์‹œ๋งˆ์šฉ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ takashima_tile_bracket_memo String? @db.VarChar(500) - //์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ4 slate_bracket_4 String? @db.VarChar(200) - //์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ4 ๋ฉ”๋ชจ slate_bracket_4_memo String? @db.VarChar(500) - //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ(์Šฌ๋ ˆ์ดํŠธ, ์‹ฑ๊ธ€) slate_single_metal_bracket String? @db.VarChar(200) - //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ(์Šฌ๋ ˆ์ดํŠธ, ์‹ฑ๊ธ€) slate_single_metal_bracket_memo String? @db.VarChar(500) - //๋‹ค์ด๋„ํ—ŒํŠธ ์งง์€ ํŠธ๋ž™4 dido_hunt_short_rack_4 String? @db.VarChar(200) - //๋‹ค์ด๋„ํ—ŒํŠธ ์งง์€ ํŠธ๋ž™4 ๋ฉ”๋ชจ dido_hunt_short_rack_4_memo String? @db.VarChar(500) - //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ takashima_slate_bracket_slate_single String? @db.VarChar(200) - //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ takashima_slate_bracket_slate_single_memo String? @db.VarChar(500) - //df ํŒ๊ธˆ ๊ธˆ๊ตฌ df_metal_bracket String? @db.VarChar(200) - //df ํŒ๊ธˆ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ df_metal_bracket_memo String? @db.VarChar(500) - //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) slate_metal_bracket String? @db.VarChar(200) - //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) ๋ฉ”๋ชจ slate_metal_bracket_memo String? @db.VarChar(500) - //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) takashima_slate_bracket_metal_roof String? @db.VarChar(200) - //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) ๋ฉ”๋ชจ takashima_slate_bracket_metal_roof_memo String? @db.VarChar(500) created_at DateTime @default(now()) updated_at DateTime @updatedAt } -// ์กฐ์‚ฌ ๋งค๋ฌผ ๊ธฐ๋ณธ ์ •๋ณด model SD_SERVEY_SALES_BASIC_INFO { - //์ผ๋ จ๋ฒˆํ˜ธ id Int @id @default(autoincrement()) - //๋‹ด๋‹น์ž๋ช… representative String @db.VarChar(200) - //ํŒ๋งค์  store String? @db.VarChar(200) - //์‹œ๊ณต์  construction_point String? @db.VarChar(200) - //ํ˜„์žฌ ์กฐ์‚ฌ์ผ investigation_date String? @db.VarChar(10) - //๊ฑด๋ฌผ๋ช… building_name String? @db.VarChar(200) - //๊ณ ๊ฐ๋ช… customer_name String? @db.VarChar(200) - //์šฐํŽธ๋ฒˆํ˜ธ post_code String? @db.VarChar(10) - //์ฃผ์†Œ address String? @db.VarChar(200) - //์ƒ์„ธ์ฃผ์†Œ address_detail String? @db.VarChar(300) - //์ œ์ถœ์ƒํƒœ submission_status Boolean @default(false) - //์ œ์ถœ์ผ submission_date DateTime? @db.Date - //์ƒ์„ธ์ •๋ณด - detail_info SD_SERVEY_SALES_DETAIL_INFO? created_at DateTime @default(now()) updated_at DateTime @updatedAt + detail_info SD_SERVEY_SALES_DETAIL_INFO? } -// ์กฐ์‚ฌ ๋งค๋ฌผ ์ „๊ธฐ ์ง€๋ถ• ์ •๋ณด model SD_SERVEY_SALES_DETAIL_INFO { - //์ผ๋ จ๋ฒˆํ˜ธ id Int @id @default(autoincrement()) - //์ „๊ธฐ๊ณ„์•ฝ ์šฉ๋Ÿ‰ contract_capacity String? @db.VarChar(20) - //์ „๊ธฐ ์†Œ๋งค ํšŒ์‚ฌ retail_company String? @db.VarChar(100) - //์ „๊ธฐ ๋ถ€๋Œ€ ์„ค๋น„ - supplementary_facilities Int? @db.Int - //์ „๊ธฐ ๋ถ€๋Œ€ ์„ค๋น„ ๊ธฐํƒ€ + supplementary_facilities Int? supplementary_facilities_etc String? @db.VarChar(200) - //์„ค์น˜ ํฌ๋ง ์‹œ์Šคํ…œ - installation_system Int? @db.Int - //์„ค์น˜ ํฌ๋ง ์‹œ์Šคํ…œ ๊ธฐํƒ€ + installation_system Int? installation_system_etc String? @db.VarChar(200) - //๊ฑด์ถ• ์—ฐ์ˆ˜ - construction_year Int? @db.Int - //๊ฑด์ถ• ์—ฐ์ˆ˜ ๊ธฐํƒ€ + construction_year Int? construction_year_etc String? @db.VarChar(200) - //์ง€๋ถ•์žฌ - roof_material Int? @db.Int - //์ง€๋ถ•์žฌ ๊ธฐํƒ€ + roof_material Int? roof_material_etc String? @db.VarChar(200) - //์ง€๋ถ• ๋ชจ์–‘ - roof_shape Int? @db.Int - //์ง€๋ถ• ๋ชจ์–‘ ๊ธฐํƒ€ + roof_shape Int? roof_shape_etc String? @db.VarChar(200) - //์ง€๋ถ• ๊ฒฝ์‚ฌ๋„ roof_slope String? @db.VarChar(5) - //์ฃผํƒ ๊ตฌ์กฐ - house_structure Int? @db.Int - //์ฃผํƒ ๊ตฌ์กฐ ๊ธฐํƒ€ + house_structure Int? house_structure_etc String? @db.VarChar(200) - //์„œ๊นŒ๋ž˜ ์žฌ์งˆ - rafter_material Int? @db.Int - //์„œ๊นŒ๋ž˜ ์žฌ์งˆ ๊ธฐํƒ€ + rafter_material Int? rafter_material_etc String? @db.VarChar(200) - //์„œ๊นŒ๋ž˜ ํฌ๊ธฐ - rafter_size Int? @db.Int - //์„œ๊นŒ๋ž˜ ํฌ๊ธฐ ๊ธฐํƒ€ + rafter_size Int? rafter_size_etc String? @db.VarChar(200) - //์„œ๊นŒ๋ž˜ ํ”ผ์น˜ - rafter_pitch Int? @db.Int - //์„œ๊นŒ๋ž˜ ํ”ผ์น˜ ๊ธฐํƒ€ + rafter_pitch Int? rafter_pitch_etc String? @db.VarChar(200) - //์„œ๊นŒ๋ž˜ ๋ฐฉํ–ฅ - rafter_direction Int? @db.Int - //๋…ธ์ง€ํŒ ์ข…๋ฅ˜ - open_field_plate_kind Int? @db.Int - //๋…ธ์ง€ํŒ ์ข…๋ฅ˜ ๊ธฐํƒ€ + rafter_direction Int? + open_field_plate_kind Int? open_field_plate_kind_etc String? @db.VarChar(200) - //๋…ธ์ง€ํŒ ๋‘๊ป˜ open_field_plate_thickness String? @db.VarChar(5) - //๋ˆ„์ˆ˜ ํ”์  leak_trace Boolean? @default(false) - //๋ฐฉ์ˆ˜์žฌ ์ข…๋ฅ˜ - waterproof_material Int? @db.Int - //๋ฐฉ์ˆ˜์žฌ ์ข…๋ฅ˜ ๊ธฐํƒ€ + waterproof_material Int? waterproof_material_etc String? @db.VarChar(200) - //๋‹จ์—ด์žฌ ์—ฌ๋ถ€ - insulation_presence Int? @db.Int - //๋‹จ์—ด์žฌ ์—ฌ๋ถ€ ๊ธฐํƒ€ + insulation_presence Int? insulation_presence_etc String? @db.VarChar(200) - //์ง€๋ถ• ๊ตฌ์กฐ ์ˆœ์„œ - structure_order Int? @db.Int - //์ง€๋ถ• ๊ตฌ์กฐ ์ˆœ์„œ ๊ธฐํƒ€ + structure_order Int? structure_order_etc String? @db.VarChar(200) - //์„ค์น˜ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ - installation_availability Int? @db.Int - //์„ค์น˜ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ๊ธฐํƒ€ + installation_availability Int? installation_availability_etc String? @db.VarChar(200) - //๋ฉ”๋ชจ memo String? @db.VarChar(500) created_at DateTime @default(now()) updated_at DateTime @updatedAt - basic_info SD_SERVEY_SALES_BASIC_INFO @relation(fields: [basic_info_id], references: [id]) basic_info_id Int @unique + basic_info SD_SERVEY_SALES_BASIC_INFO @relation(fields: [basic_info_id], references: [id]) } From 0d12239460e0332d00fc7762a05c254ab2f7db31 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Thu, 8 May 2025 15:47:29 +0900 Subject: [PATCH 7/7] refactor: expand MS_SUITABLE model in Prisma schema with detailed field definitions and comments for clarity --- prisma/schema.prisma | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index af610d0..e11395a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,39 +19,73 @@ model User { updated_at DateTime @updatedAt } +// ์ง€๋ถ•์žฌ ์ ํ•ฉ์„ฑ ์ •๋ณด model MS_SUITABLE { + //์ผ๋ จ๋ฒˆํ˜ธ id Int @id @default(autoincrement()) + //์ œํ’ˆ๋ช… product_name String @db.VarChar(200) + //์ œ์กฐ์—…์ฒด๋ช… manufacturer String? @db.VarChar(200) + //์ง€๋ถ•์žฌ roof_material String? @db.VarChar(100) + //๊ธˆ๊ตฌํ˜•ํƒœ(์‡ ๋ถ™์ดํ˜•) shape String? @db.VarChar(200) + //์ง€์ง€ ๊ธฐ์™€ support_roof_tile String? @db.VarChar(2) + //์ง€์ง€ ๊ธฐ์™€ ๋ฉ”๋ชจ support_roof_tile_memo String? @db.VarChar(500) + //์ง€์ง€ ๊ธˆ๊ตฌ support_roof_bracket String? @db.VarChar(200) + //์ง€์ง€ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ support_roof_bracket_memo String? @db.VarChar(500) + //yg ์•ต์ปค yg_anchor String? @db.VarChar(200) + //yg ์•ต์ปค ๋ฉ”๋ชจ yg_anchor_memo String? @db.VarChar(500) + //rg ์ง€๋ถ•ํŒ rg_roof_tile_part String? @db.VarChar(200) + //rg ์ง€๋ถ•ํŒ ๋ฉ”๋ชจ rg_roof_tile_part_memo String? @db.VarChar(500) + //๋‹ค์ด๋„ํ—ŒํŠธ ์ง€์ง€ ๊ธฐ์™€2 dido_hunt_support_tile_2 String? @db.VarChar(200) + //๋‹ค์ด๋„ํ—ŒํŠธ ์ง€์ง€ ๊ธฐ์™€2 ๋ฉ”๋ชจ dido_hunt_support_tile_2_memo String? @db.VarChar(500) + //ํƒ€์นด์‹œ๋งˆ ํŒŒ์›Œ ๋ฒ ์ด์Šค takashima_power_base String? @db.VarChar(200) + //ํƒ€์นด์‹œ๋งˆ ํŒŒ์›Œ ๋ฒ ์ด์Šค ๋ฉ”๋ชจ takashima_power_base_memo String? @db.VarChar(500) + //ํƒ€์นด์‹œ๋งˆ์šฉ ๊ธˆ๊ตฌ takashima_tile_bracket String? @db.VarChar(200) + //ํƒ€์นด์‹œ๋งˆ์šฉ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ takashima_tile_bracket_memo String? @db.VarChar(500) + //์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ4 slate_bracket_4 String? @db.VarChar(200) + //์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ4 ๋ฉ”๋ชจ slate_bracket_4_memo String? @db.VarChar(500) + //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ(์Šฌ๋ ˆ์ดํŠธ, ์‹ฑ๊ธ€) slate_single_metal_bracket String? @db.VarChar(200) + //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ(์Šฌ๋ ˆ์ดํŠธ, ์‹ฑ๊ธ€) slate_single_metal_bracket_memo String? @db.VarChar(500) + //๋‹ค์ด๋„ํ—ŒํŠธ ์งง์€ ํŠธ๋ž™4 dido_hunt_short_rack_4 String? @db.VarChar(200) + //๋‹ค์ด๋„ํ—ŒํŠธ ์งง์€ ํŠธ๋ž™4 ๋ฉ”๋ชจ dido_hunt_short_rack_4_memo String? @db.VarChar(500) + //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ takashima_slate_bracket_slate_single String? @db.VarChar(200) + //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ takashima_slate_bracket_slate_single_memo String? @db.VarChar(500) + //df ํŒ๊ธˆ ๊ธˆ๊ตฌ df_metal_bracket String? @db.VarChar(200) + //df ํŒ๊ธˆ ๊ธˆ๊ตฌ ๋ฉ”๋ชจ df_metal_bracket_memo String? @db.VarChar(500) + //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) slate_metal_bracket String? @db.VarChar(200) + //์Šฌ๋ ˆ์ดํŠธ ํŒ๊ธˆ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) ๋ฉ”๋ชจ slate_metal_bracket_memo String? @db.VarChar(500) + //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) takashima_slate_bracket_metal_roof String? @db.VarChar(200) + //ํƒ€์นด์‹œ๋งˆ ์Šฌ๋ ˆ์ดํŠธ ๊ธˆ๊ตฌ(๊ธˆ์† ์ง€๋ถ•) ๋ฉ”๋ชจ takashima_slate_bracket_metal_roof_memo String? @db.VarChar(500) created_at DateTime @default(now()) updated_at DateTime @updatedAt