From 0fc0e22ade87f47a964fbf06845dcc90f39677e3 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sat, 6 Dec 2025 00:19:11 +0800 Subject: [PATCH 1/3] feat: support backend image upload provider --- .env.example | 6 ++++++ src/api/index.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index b179561..9f5306e 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,8 @@ VUE_APP_BASE_URL=https://staging.disfactory.tw/api VUE_APP_IMGUR_FALLBACK_URL=https://api.disfactory.tw/imgur + +# Image upload configuration +# Set to 'backend' to use the backend filesystem upload, or 'imgur' to use Imgur (default) +VUE_APP_IMAGE_UPLOAD_PROVIDER=imgur +# Backend upload URL (only used when VUE_APP_IMAGE_UPLOAD_PROVIDER=backend) +VUE_APP_IMAGE_UPLOAD_URL=https://api.disfactory.tw/api/upload diff --git a/src/api/index.ts b/src/api/index.ts index 57ed05c..e4ce1be 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -28,6 +28,11 @@ export type UploadedImages = { src: string // used for preview images }[] +// Image upload provider configuration +type ImageUploadProvider = 'imgur' | 'backend' +const IMAGE_UPLOAD_PROVIDER: ImageUploadProvider = (process.env.VUE_APP_IMAGE_UPLOAD_PROVIDER as ImageUploadProvider) || 'imgur' +const IMAGE_UPLOAD_URL = process.env.VUE_APP_IMAGE_UPLOAD_URL || '' + export async function getFactories (range: number, lng: number, lat: number): Promise { try { const { data } = await instance.get(`/factories?range=${range}&lng=${lng}&lat=${lat}`) @@ -50,7 +55,13 @@ export async function getFactory (factoryId: string): Promise { const IMGUR_CLIENT_ID = '39048813b021935' -async function uploadToImgur (file: File) { +type ImageUploadResult = { + link: string, + deletehash: string, + file: File +} + +async function uploadToImgur (file: File): Promise { const formData = new FormData() formData.append('image', file) @@ -71,6 +82,42 @@ async function uploadToImgur (file: File) { } } +async function uploadToBackend (file: File): Promise { + const formData = new FormData() + formData.append('image', file) + + // Use the configured backend upload URL, or construct from current base URL + const uploadUrl = IMAGE_UPLOAD_URL || `${currentBaseURL.value}/upload` + + const { data } = await axios({ + method: 'POST', + url: uploadUrl, + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + + // Backend returns Imgur-compatible response format + if (!data.success) { + throw new Error(data.data?.error || 'Image upload failed') + } + + return { + link: data.data.link as string, + deletehash: data.data.deletehash as string, + file + } +} + +// Upload image using configured provider +async function uploadImage (file: File): Promise { + if (IMAGE_UPLOAD_PROVIDER === 'backend') { + return uploadToBackend(file) + } + return uploadToImgur(file) +} + const convertTurple2Number = (input: [number, number, number]) => input[0] + (input[1] / 60) + (input[2] / 3600) type ExifData = { DateTimeOriginal?: string, GPSLatitude?: [number, number, number], GPSLongitude?: [number, number, number] } @@ -120,13 +167,13 @@ async function uploadExifAndGetToken ({ link, file, deletehash }: { link: string export async function uploadImages (files: FileList): Promise { return Promise.all( - Array.from(files).map((file) => uploadToImgur(file).then((el) => uploadExifAndGetToken(el))) + Array.from(files).map((file) => uploadImage(file).then((el) => uploadExifAndGetToken(el))) ) } export async function updateFactoryImages (factoryId: string, files: FileList, { nickname, contact }: { nickname?: string, contact?: string }) { return Promise.all( - Array.from(files).map((file) => uploadToImgur(file).then((el) => (async () => { + Array.from(files).map((file) => uploadImage(file).then((el) => (async () => { const exifData = await readImageExif(el.file) const { data }: { data: FactoryImage } = await instance.post(`/factories/${factoryId}/images`, { url: el.link, ...exifData, nickname, contact, deletehash: el.deletehash }) data.image_path = el.link From 35be91054a9c846d30c466fafe79d4ae71b3cf01 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sat, 6 Dec 2025 00:52:54 +0800 Subject: [PATCH 2/3] chore: add ci config about image upload endpoint --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2103c1b..f4dec92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,8 @@ jobs: npm ci echo "VUE_APP_BASE_URL=https://staging.disfactory.tw/api" > .env echo "VUE_APP_IMGUR_FALLBACK_URL=https://staging.disfactory.tw/imgur" >> .env + echo "VUE_APP_IMAGE_UPLOAD_PROVIDER=backend" >> .env + echo "VUE_APP_IMAGE_UPLOAD_URL=https://staging.disfactory.tw/api/upload" >> .env npm run build - name: Deploy to GitHub Pages uses: JamesIves/github-pages-deploy-action@v4 @@ -107,6 +109,8 @@ jobs: npm ci echo "VUE_APP_BASE_URL=https://api.disfactory.tw/api" > .env.production echo "VUE_APP_IMGUR_FALLBACK_URL=https://api.disfactory.tw/imgur" >> .env.production + echo "VUE_APP_IMAGE_UPLOAD_PROVIDER=backend" >> .env.production + echo "VUE_APP_IMAGE_UPLOAD_URL=https://api.disfactory.tw/api/upload" >> .env.production npm run build echo "disfactory.tw" > dist/CNAME - name: Deploy to GitHub Pages From 40de106e9488c6d4a89ed8f3ce205006a217a9ae Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sat, 6 Dec 2025 00:55:22 +0800 Subject: [PATCH 3/3] feat: bring back image upload functionaility --- src/App.vue | 15 ++------------- src/components/ImageUploadForm.vue | 3 --- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/App.vue b/src/App.vue index 53d3cc7..3da0385 100644 --- a/src/App.vue +++ b/src/App.vue @@ -133,7 +133,6 @@ - @@ -160,8 +159,6 @@ import { useModalState } from './lib/hooks' import { useAppState } from './lib/appState' import { useAlertState } from './lib/useAlert' -import MaintenanceModal from './components/MaintenanceModal.vue' - export default defineComponent({ name: 'App', components: { @@ -176,8 +173,7 @@ export default defineComponent({ ApiConfigModal, CreateFactorySteps, UpdateFactorySteps, - FactoryDetailPage, - MaintenanceModal + FactoryDetailPage }, setup () { const [modalState, modalActions] = useModalState() @@ -191,11 +187,6 @@ export default defineComponent({ const drawer = ref(false) - // Show maintenance modal on app start, allow closing - const maintenanceModalOpen = ref(true) - const dismissMaintenanceModal = () => { - maintenanceModalOpen.value = false - } return { appState, alertState, @@ -203,9 +194,7 @@ export default defineComponent({ appActions, modalState, modalActions, - drawer, - maintenanceModalOpen, - dismissMaintenanceModal + drawer } } }) diff --git a/src/components/ImageUploadForm.vue b/src/components/ImageUploadForm.vue index 299a868..16066e8 100644 --- a/src/components/ImageUploadForm.vue +++ b/src/components/ImageUploadForm.vue @@ -1,8 +1,5 @@