From d8341385a4828212ff212744950e96a4dea6ce5e Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Fri, 21 Mar 2025 16:44:01 +0900 Subject: [PATCH 01/14] =?UTF-8?q?chore:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 8 +++++++- .env.production | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index cfc50e71..bd024a3b 100644 --- a/.env.development +++ b/.env.development @@ -8,4 +8,10 @@ SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS" NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin" -NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" \ No newline at end of file +NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" + +AWS_REGION="ap-northeast-2" +AMPLIFY_BUCKET="interplug" +AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" +AWS_SECRET_ACCESS_KEY="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" +AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" diff --git a/.env.production b/.env.production index 09bb27d3..28c4dd86 100644 --- a/.env.production +++ b/.env.production @@ -10,4 +10,10 @@ NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secr # NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="https://q-order.q-cells.jp/eos/login/autoLogin" # NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="https://q-musubi.q-cells.jp/qm/login/autoLogin" NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin" -NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" \ No newline at end of file +NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" + +AWS_REGION="ap-northeast-2" +AMPLIFY_BUCKET="interplug" +AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" +AWS_SECRET_ACCESS_KEY="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" +AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" \ No newline at end of file From 20cef812ae305d12b88e0b42cec36be401e5ea4b Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Fri, 21 Mar 2025 16:47:14 +0900 Subject: [PATCH 02/14] =?UTF-8?q?chore:=20=EB=B8=8C=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=EC=A0=80=EC=9A=A9=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index bd024a3b..4f613c70 100644 --- a/.env.development +++ b/.env.development @@ -14,4 +14,4 @@ AWS_REGION="ap-northeast-2" AMPLIFY_BUCKET="interplug" AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" AWS_SECRET_ACCESS_KEY="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" -AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" +NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" diff --git a/.env.production b/.env.production index 28c4dd86..f0951f7a 100644 --- a/.env.production +++ b/.env.production @@ -16,4 +16,4 @@ AWS_REGION="ap-northeast-2" AMPLIFY_BUCKET="interplug" AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" AWS_SECRET_ACCESS_KEY="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" -AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" \ No newline at end of file +NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" \ No newline at end of file From a7b90621542218c6deb00210741e1ff56c45d880 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 13:58:29 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20s3=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + qcast3.database.sqlite | Bin 16384 -> 16384 bytes src/app/api/image/upload/route.js | 70 ++++++++++++++++++++ src/components/floor-plan/modal/ImgLoad.jsx | 2 +- src/hooks/common/useRefFiles.js | 17 +++-- 5 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/app/api/image/upload/route.js diff --git a/package.json b/package.json index 1f31b9d3..230e1b81 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "serve": "node server.js" }, "dependencies": { + "@aws-sdk/client-s3": "^3.772.0", "ag-grid-react": "^32.0.2", "axios": "^1.7.8", "big.js": "^6.2.2", diff --git a/qcast3.database.sqlite b/qcast3.database.sqlite index c0c43e6faa95884fffddcdf0dcf1413c2e23f368..66ac677f482169e439cd063c21efbc753f2a27c9 100644 GIT binary patch delta 122 zcmZo@U~Fh$oFL68J5k1&QFdd(e0^>{UIri#U|`^1y_w5kA%AEv9~*-xW23Bzp}7H9 zMoCFQv6a4lW?o5ZQ9({=x?ZudUSff6UVc$YMrvYliLQ}eVs2tpeqLgEv0ie1u6}7j YPJUvFer{!PVUBK2etN!MRzYey0Qtuyp8x;= delta 81 zcmZo@U~Fh$oFL68HBrWyQEFqte0^?SUIri#U|`^1xtYsgA^&85J6#RSloWGg%T!a{ jH1jlLT@#B`Bi$sUltkS`LvvGe( { + const Body = Buffer.from(await file.arrayBuffer()) + const Key = `upload/${file.name}` + const ContentType = file.ContentType + + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body, + ContentType, + }), + ) + + return { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } +} + +export async function POST(req) { + try { + const formData = await req.formData() + const file = formData.get('file') + + const result = await uploadImage(file) + result.message = '이미지 업로드 성공' + + return NextResponse.json(result) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 }) + } +} + +export async function DELETE(req) { + try { + const searchParams = req.nextUrl.searchParams + const Key = `upload/${searchParams.get('fileName')}` + console.log('🚀 ~ DELETE ~ Key:', Key) + + if (!Key) { + return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) + } + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key, + }), + ) + + return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 }) + } catch (error) { + console.error('S3 Delete Error:', error) + return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 }) + } +} diff --git a/src/components/floor-plan/modal/ImgLoad.jsx b/src/components/floor-plan/modal/ImgLoad.jsx index dda395a9..f0a86938 100644 --- a/src/components/floor-plan/modal/ImgLoad.jsx +++ b/src/components/floor-plan/modal/ImgLoad.jsx @@ -120,7 +120,7 @@ export default function ImgLoad() { value={refImage ? (refImage?.name ?? '') : (currentCanvasPlan?.bgImageName ?? '')} readOnly /> - {refImage && } + {currentCanvasPlan?.bgImageName && } diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index b21cc954..1642daa9 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -28,7 +28,7 @@ export function useRefFiles() { const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState) const { handleBackImageLoadToCanvas } = useCanvas() const { swalFire } = useSwal() - const { get, post } = useAxios() + const { get, post, del } = useAxios() useEffect(() => { if (refFileMethod === '1') { @@ -84,6 +84,9 @@ export function useRefFiles() { text: '삭제하시겠습니까?', type: 'confirm', confirmFn: async () => { + console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage) + console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) + await del({ url: `http://localhost:3000/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` }) setRefImage(null) setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null })) await deleteBackGroundImage({ @@ -179,18 +182,24 @@ export function useRefFiles() { const formData = new FormData() formData.append('file', file) + // const res = await post({ + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/upload`, + // data: formData, + // }) const res = await post({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/upload`, + url: `http://localhost:3000/api/image/upload`, data: formData, }) console.log('🚀 ~ handleUploadImageRefFile ~ res:', res) - setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + setCurrentBgImage(`${res.filePath}`) setRefImage(file) const params = { objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, - imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + imagePath: `${res.filePath}`, } console.log('🚀 ~ handleUploadImageRefFile ~ params:', params) await setBackGroundImage(params) From 2492b45a66159ad7392ff04a86c91ae9c5dcc5b9 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 17:08:31 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B8=80=20=EB=A7=B5=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=84=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/map/route.js | 68 +++++++++++++++++++++++++++++++++ src/hooks/common/useRefFiles.js | 14 +++++-- 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/app/api/image/map/route.js diff --git a/src/app/api/image/map/route.js b/src/app/api/image/map/route.js new file mode 100644 index 00000000..3bcfb35a --- /dev/null +++ b/src/app/api/image/map/route.js @@ -0,0 +1,68 @@ +import { NextResponse } from 'next/server' + +export async function GET(req) { + try { + const searchParams = req.nextUrl.searchParams + const q = searchParams.get('q') + const fileNm = searchParams.get('fileNm') + const zoom = searchParams.get('zoom') + + /** 구글 맵을 이미지로 변경하기 위한 API */ + const API_KEY = 'AIzaSyDO7nVR1N_D2tKy60hgGFavpLaXkHpiHpc' + const targetUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${q}&zoom=${zoom}&maptype=satellite&size=640x640&scale=1&key=${API_KEY}` + const decodeUrl = decodeURIComponent(targetUrl) + + /** 구글 맵을 이미지로 변경하기 위한 API 호출 */ + const response = await fetch(decodeUrl) + const data = await response.arrayBuffer() + // const buffer = Buffer.from(data) + + /** 변경된 이미지를 S3에 업로드 */ + const Body = Buffer.from(data) + const Key = `map/${file.name}` + const ContentType = 'image/png' + + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body, + ContentType, + }), + ) + + const result = { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } + + return NextResponse.json(result) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 }) + } +} + +export async function DELETE(req) { + try { + const searchParams = req.nextUrl.searchParams + const Key = `map/${searchParams.get('fileName')}` + console.log('🚀 ~ DELETE ~ Key:', Key) + + if (!Key) { + return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) + } + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key, + }), + ) + + return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 }) + } catch (error) { + console.error('S3 Delete Error:', error) + return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 }) + } +} diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 1642daa9..254c4b12 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -105,6 +105,9 @@ export function useRefFiles() { text: '삭제하시겠습니까?', type: 'confirm', confirmFn: async () => { + console.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage) + console.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) + await del({ url: `http://localhost:3000/api/image/map?fileName=${currentCanvasPlan.bgImageName}` }) setMapPositionAddress('') setCurrentBgImage(null) setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null })) @@ -135,16 +138,21 @@ export function useRefFiles() { option1: newOption1, })) + // const res = await get({ + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/map/convert?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, + // }) const res = await get({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/map/convert?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, + url: `http://localhost:3000/api/map/upload?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, }) console.log('🚀 ~ handleMapImageDown ~ res:', res) - setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) + setCurrentBgImage(`${res.filePath}`) await setBackGroundImage({ objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, - imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + imagePath: `${res.filePath}`, }) } From 9a9fa522f9e158d06c18a5bee97c3e749d4f0afe Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 17:54:32 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B8=80=20=EB=A7=B5=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=84=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qcast3.database.sqlite | Bin 16384 -> 16384 bytes src/app/api/image/map/route.js | 14 ++++++++++++-- src/hooks/common/useRefFiles.js | 29 +++++++++++++++++++---------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/qcast3.database.sqlite b/qcast3.database.sqlite index 66ac677f482169e439cd063c21efbc753f2a27c9..e6022fd79a14effb51bb9a253191dd42b5f6ec9e 100644 GIT binary patch delta 59 zcmZo@U~Fh$oFL7pHc`fzQEg+we0^?VUIqpRM*bZP{5v*t8LZ>ya1&-@5M^v+p1j%K PoHIAEpjh9;(A)q3y!Q^F delta 63 zcmZo@U~Fh$oFL68J5k1&QFdd(e0^>{UIri#U|`^1y_w5k9slH`_C{i*1v&YNDf+pU R#f3S#Ir-`NdRYaj=>Q$c63zes diff --git a/src/app/api/image/map/route.js b/src/app/api/image/map/route.js index 3bcfb35a..0cc76c02 100644 --- a/src/app/api/image/map/route.js +++ b/src/app/api/image/map/route.js @@ -1,4 +1,14 @@ import { NextResponse } from 'next/server' +import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3' + +const Bucket = process.env.AMPLIFY_BUCKET +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}) export async function GET(req) { try { @@ -19,7 +29,7 @@ export async function GET(req) { /** 변경된 이미지를 S3에 업로드 */ const Body = Buffer.from(data) - const Key = `map/${file.name}` + const Key = `maps/${fileNm}` const ContentType = 'image/png' await s3.send( @@ -46,7 +56,7 @@ export async function GET(req) { export async function DELETE(req) { try { const searchParams = req.nextUrl.searchParams - const Key = `map/${searchParams.get('fileName')}` + const Key = `maps/${searchParams.get('fileName')}` console.log('🚀 ~ DELETE ~ Key:', Key) if (!Key) { diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 254c4b12..3e726364 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -142,7 +142,7 @@ export function useRefFiles() { // url: `${process.env.NEXT_PUBLIC_HOST_URL}/map/convert?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, // }) const res = await get({ - url: `http://localhost:3000/api/map/upload?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, + url: `http://localhost:3000/api/image/map?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`, }) console.log('🚀 ~ handleMapImageDown ~ res:', res) // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`) @@ -160,16 +160,25 @@ export function useRefFiles() { * 배경 이미지 로드를 위한 세팅 */ useEffect(() => { - if (!currentBgImage) { - return - } + // if (!currentBgImage) { + // return + // } console.log('🚀 ~ useEffect ~ currentBgImage:', currentBgImage) - handleBackImageLoadToCanvas(currentBgImage) - setCurrentCanvasPlan((prev) => ({ - ...prev, - bgImageName: refImage?.name ?? null, - mapPositionAddress: queryRef.current.value, - })) + if (currentBgImage) { + handleBackImageLoadToCanvas(currentBgImage) + setCurrentCanvasPlan((prev) => ({ + ...prev, + // bgImageName: refImage?.name ?? null, + bgImageName: currentBgImage.split('/').pop(), + mapPositionAddress: queryRef.current.value, + })) + } else { + setCurrentCanvasPlan((prev) => ({ + ...prev, + bgImageName: null, + mapPositionAddress: null, + })) + } }, [currentBgImage]) /** From 11edbe19883e7034f58673dabc38def0179fd1a2 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Mon, 24 Mar 2025 19:01:09 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20cad=ED=8C=8C=EC=9D=BC=20=EC=BB=A8?= =?UTF-8?q?=EB=B2=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/cad/route.js | 70 +++++++++++++++++++++++++++++++++ src/hooks/common/useRefFiles.js | 37 ++++++++++++++--- 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 src/app/api/image/cad/route.js diff --git a/src/app/api/image/cad/route.js b/src/app/api/image/cad/route.js new file mode 100644 index 00000000..510feff2 --- /dev/null +++ b/src/app/api/image/cad/route.js @@ -0,0 +1,70 @@ +import { NextResponse } from 'next/server' +import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3' + +const Bucket = process.env.AMPLIFY_BUCKET +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}) + +const uploadImage = async (file) => { + const Body = Buffer.from(await file.arrayBuffer()) + const Key = `cads/${file.name}` + const ContentType = file.ContentType + + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body, + ContentType, + }), + ) + + return { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } +} + +export async function POST(req) { + try { + const formData = await req.formData() + const file = formData.get('file') + + const result = await uploadImage(file) + result.message = '이미지 업로드 성공' + + return NextResponse.json(result) + } catch (error) { + console.error(error) + return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 }) + } +} + +export async function DELETE(req) { + try { + const searchParams = req.nextUrl.searchParams + const Key = `cads/${searchParams.get('fileName')}` + console.log('🚀 ~ DELETE ~ Key:', Key) + + if (!Key) { + return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) + } + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key, + }), + ) + + return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 }) + } catch (error) { + console.error('S3 Delete Error:', error) + return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 }) + } +} diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 3e726364..097d42ae 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -87,8 +87,9 @@ export function useRefFiles() { console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage) console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) await del({ url: `http://localhost:3000/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` }) - setRefImage(null) - setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null })) + // setRefImage(null) + setCurrentBgImage(null) + // setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null })) await deleteBackGroundImage({ objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, @@ -110,7 +111,7 @@ export function useRefFiles() { await del({ url: `http://localhost:3000/api/image/map?fileName=${currentCanvasPlan.bgImageName}` }) setMapPositionAddress('') setCurrentBgImage(null) - setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null })) + // setCurrentCanvasPlan((prev) => ({ ...prev, mapPositionAddress: null })) await deleteBackGroundImage({ objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, @@ -173,6 +174,7 @@ export function useRefFiles() { mapPositionAddress: queryRef.current.value, })) } else { + setRefImage(null) setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: null, @@ -230,15 +232,38 @@ export function useRefFiles() { const formData = new FormData() formData.append('file', file) + /** 캐드 도면 파일 변환 */ const res = await post({ url: converterUrl, data: formData }) console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res) + + /** 캐드 도면 파일 업로드 */ const result = await post({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/cad/convert`, + url: `http://localhost:3000/api/image/cad`, data: res, }) console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result) - setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${result.filePath}`) - setRefImage(res.Files[0].FileData) + + setCurrentBgImage(`${result.filePath}`) + setRefImage(file) + + const params = { + objectId: currentCanvasPlan.id, + planNo: currentCanvasPlan.planNo, + // imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`, + imagePath: `${result.filePath}`, + } + console.log('🚀 ~ handleUploadImageRefFile ~ params:', params) + await setBackGroundImage(params) + + // const res = await post({ url: converterUrl, data: formData }) + // console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res) + // const result = await post({ + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/cad/convert`, + // data: res, + // }) + // console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result) + // setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${result.filePath}`) + // setRefImage(res.Files[0].FileData) } /** From 97389f714162a0872e8a7a9d9d3ed3a5d9735f20 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 11:01:40 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=EC=BA=94=EB=B2=84=EC=8A=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20S3=EB=A1=9C=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/canvas/route.js | 120 ++++++++++++++++++++++++++++ src/app/api/image/upload/route.js | 8 +- src/hooks/floorPlan/useImgLoader.js | 3 +- 3 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 src/app/api/image/canvas/route.js diff --git a/src/app/api/image/canvas/route.js b/src/app/api/image/canvas/route.js new file mode 100644 index 00000000..f2e6df86 --- /dev/null +++ b/src/app/api/image/canvas/route.js @@ -0,0 +1,120 @@ +import { NextResponse } from 'next/server' +import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3' +import sharp from 'sharp' +import { v4 as uuidv4 } from 'uuid' +const Bucket = process.env.AMPLIFY_BUCKET +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}) + +const checkArea = (obj) => { + const { width, height, left, top } = obj + + if (left < 0 || top < 0 || width > 1600 || height > 1000) { + return false + } + + return true +} + +const cropImage = async (Key, width, height, left, top) => { + try { + const checkResult = checkArea({ width, height, left, top }) + + // Get the image from S3 + const { Body } = await s3.send( + new GetObjectCommand({ + Bucket, + Key, + }), + ) + + // Convert stream to buffer + const chunks = [] + for await (const chunk of Body) { + chunks.push(chunk) + } + const imageBuffer = Buffer.concat(chunks) + + let processedImage + if (!checkResult) { + processedImage = await sharp(imageBuffer).toBuffer() + } else { + processedImage = await sharp(imageBuffer) + .extract({ + width: parseInt(width), + height: parseInt(height), + left: parseInt(left), + top: parseInt(top), + }) + .png() + .toBuffer() + } + return processedImage + } catch (error) { + console.error('Error processing image:', error) + throw error + } +} + +export async function POST(req) { + try { + const formData = await req.formData() + const file = formData.get('file') + const objectNo = formData.get('objectNo') + const planNo = formData.get('planNo') + const type = formData.get('type') + const width = formData.get('width') + const height = formData.get('height') + const left = formData.get('left') + const top = formData.get('top') + + const OriginalKey = `Drawing/${uuidv4()}` + + // Upload original image + await s3.send( + new PutObjectCommand({ + Bucket, + Key: OriginalKey, + Body: Buffer.from(await file.arrayBuffer()), + ContentType: 'image/png', + }), + ) + + // Process the image + const bufferImage = await cropImage(OriginalKey, width, height, left, top) + + const Key = `Drawing/${objectNo}_${planNo}_${type}` + + // Upload processed image + await s3.send( + new PutObjectCommand({ + Bucket, + Key, + Body: bufferImage, + ContentType: 'image/png', + }), + ) + + await s3.send( + new DeleteObjectCommand({ + Bucket, + Key: OriginalKey, + }), + ) + + const result = { + filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`, + fileName: Key, + } + + return NextResponse.json(result) + } catch (error) { + console.error('Error in POST:', error) + return NextResponse.json({ error: 'Failed to process and upload image' }, { status: 500 }) + } +} diff --git a/src/app/api/image/upload/route.js b/src/app/api/image/upload/route.js index 99daa259..4d875257 100644 --- a/src/app/api/image/upload/route.js +++ b/src/app/api/image/upload/route.js @@ -48,13 +48,15 @@ export async function POST(req) { export async function DELETE(req) { try { const searchParams = req.nextUrl.searchParams - const Key = `upload/${searchParams.get('fileName')}` - console.log('🚀 ~ DELETE ~ Key:', Key) + const fileName = searchParams.get('fileName') - if (!Key) { + if (!fileName) { return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 }) } + const Key = `upload/${fileName}` + console.log('🚀 ~ DELETE ~ Key:', Key) + await s3.send( new DeleteObjectCommand({ Bucket, diff --git a/src/hooks/floorPlan/useImgLoader.js b/src/hooks/floorPlan/useImgLoader.js index e51a4a2b..2a5a09ab 100644 --- a/src/hooks/floorPlan/useImgLoader.js +++ b/src/hooks/floorPlan/useImgLoader.js @@ -79,7 +79,8 @@ export function useImgLoader() { /** 이미지 크롭 요청 */ const result = await post({ - url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/canvas`, + // url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/canvas`, + url: `http://localhost:3000/api/image/canvas`, data: formData, }) console.log('🚀 ~ handleCanvasToPng ~ result:', result) From 553a259c1fc4f09e14bc4ab0569898f225d91b32 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 11:02:08 +0900 Subject: [PATCH 08/14] =?UTF-8?q?chore:=20=EB=8F=84=EB=A9=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=81=AC=EB=A1=AD=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20sharp=20lib=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 230e1b81..4c3b230b 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-responsive-modal": "^6.4.2", "react-select": "^5.8.1", "recoil": "^0.7.7", + "sharp": "^0.33.5", "sqlite": "^5.1.1", "sqlite3": "^5.1.7", "sweetalert2": "^11.14.1", From 8d645d08dca2ba91b6aa71dcc542804acfdc84ff Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 13:33:36 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20API=20Router=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9D=BC=EB=B6=80=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 패턴에 어긋나는 응답 포맷 수정 - 주석 작성 --- src/app/api/image/cad/route.js | 1 - src/app/api/image/canvas/route.js | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/api/image/cad/route.js b/src/app/api/image/cad/route.js index 510feff2..b7d64a50 100644 --- a/src/app/api/image/cad/route.js +++ b/src/app/api/image/cad/route.js @@ -36,7 +36,6 @@ export async function POST(req) { const file = formData.get('file') const result = await uploadImage(file) - result.message = '이미지 업로드 성공' return NextResponse.json(result) } catch (error) { diff --git a/src/app/api/image/canvas/route.js b/src/app/api/image/canvas/route.js index f2e6df86..6a15e9bf 100644 --- a/src/app/api/image/canvas/route.js +++ b/src/app/api/image/canvas/route.js @@ -75,7 +75,10 @@ export async function POST(req) { const OriginalKey = `Drawing/${uuidv4()}` - // Upload original image + /** + * 원본 이미지를 우선 저장한다. + * 이미지 이름이 겹지는 현상을 방지하기 위해 uuid 를 사용한다. + */ await s3.send( new PutObjectCommand({ Bucket, @@ -85,12 +88,19 @@ export async function POST(req) { }), ) - // Process the image + /** + * 저장된 원본 이미지를 기준으로 크롭여부를 결정하여 크롭 이미지를 저장한다. + */ const bufferImage = await cropImage(OriginalKey, width, height, left, top) + /** + * 크롭 이미지 이름을 결정한다. + */ const Key = `Drawing/${objectNo}_${planNo}_${type}` - // Upload processed image + /** + * 크롭이 완료된 이미지를 업로드한다. + */ await s3.send( new PutObjectCommand({ Bucket, @@ -100,6 +110,9 @@ export async function POST(req) { }), ) + /** + * 크롭이미지 저장이 완료되면 원본 이미지를 삭제한다. + */ await s3.send( new DeleteObjectCommand({ Bucket, From c7de33b8b9021a794c1d7129e2931d7c76dc72cc Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 25 Mar 2025 14:20:28 +0900 Subject: [PATCH 10/14] =?UTF-8?q?fix:=20popup=20spinner=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qcast3.database.sqlite | Bin 16384 -> 16384 bytes src/hooks/common/useRefFiles.js | 8 +++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/qcast3.database.sqlite b/qcast3.database.sqlite index e6022fd79a14effb51bb9a253191dd42b5f6ec9e..166f2da26bfaf6d977a400c5ef8dd5a21ef49896 100644 GIT binary patch delta 82 zcmZo@U~Fh$oFL7pHBrWyQEOwue0^R~UIqpRM*bZP{5$wlHuD%n@bd(TvN4EqHp-fq mm|0FfYHuV~T9A{Un4+IsSzMT-o0FfOua{Mjnm&1ly$1kpWEZml delta 102 zcmZo@U~Fh$oFL7pHc`fzQEg+we0^SFUIqpRM*bZP{5yC(HuD%n@K27nlQmB^w=_vL zH8IjnPDx79HAyoz)lEt>veZpUGdD6yGBZd_O197|$O{YQ4d!EG5M^wXH8C_d;F`R{ G-U9$hz8XaU diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index 097d42ae..3be7fff2 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react' -import { useRecoilState } from 'recoil' +import { useRecoilState, useSetRecoilState } from 'recoil' import { useSwal } from '@/hooks/useSwal' import { useAxios } from '../useAxios' @@ -7,6 +7,7 @@ import { currentCanvasPlanState } from '@/store/canvasAtom' import { useCanvas } from '@/hooks/useCanvas' import { deleteBackGroundImage, setBackGroundImage } from '@/lib/imageActions' import { settingModalFirstOptionsState } from '@/store/settingAtom' +import { popSpinnerState } from '@/store/popupAtom' /** * 배경 이미지 관리 @@ -29,6 +30,7 @@ export function useRefFiles() { const { handleBackImageLoadToCanvas } = useCanvas() const { swalFire } = useSwal() const { get, post, del } = useAxios() + const setPopSpinnerStore = useSetRecoilState(popSpinnerState) useEffect(() => { if (refFileMethod === '1') { @@ -84,6 +86,7 @@ export function useRefFiles() { text: '삭제하시겠습니까?', type: 'confirm', confirmFn: async () => { + setPopSpinnerStore(true) console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage) console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName) await del({ url: `http://localhost:3000/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` }) @@ -94,6 +97,7 @@ export function useRefFiles() { objectId: currentCanvasPlan.id, planNo: currentCanvasPlan.planNo, }) + setPopSpinnerStore(false) }, }) } @@ -188,6 +192,7 @@ export function useRefFiles() { * @param {*} file */ const handleUploadImageRefFile = async (file) => { + setPopSpinnerStore(true) const newOption1 = settingModalFirstOptions.option1.map((option) => ({ ...option, selected: option.column === 'imageDisplay' ? true : option.selected, @@ -222,6 +227,7 @@ export function useRefFiles() { } console.log('🚀 ~ handleUploadImageRefFile ~ params:', params) await setBackGroundImage(params) + setPopSpinnerStore(false) } /** From c6b96bec23a054ed5dcff169a5c14719f54d7ccc Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 23 Apr 2025 10:03:25 +0900 Subject: [PATCH 11/14] chore: update environment variables to use protocol-relative URLs for host --- .env.development | 2 +- .env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index 4f613c70..ceac1712 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ NEXT_PUBLIC_API_SERVER_PATH="http://1.248.227.176:38080" -NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000" +NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000" SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" diff --git a/.env.production b/.env.production index f0951f7a..767c3e4b 100644 --- a/.env.production +++ b/.env.production @@ -1,6 +1,6 @@ NEXT_PUBLIC_API_SERVER_PATH="https://api.hanasys.jp/" -NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000" +NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000" SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" From ef75ad0ef8a080d58e086de2d4f98204a254bd63 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Thu, 8 May 2025 13:29:13 +0900 Subject: [PATCH 12/14] chore: update environment variables for Japanese server configuration --- .env.development | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.env.development b/.env.development index ceac1712..6488b496 100644 --- a/.env.development +++ b/.env.development @@ -10,8 +10,16 @@ NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secr NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin" NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin" -AWS_REGION="ap-northeast-2" -AMPLIFY_BUCKET="interplug" -AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" -AWS_SECRET_ACCESS_KEY="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" -NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" +# 테스트용 +# AWS_REGION="ap-northeast-2" +# AMPLIFY_BUCKET="interplug" +# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR" +# AWS_SECRET_ACCESS_KEY="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" +# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com" + +# 실제 일본 서버 +AWS_REGION="ap-northeast-1" +AMPLIFY_BUCKET="files.hanasys.jp" +AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E" +AWS_SECRET_ACCESS_KEY="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" +NEXT_PUBLIC_AWS_S3_BASE_URL="http://files.hanasys.jp.s3-website-ap-northeast-1.amazonaws.com" \ No newline at end of file From 1d4100a579abf9d94dbe39bcd55403b216b17f28 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 13 May 2025 15:13:59 +0900 Subject: [PATCH 13/14] chore: nextjs version update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c3b230b..bbddf9ad 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "js-cookie": "^3.0.5", "mathjs": "^13.0.2", "mssql": "^11.0.1", - "next": "14.2.21", + "next": "14.2.28", "next-international": "^1.2.4", "react": "^18", "react-chartjs-2": "^5.2.0", From ad2aa50d83c8e6264da01dbe20ad5c3fcd668bb4 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Tue, 13 May 2025 15:24:57 +0900 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20=EC=BA=94=EB=B2=84=EC=8A=A4=20s3?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F?= =?UTF-8?q?=20=ED=99=95=EC=9E=A5=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/canvas/route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/image/canvas/route.js b/src/app/api/image/canvas/route.js index 6a15e9bf..0a4d0be2 100644 --- a/src/app/api/image/canvas/route.js +++ b/src/app/api/image/canvas/route.js @@ -96,7 +96,7 @@ export async function POST(req) { /** * 크롭 이미지 이름을 결정한다. */ - const Key = `Drawing/${objectNo}_${planNo}_${type}` + const Key = `Drawing/${objectNo}_${planNo}_${type}.png` /** * 크롭이 완료된 이미지를 업로드한다.