display date
This commit is contained in:
@@ -3,12 +3,12 @@ package config
|
||||
import "os"
|
||||
|
||||
type Config struct {
|
||||
Port string
|
||||
DBPath string
|
||||
OCREndpoint string
|
||||
UploadDir string
|
||||
HMACSecret string
|
||||
ServerHost string
|
||||
Port string
|
||||
DBPath string
|
||||
OCREndpoint string
|
||||
UploadDir string
|
||||
HMACSecret string
|
||||
ServerHost string
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
@@ -18,7 +18,7 @@ func Load() *Config {
|
||||
OCREndpoint: envOr("OCR_ENDPOINT", "http://localhost:9090"),
|
||||
UploadDir: envOr("UPLOAD_DIR", "./uploads"),
|
||||
HMACSecret: envOr("HMAC_SECRET", "thisismyrandomstring"),
|
||||
ServerHost: envOr("SERVER_HOST", "http://localhost:8080"),
|
||||
ServerHost: envOr("SERVER_HOST", "http://192.168.1.17:8080"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,45 @@ func (q *Queries) ListFiles(ctx context.Context) ([]File, error) {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listFilesByID = `-- name: ListFilesByID :many
|
||||
SELECT id, name, mime_type, size, storage_key, checksum, ocr_text, created_at, updated_at FROM files
|
||||
WHERE id IN (SELECT value FROM json_each(?))
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
func (q *Queries) ListFilesByID(ctx context.Context, jsonEach interface{}) ([]File, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listFilesByID, jsonEach)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []File
|
||||
for rows.Next() {
|
||||
var i File
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.MimeType,
|
||||
&i.Size,
|
||||
&i.StorageKey,
|
||||
&i.Checksum,
|
||||
&i.OcrText,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateFile = `-- name: UpdateFile :exec
|
||||
UPDATE files
|
||||
SET name = ?, mime_type = ?, ocr_text = ?, updated_at = CURRENT_TIMESTAMP
|
||||
|
||||
@@ -6,6 +6,11 @@ WHERE id = ? LIMIT 1;
|
||||
SELECT * FROM files
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListFilesByID :many
|
||||
SELECT * FROM files
|
||||
WHERE id IN (SELECT value FROM json_each(?))
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: CreateFile :one
|
||||
INSERT INTO files (id, name, mime_type, size, storage_key, checksum, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
|
||||
@@ -60,21 +60,38 @@ func (h *FileHandler) List(c *gin.Context) {
|
||||
}
|
||||
|
||||
type fileResponse struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Tags []string `json:"tags"`
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Tags []string `json:"tags"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
resp := make([]fileResponse, len(files))
|
||||
for i, f := range files {
|
||||
|
||||
metadata, err := h.files.Get(f.ID)
|
||||
if err != nil {
|
||||
|
||||
resp[i] = fileResponse{
|
||||
ID: f.ID,
|
||||
URL: h.urls.GenerateDownloadURL(f.ID),
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
Tags: []string{},
|
||||
}
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
resp[i] = fileResponse{
|
||||
ID: f.ID,
|
||||
URL: h.urls.GenerateDownloadURL(f.ID),
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
Tags: []string{},
|
||||
ID: f.ID,
|
||||
URL: h.urls.GenerateDownloadURL(f.ID),
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
Tags: []string{},
|
||||
CreatedAt: metadata.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func (s *URLService) GenerateDownloadURL(fileUUID string) string {
|
||||
sig := s.sign(fileUUID, expires)
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%s/api/v1/files/%s?expires=%d&sig=%s",
|
||||
"%s/api/v1/files/download/%s?expires=%d&sig=%s",
|
||||
s.serverHost,
|
||||
fileUUID,
|
||||
expires,
|
||||
|
||||
+35
-13
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { View, FlatList, StyleSheet, TouchableOpacity, Text, Dimensions, Image } from 'react-native';
|
||||
import { View, FlatList, StyleSheet, TouchableOpacity, Text, Dimensions, Image, ActivityIndicator } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { useFiles } from '../hooks/useFiles';
|
||||
import { useFiles, useFileImage } from '../hooks/useFiles';
|
||||
import { FileItem } from '../types';
|
||||
|
||||
const NUM_COLUMNS = 3;
|
||||
@@ -20,10 +20,18 @@ type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
|
||||
type GroupedFiles = Record<string, FileItem[]>;
|
||||
|
||||
function parseBackendDate(dateStr: string): Date | null {
|
||||
if (!dateStr) return null;
|
||||
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})/);
|
||||
if (!match) return new Date(dateStr);
|
||||
return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6]));
|
||||
}
|
||||
|
||||
function groupByDate(files: FileItem[]): GroupedFiles {
|
||||
const groups: GroupedFiles = {};
|
||||
for (const file of files) {
|
||||
const d = new Date(file.createdAt);
|
||||
const d = parseBackendDate(file.createdAt);
|
||||
if (!d) continue;
|
||||
const key = d.toLocaleDateString('fr-FR', {
|
||||
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
|
||||
});
|
||||
@@ -80,16 +88,7 @@ export function HomeScreen() {
|
||||
<Text style={styles.sectionTitle}>{formatDateLabel(dateKey)}</Text>
|
||||
<View style={styles.grid}>
|
||||
{groupFiles.map((file) => (
|
||||
<View key={file.id} style={styles.gridItem}>
|
||||
{file.url ? (
|
||||
<Image source={{ uri: file.url }} style={styles.thumb} />
|
||||
) : (
|
||||
<View style={styles.placeholder}>
|
||||
<Text style={styles.placeholderText}>PDF</Text>
|
||||
</View>
|
||||
)}
|
||||
<Text style={styles.fileName} numberOfLines={1}>{file.name}</Text>
|
||||
</View>
|
||||
<FileGridItem key={file.id} file={file} />
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
@@ -123,6 +122,29 @@ export function HomeScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
function FileGridItem({ file }: { file: FileItem }) {
|
||||
const { data, isLoading } = useFileImage(file.id);
|
||||
|
||||
const uri = data?.data?.url;
|
||||
|
||||
return (
|
||||
<View style={styles.gridItem}>
|
||||
{isLoading ? (
|
||||
<View style={styles.placeholder}>
|
||||
<ActivityIndicator size="small" color="#1976D2" />
|
||||
</View>
|
||||
) : uri ? (
|
||||
<Image source={{ uri }} style={styles.thumb} />
|
||||
) : (
|
||||
<View style={styles.placeholder}>
|
||||
<Text style={styles.placeholderText}>PDF</Text>
|
||||
</View>
|
||||
)}
|
||||
<Text style={styles.fileName} numberOfLines={1}>{file.name}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
|
||||
@@ -21,6 +21,14 @@ export function useFile(id: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useFileImage(fileId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['fileImage', fileId],
|
||||
queryFn: () => apiClient.get<{ data: { id: string; name: string; url: string; size: number } }>(`${ENDPOINTS.FILE}/${fileId}`),
|
||||
enabled: !!fileId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteFile() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user