From b6e38e930cd7ab0318cac6bf222a448d27cdcadd Mon Sep 17 00:00:00 2001 From: m Date: Sat, 11 Jul 2026 23:09:35 +0200 Subject: [PATCH] refresh signed files --- backend/internal/handlers/files.go | 11 ++-- mobile/api/client.ts | 6 ++- mobile/components/FileCard.tsx | 3 +- mobile/constants/api.ts | 1 + mobile/hooks/useFileImage.ts | 82 ++++++++++++++++++++++-------- 5 files changed, 72 insertions(+), 31 deletions(-) diff --git a/backend/internal/handlers/files.go b/backend/internal/handlers/files.go index d07377a..7f28c22 100644 --- a/backend/internal/handlers/files.go +++ b/backend/internal/handlers/files.go @@ -1,7 +1,6 @@ package handlers import ( - "fmt" "net/http" "os" "path" @@ -72,7 +71,7 @@ func ListFiles(c *gin.Context) { func ListFile(c *gin.Context) { - id := c.Query("id") + id := c.Param("id") dirs, err := os.ReadDir("./uploads/") @@ -175,12 +174,10 @@ func UploadFile(c *gin.Context) { c.SaveUploadedFile(file, dst) - c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) - - c.JSON(http.StatusNotImplemented, gin.H{ + c.JSON(http.StatusOK, gin.H{ "error": gin.H{ - "code": "NOT_IMPLEMENTED", - "message": "Upload not yet implemented", + "code": "SUCCESS", + "message": "Uploaded", }, }) } diff --git a/mobile/api/client.ts b/mobile/api/client.ts index eeb5df9..6c13e61 100644 --- a/mobile/api/client.ts +++ b/mobile/api/client.ts @@ -1,4 +1,4 @@ -import { API_BASE_URL } from '../constants/api'; +import { API_BASE_URL, ENDPOINTS } from '../constants/api'; import { ApiError } from '../types'; class ApiClient { @@ -43,6 +43,10 @@ class ApiClient { async delete(endpoint: string): Promise { return this.request(endpoint, { method: 'DELETE' }); } + + async getFileUrl(fileName: string): Promise<{ data: { url: string; name: string } }> { + return this.request(`${ENDPOINTS.FILE}/${fileName}`); + } } export const apiClient = new ApiClient(API_BASE_URL); diff --git a/mobile/components/FileCard.tsx b/mobile/components/FileCard.tsx index 91740f9..aad32f9 100644 --- a/mobile/components/FileCard.tsx +++ b/mobile/components/FileCard.tsx @@ -10,8 +10,7 @@ interface FileCardProps { } export function FileCard({ file, onPress }: FileCardProps) { - const { localUri, loading } = useFileImage(file.url); -console.log(localUri) + const { localUri, loading } = useFileImage(file.url, file.name); const formatSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; diff --git a/mobile/constants/api.ts b/mobile/constants/api.ts index b6e2c44..5248594 100644 --- a/mobile/constants/api.ts +++ b/mobile/constants/api.ts @@ -2,6 +2,7 @@ export const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL || 'http://192. export const ENDPOINTS = { FILES: '/files', + FILE: '/file', UPLOAD: '/files/upload', SEARCH: '/files/search', OCR_JOBS: '/ocr/jobs', diff --git a/mobile/hooks/useFileImage.ts b/mobile/hooks/useFileImage.ts index a21b38f..02050fa 100644 --- a/mobile/hooks/useFileImage.ts +++ b/mobile/hooks/useFileImage.ts @@ -1,36 +1,70 @@ -import { useEffect, useState } from 'react'; -import { downloadAsync, documentDirectory, makeDirectoryAsync, getInfoAsync } from 'expo-file-system/legacy'; +import { useEffect, useState, useCallback } from 'react'; +import { downloadAsync, documentDirectory, makeDirectoryAsync, getInfoAsync, deleteAsync } from 'expo-file-system/legacy'; +import { useQueryClient } from '@tanstack/react-query'; +import { apiClient } from '../api/client'; +import { FileItem, PaginatedResponse } from '../types'; const CACHE_DIR = `${documentDirectory}file-images/`; -function getCacheKey(url: string): string { +function getCacheKey(name: string): string { let hash = 0; - for (let i = 0; i < url.length; i++) { - hash = ((hash << 5) - hash + url.charCodeAt(i)) | 0; + for (let i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; } return Math.abs(hash).toString(36); } -function getExtension(url: string): string { - const pathname = new URL(url).pathname; - const dot = pathname.lastIndexOf('.'); - return dot >= 0 ? pathname.slice(dot) : '.jpg'; +function getExtension(name: string): string { + const dot = name.lastIndexOf('.'); + return dot >= 0 ? name.slice(dot) : '.jpg'; } -export function useFileImage(url: string | undefined) { +export function useFileImage(url: string | undefined, fileName: string | undefined) { const [localUri, setLocalUri] = useState(null); const [loading, setLoading] = useState(false); + const queryClient = useQueryClient(); + + const refreshUrl = useCallback(async (): Promise => { + if (!fileName) return null; + try { + const res = await apiClient.getFileUrl(fileName); + const freshUrl = res.data.url; + + queryClient.setQueriesData>( + { queryKey: ['files'] }, + (old) => { + if (!old) return old; + return { + ...old, + data: old.data.map((f) => + f.name === fileName ? { ...f, url: freshUrl } : f + ), + }; + } + ); + + return freshUrl; + } catch { + return null; + } + }, [fileName, queryClient]); + + const download = useCallback(async (downloadUrl: string, fileUri: string) => { + await makeDirectoryAsync(CACHE_DIR, { intermediates: true }); + const result = await downloadAsync(downloadUrl, fileUri); + return result.uri; + }, []); useEffect(() => { - if (!url) return; + if (!url || !fileName) return; const resolvedUrl = url; let cancelled = false; + const cacheKey = getCacheKey(fileName); + const ext = getExtension(fileName); + const fileUri = `${CACHE_DIR}${cacheKey}${ext}`; async function load() { - const fileName = `${getCacheKey(resolvedUrl)}${getExtension(resolvedUrl)}`; - const fileUri = `${CACHE_DIR}${fileName}`; - const info = await getInfoAsync(fileUri); if (info.exists) { if (!cancelled) setLocalUri(info.uri); @@ -39,13 +73,19 @@ export function useFileImage(url: string | undefined) { setLoading(true); try { - await makeDirectoryAsync(CACHE_DIR, { intermediates: true }); - const result = await downloadAsync(resolvedUrl, fileUri); - if (!cancelled) { - setLocalUri(result.uri); - } + const uri = await download(resolvedUrl, fileUri); + if (!cancelled) setLocalUri(uri); } catch { - if (!cancelled) { + const freshUrl = await refreshUrl(); + if (freshUrl && !cancelled) { + try { + await deleteAsync(fileUri, { idempotent: true }); + const uri = await download(freshUrl, fileUri); + if (!cancelled) setLocalUri(uri); + } catch { + if (!cancelled) setLocalUri(null); + } + } else if (!cancelled) { setLocalUri(null); } } finally { @@ -58,7 +98,7 @@ export function useFileImage(url: string | undefined) { return () => { cancelled = true; }; - }, [url]); + }, [url, fileName, download, refreshUrl]); return { localUri, loading }; }