upload file is working

This commit is contained in:
m
2026-07-11 22:10:17 +02:00
parent b1f7fe6959
commit 8e728c9042
12 changed files with 158 additions and 71 deletions
+69 -1
View File
@@ -1,19 +1,69 @@
package handlers
import (
"fmt"
"net/http"
"os"
"path/filepath"
"github.com/gin-gonic/gin"
)
func ListFiles(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
dirs, err := os.ReadDir("./uploads/")
if err != nil {
fmt.Println("What")
c.JSON(http.StatusInternalServerError, gin.H{
"data": []interface{}{},
"meta": gin.H{
"page": 1,
"total": 0,
},
})
return
}
type Finfo struct {
Name string `json:"name"`
Size int64 `json:"size"`
Tags []string `json:"tags"`
}
files := []Finfo{}
for _, dir := range dirs {
if dir.IsDir() {
continue
}
i, e := dir.Info()
if e != nil {
continue
}
ps := Finfo{
Name: i.Name(),
Size: i.Size(),
Tags: []string{},
}
files = append(files, ps)
}
c.JSON(http.StatusOK, gin.H{
"data": files,
"meta": gin.H{
"page": 1,
"total": 0,
},
})
}
func GetFile(c *gin.Context) {
@@ -26,6 +76,24 @@ func GetFile(c *gin.Context) {
}
func UploadFile(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"error": gin.H{
"code": "NOT_FOUND",
"message": "form file with file name missing",
},
})
}
dst := filepath.Join("./uploads/", filepath.Base(file.Filename))
c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
c.JSON(http.StatusNotImplemented, gin.H{
"error": gin.H{
"code": "NOT_IMPLEMENTED",
+10
View File
@@ -0,0 +1,10 @@
package service
import (
"fmt"
"mime/multipart"
)
func UploadFile(f *multipart.FileHeader) {
fmt.Println(f.Filename)
}
-18
View File
@@ -43,24 +43,6 @@ class ApiClient {
async delete<T>(endpoint: string): Promise<T> {
return this.request<T>(endpoint, { method: 'DELETE' });
}
async uploadFile<T>(endpoint: string, formData: FormData): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
if (!response.ok) {
const error: ApiError = await response.json();
throw new Error(error.error?.message || 'Upload failed');
}
return response.json();
}
}
export const apiClient = new ApiClient(API_BASE_URL);
+6 -1
View File
@@ -7,9 +7,14 @@
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"ios": {
"supportsTablet": true
"supportsTablet": true,
"infoPlist": {
"NSCameraUsageDescription": "VaultDrop a besoin d'accéder à votre caméra pour scanner des documents.",
"NSPhotoLibraryUsageDescription": "VaultDrop a besoin d'accéder à votre galerie pour sélectionner des photos."
}
},
"android": {
"permissions": ["CAMERA", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"],
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/android-icon-foreground.png",
+1 -1
View File
@@ -47,7 +47,7 @@ export function HomeScreen() {
<FlatList
data={data?.data || []}
renderItem={renderItem}
keyExtractor={(item) => item.id}
keyExtractor={(item) => item.name}
contentContainerStyle={styles.list}
/>
+10 -8
View File
@@ -1,20 +1,22 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import * as ImagePicker from 'react-native-image-picker';
import * as ImagePicker from 'expo-image-picker';
export function ScanScreen() {
const takePhoto = async () => {
try {
const result = await ImagePicker.launchCamera({
mediaType: 'photo',
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Permission requise', "L'accès à la caméra est nécessaire pour scanner un document.");
return;
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ['images'],
quality: 1,
});
if (result.didCancel) return;
if (result.errorCode) {
Alert.alert('Erreur', result.errorMessage || "Impossible d'accéder à la caméra");
return;
}
if (result.canceled) return;
if (result.assets && result.assets[0]) {
console.log('Photo taken:', result.assets[0]);
+13 -11
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import * as ImagePicker from 'react-native-image-picker';
import * as ImagePicker from 'expo-image-picker';
import { useUpload } from '../hooks/useUpload';
import { UploadProgress } from '../components/UploadProgress';
@@ -11,28 +11,30 @@ export function UploadScreen() {
const pickImage = async () => {
try {
const result = await ImagePicker.launchImageLibrary({
mediaType: 'photo',
quality: 1,
});
if (result.didCancel) return;
if (result.errorCode) {
Alert.alert('Erreur', result.errorMessage || "Impossible d'accéder à la galerie");
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Permission requise', "L'accès à la galerie est nécessaire pour sélectionner une photo.");
return;
}
console.log('yes')
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
quality: 1,
});
console.log(result)
if (result.canceled) return;
if (result.assets && result.assets[0]) {
setUploadStatus('uploading');
const asset = result.assets[0];
const file = {
uri: asset.uri,
type: asset.type || 'image/jpeg',
type: asset.mimeType || 'image/jpeg',
name: asset.fileName || 'photo.jpg',
};
try {
const response = await upload.mutateAsync(file as any);
const response = await upload.mutateAsync(file);
setUploadStatus('processing');
console.log('Upload success:', response);
setUploadStatus('success');
+2
View File
@@ -15,6 +15,8 @@ export function FileCard({ file, onPress }: FileCardProps) {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
console.log(file)
return (
<TouchableOpacity style={styles.container} onPress={() => onPress?.(file)}>
<View style={styles.header}>
+1 -1
View File
@@ -1,4 +1,4 @@
export const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL || 'http://localhost:8080/api/v1';
export const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL || 'http://192.168.1.142:8080/api/v1';
export const ENDPOINTS = {
FILES: '/files',
+11 -8
View File
@@ -1,20 +1,23 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { File, UploadType } from 'expo-file-system';
import { apiClient } from '../api/client';
import { ENDPOINTS } from '../constants/api';
import { API_BASE_URL, ENDPOINTS } from '../constants/api';
import { OcrJob } from '../types';
export function useUpload() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (file: File) => {
const formData = new FormData();
formData.append('file', file);
mutationFn: async (file: { uri: string; type: string; name: string }) => {
const fsFile = new File(file.uri);
const result = await fsFile.upload(`${API_BASE_URL}${ENDPOINTS.UPLOAD}`, {
httpMethod: 'POST',
uploadType: UploadType.MULTIPART,
fieldName: 'file',
mimeType: file.type,
});
return apiClient.uploadFile<{ id: string; ocrJobId: string }>(
ENDPOINTS.UPLOAD,
formData
);
return JSON.parse(result.body) as { id: string; ocrJobId: string };
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['files'] });
+33 -21
View File
@@ -12,10 +12,11 @@
"@react-navigation/native-stack": "^7.17.10",
"@tanstack/react-query": "^5.101.2",
"expo": "~57.0.4",
"expo-file-system": "~57.0.0",
"expo-image-picker": "~57.0.2",
"expo-status-bar": "~57.0.0",
"react": "19.2.3",
"react-native": "0.86.0",
"react-native-image-picker": "^8.2.1",
"react-native-mmkv": "^4.3.2",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2"
@@ -2797,6 +2798,37 @@
}
}
},
"node_modules/expo-file-system": {
"version": "57.0.0",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.0.tgz",
"integrity": "sha512-G+eytNuNGMiS7dbdj1j0nAYMowXnNr1vpO+jss6nQvBlRxshcGBFMtk8xfc67ynz0MUVkzod7WwKO7Vf1iOaJw==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo-image-loader": {
"version": "57.0.0",
"resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-57.0.0.tgz",
"integrity": "sha512-EhwnoPC4T/EMdB7Nsg7qITzhO/qB0hUnmVghmtAKQwwDVaLWWYCGE4NLkes3zk9Ub451SmS/swgPp4PCPzOlnw==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-image-picker": {
"version": "57.0.2",
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-57.0.2.tgz",
"integrity": "sha512-XW+C5weIthkOLCJZzExeRJf9pcWfaXNHSDm76gmZVhDUVIHdi9IS3eihAIF0WA0fBo9ogIfQs/iep/c6CzIMKg==",
"license": "MIT",
"dependencies": {
"expo-image-loader": "~57.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-modules-autolinking": {
"version": "57.0.5",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.5.tgz",
@@ -3177,16 +3209,6 @@
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-file-system": {
"version": "57.0.0",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.0.tgz",
"integrity": "sha512-G+eytNuNGMiS7dbdj1j0nAYMowXnNr1vpO+jss6nQvBlRxshcGBFMtk8xfc67ynz0MUVkzod7WwKO7Vf1iOaJw==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-font": {
"version": "57.0.0",
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.0.tgz",
@@ -5225,16 +5247,6 @@
}
}
},
"node_modules/react-native-image-picker": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-8.2.1.tgz",
"integrity": "sha512-FBeGYJGFDjMdGCcyubDJgBAPCQ4L1D3hwLXyUU91jY9ahOZMTbluceVvRmrEKqnDPFJ0gF1NVhJ0nr1nROFLdg==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-mmkv": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/react-native-mmkv/-/react-native-mmkv-4.3.2.tgz",
+2 -1
View File
@@ -7,10 +7,11 @@
"@react-navigation/native-stack": "^7.17.10",
"@tanstack/react-query": "^5.101.2",
"expo": "~57.0.4",
"expo-file-system": "~57.0.0",
"expo-image-picker": "~57.0.2",
"expo-status-bar": "~57.0.0",
"react": "19.2.3",
"react-native": "0.86.0",
"react-native-image-picker": "^8.2.1",
"react-native-mmkv": "^4.3.2",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2"