Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 2 additions & 13 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@
</v-main>

<factory-detail-page />
<maintenance-modal :open="maintenanceModalOpen" :dismiss="dismissMaintenanceModal" />
</v-app>
</template>

Expand All @@ -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: {
Expand All @@ -176,8 +173,7 @@ export default defineComponent({
ApiConfigModal,
CreateFactorySteps,
UpdateFactorySteps,
FactoryDetailPage,
MaintenanceModal
FactoryDetailPage
},
setup () {
const [modalState, modalActions] = useModalState()
Expand All @@ -191,21 +187,14 @@ 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,
alertActions,
appActions,
modalState,
modalActions,
drawer,
maintenanceModalOpen,
dismissMaintenanceModal
drawer
}
}
})
Expand Down
53 changes: 50 additions & 3 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

// Update base URL whenever currentBaseURL changes
const updateBaseURL = () => {
instance.defaults.baseURL = currentBaseURL.value

Check warning on line 15 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe assignment of an `any` value

Check warning on line 15 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe assignment of an `any` value
}

// Set initial base URL and watch for changes
Expand All @@ -28,10 +28,15 @@
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'

Check warning on line 33 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .VUE_APP_IMAGE_UPLOAD_PROVIDER on an `any` value

Check warning on line 33 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .VUE_APP_IMAGE_UPLOAD_PROVIDER on an `any` value

Copilot AI Dec 5, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment variable VUE_APP_IMAGE_UPLOAD_PROVIDER is cast to ImageUploadProvider type without validation. If someone sets an invalid value (e.g., 'aws' or 'cloudinary'), TypeScript will not catch this at runtime, and the code will silently fall back to 'imgur'. Consider adding runtime validation:

const rawProvider = process.env.VUE_APP_IMAGE_UPLOAD_PROVIDER
if (rawProvider && rawProvider !== 'imgur' && rawProvider !== 'backend') {
  console.warn(`Invalid IMAGE_UPLOAD_PROVIDER: ${rawProvider}. Falling back to 'imgur'`)
}
const IMAGE_UPLOAD_PROVIDER: ImageUploadProvider = (rawProvider as ImageUploadProvider) || 'imgur'
Suggested change
const IMAGE_UPLOAD_PROVIDER: ImageUploadProvider = (process.env.VUE_APP_IMAGE_UPLOAD_PROVIDER as ImageUploadProvider) || 'imgur'
const rawProvider = process.env.VUE_APP_IMAGE_UPLOAD_PROVIDER
let IMAGE_UPLOAD_PROVIDER: ImageUploadProvider
if (rawProvider === 'imgur' || rawProvider === 'backend') {
IMAGE_UPLOAD_PROVIDER = rawProvider
} else {
if (rawProvider) {
console.warn(`Invalid VUE_APP_IMAGE_UPLOAD_PROVIDER: ${rawProvider}. Falling back to 'imgur'`)
}
IMAGE_UPLOAD_PROVIDER = 'imgur'
}

Copilot uses AI. Check for mistakes.
const IMAGE_UPLOAD_URL = process.env.VUE_APP_IMAGE_UPLOAD_URL || ''

Check warning on line 34 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .VUE_APP_IMAGE_UPLOAD_URL on an `any` value

Check warning on line 34 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe assignment of an `any` value

Check warning on line 34 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .VUE_APP_IMAGE_UPLOAD_URL on an `any` value

Check warning on line 34 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe assignment of an `any` value

export async function getFactories (range: number, lng: number, lat: number): Promise<FactoriesResponse> {
try {
const { data } = await instance.get(`/factories?range=${range}&lng=${lng}&lat=${lat}`)

Check warning on line 38 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe array destructuring of a tuple element with an `any` value

Check warning on line 38 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe array destructuring of a tuple element with an `any` value
return data

Check warning on line 39 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe return of an `any` typed value

Check warning on line 39 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe return of an `any` typed value
} catch (err) {
console.error(err)
throw new TypeError('Get factory failed')
Expand All @@ -40,8 +45,8 @@

export async function getFactory (factoryId: string): Promise<FactoryData> {
try {
const { data } = await instance.get(`/factories/${factoryId}`)

Check warning on line 48 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe array destructuring of a tuple element with an `any` value

Check warning on line 48 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe array destructuring of a tuple element with an `any` value
return data

Check warning on line 49 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe return of an `any` typed value

Check warning on line 49 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe return of an `any` typed value
} catch (err) {
console.error(err)
throw new TypeError('Get factory failed')
Expand All @@ -50,11 +55,17 @@

const IMGUR_CLIENT_ID = '39048813b021935'

async function uploadToImgur (file: File) {
type ImageUploadResult = {
link: string,
deletehash: string,
file: File
}

async function uploadToImgur (file: File): Promise<ImageUploadResult> {
const formData = new FormData()
formData.append('image', file)

const { data } = await axios({

Check warning on line 68 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe array destructuring of a tuple element with an `any` value

Check warning on line 68 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe array destructuring of a tuple element with an `any` value
method: 'POST',
url: 'https://api.imgur.com/3/image',
data: formData,
Expand All @@ -65,12 +76,48 @@
})

return {
link: data.data.link as string,

Check warning on line 79 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .data on an `any` value

Check warning on line 79 in src/api/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .data on an `any` value
deletehash: data.data.deletehash as string,
file
}
}

async function uploadToBackend (file: File): Promise<ImageUploadResult> {
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<ImageUploadResult> {
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] }
Expand Down Expand Up @@ -120,13 +167,13 @@

export async function uploadImages (files: FileList): Promise<UploadedImages> {
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
Expand Down
3 changes: 0 additions & 3 deletions src/components/ImageUploadForm.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
<template>
<div class="image-upload-form w-100">
<v-alert type="warning" prominent color="warning" class="mb-4">
目前上傳圖片功能受限,暫時停止回報功能,敬請見諒!
</v-alert>
<v-container style="max-width: 630px; position: relative;" class="pt-3 pt-md-12 pb-md-8">
<h2 class="mb-4 secondary--text">上傳工廠照片</h2>

Expand Down