This commit is contained in:
alma 2025-05-04 14:20:40 +02:00
parent f657d62373
commit 6ef3d4791c
4 changed files with 179 additions and 102 deletions

View File

@ -1,12 +1,15 @@
import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
import { S3Client, ListBucketsCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
import { createUserFolderStructure, listUserObjects } from '@/lib/s3';
// Import the configured S3 client from lib/s3.ts
// Import the configured S3 client and bucket name from lib/s3.ts
import { s3Client as configuredS3Client } from '@/lib/s3';
// Get bucket name from environment
const S3_BUCKET_NAME = process.env.MINIO_AWS_S3_UPLOAD_BUCKET_NAME || 'pages';
// Cache for folder lists
const folderCache = new Map<string, { folders: string[], timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
@ -23,6 +26,7 @@ export async function GET() {
// Check if we have cached folders for this user
const cachedData = folderCache.get(userId);
if (cachedData && (Date.now() - cachedData.timestamp < CACHE_TTL)) {
console.log(`Returning cached folders for user ${userId}:`, cachedData.folders);
return NextResponse.json({
status: 'ready',
folders: cachedData.folders
@ -41,28 +45,34 @@ export async function GET() {
}, { status: 503 });
}
// List the user's base folders
// Direct approach - list all folders under the user prefix
try {
// Standard folder list for the user
const standardFolders = ['notes', 'diary', 'health', 'contacts'];
let userFolders: string[] = [];
// Try to list existing folders
for (const folder of standardFolders) {
try {
const files = await listUserObjects(userId, folder);
if (files.length > 0 || folder === 'notes') {
userFolders.push(folder);
}
} catch (error) {
console.error(`Error checking folder ${folder}:`, error);
}
}
const prefix = `user-${userId}/`;
// List all objects with this prefix to find folders
const command = new ListObjectsV2Command({
Bucket: S3_BUCKET_NAME,
Prefix: prefix,
Delimiter: '/'
});
const response = await configuredS3Client.send(command);
// CommonPrefixes contains the folder paths
const userPrefixes = response.CommonPrefixes || [];
// Extract folder names from prefixes (e.g., "user-123/notes/" → "Notes")
let userFolders = userPrefixes
.map(prefix => prefix.Prefix?.split('/')[1])
.filter(Boolean) as string[];
console.log(`Found ${userFolders.length} folders for user ${userId}:`, userFolders);
// If no folders found, create the standard structure
if (userFolders.length === 0) {
console.log(`No folders found for user ${userId}, creating standard structure`);
await createUserFolderStructure(userId);
userFolders = standardFolders;
userFolders = ['notes', 'diary', 'health', 'contacts'];
}
// Convert to Pascal case for backwards compatibility with NextCloud
@ -70,6 +80,8 @@ export async function GET() {
folder.charAt(0).toUpperCase() + folder.slice(1)
);
console.log(`Returning formatted folders for user ${userId}:`, formattedFolders);
// Update cache
folderCache.set(userId, {
folders: formattedFolders,

View File

@ -1,10 +1,11 @@
"use client";
import { signIn, useSession } from "next-auth/react";
import { useEffect } from "react";
import { useEffect, useState } from "react";
export default function SignIn() {
const { data: session } = useSession();
const [initializationStatus, setInitializationStatus] = useState<string | null>(null);
useEffect(() => {
// Trigger Keycloak sign-in
@ -12,17 +13,36 @@ export default function SignIn() {
}, []);
useEffect(() => {
if (session?.user && !session.user.nextcloudInitialized) {
// Initialize Nextcloud
fetch('/api/nextcloud/init', {
method: 'POST'
}).then(response => {
if (!response.ok) {
console.error('Failed to initialize Nextcloud');
if (session?.user) {
console.log("Session available, checking storage initialization");
// Initialize storage
const initializeStorage = async () => {
try {
setInitializationStatus("initializing");
const response = await fetch('/api/nextcloud/init', {
method: 'POST'
});
if (!response.ok) {
console.error('Failed to initialize storage:', await response.text());
setInitializationStatus("failed");
} else {
console.log('Storage initialized successfully');
setInitializationStatus("success");
// Force reload to ensure session is updated
setTimeout(() => {
window.location.href = "/";
}, 1000);
}
} catch (error) {
console.error('Error initializing storage:', error);
setInitializationStatus("failed");
}
}).catch(error => {
console.error('Error initializing Nextcloud:', error);
});
};
initializeStorage();
}
}, [session]);
@ -36,11 +56,22 @@ export default function SignIn() {
backgroundRepeat: 'no-repeat'
}}
>
<div className="w-full max-w-md space-y-8">
<div className="w-full max-w-md space-y-8 bg-white/90 backdrop-blur-sm p-8 rounded-xl shadow-xl">
<div>
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-white">
Redirecting to login...
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
{initializationStatus === "initializing"
? "Initialisation de votre espace..."
: initializationStatus === "success"
? "Initialisation réussie, redirection..."
: initializationStatus === "failed"
? "Échec de l'initialisation. Veuillez réessayer."
: "Redirection vers la page de connexion..."}
</h2>
{initializationStatus === "initializing" && (
<div className="flex justify-center mt-4">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
)}
</div>
</div>
</div>

View File

@ -67,12 +67,14 @@ export default function Navigation({ nextcloudFolders, onFolderSelect }: Navigat
}
};
// Sort folders according to the specified order
const sortedFolders = [...nextcloudFolders].sort((a, b) => {
const orderA = (FOLDER_CONFIG[a as FolderType]?.order) || 999;
const orderB = (FOLDER_CONFIG[b as FolderType]?.order) || 999;
return orderA - orderB;
});
// Make sure the sortedFolders logic can handle both empty arrays and other issues
const sortedFolders = nextcloudFolders && nextcloudFolders.length > 0
? [...nextcloudFolders].sort((a, b) => {
const orderA = (FOLDER_CONFIG[a as FolderType]?.order) || 999;
const orderB = (FOLDER_CONFIG[b as FolderType]?.order) || 999;
return orderA - orderB;
})
: ['Notes', 'Diary', 'Health', 'Contacts']; // Default folders if none returned from API
const fetchContactFiles = async () => {
try {
@ -201,62 +203,68 @@ export default function Navigation({ nextcloudFolders, onFolderSelect }: Navigat
<div className="flex-1 overflow-y-auto p-4">
<h2 className="text-xs font-semibold text-carnet-text-primary mb-2">VUES</h2>
<div className="space-y-1">
{sortedFolders.map((folder) => {
const config = FOLDER_CONFIG[folder as FolderType];
const Icon = config?.icon || FileText;
const displayName = config?.displayName || folder;
return (
<div key={folder}>
<button
onClick={() => {
if (folder === 'Contacts') {
setExpandedContacts(!expandedContacts);
} else {
onFolderSelect(folder);
}
}}
className="w-full flex items-center px-2 py-1.5 text-sm rounded-md text-carnet-text-primary hover:bg-carnet-hover"
>
<Icon className="h-3.5 w-3.5 flex-shrink-0" />
<span className="ml-2 truncate">{displayName}</span>
{folder === 'Contacts' && (
<ChevronRight
className={`h-3.5 w-3.5 flex-shrink-0 ml-auto transition-transform ${
expandedContacts ? 'transform rotate-90' : ''
}`}
/>
)}
</button>
{folder === 'Contacts' && expandedContacts && (
<div className="ml-4 mt-1 space-y-1">
{isLoadingContacts ? (
<div className="px-3 py-2 text-sm text-carnet-text-muted">Chargement...</div>
) : contactFiles.length === 0 ? (
<div className="px-3 py-2 text-sm text-carnet-text-muted">Aucun contact</div>
) : (
contactFiles.map((file) => {
return (
<button
key={file.id}
onClick={() => {
// When clicking a VCF file, we want to select the Contacts folder
// and pass the file information to the parent component
onFolderSelect('Contacts');
// You might want to add a callback here to handle the selected contact
}}
className="w-full flex items-center space-x-2 px-3 py-2 text-sm rounded-md text-carnet-text-muted hover:bg-carnet-hover"
>
<span>{file.basename.replace('.vcf', '')}</span>
</button>
);
})
{sortedFolders.length === 0 ? (
<div className="text-xs text-carnet-text-muted py-1 px-2">
Chargement des dossiers...
</div>
) : (
sortedFolders.map((folder) => {
const config = FOLDER_CONFIG[folder as FolderType];
const Icon = config?.icon || FileText;
const displayName = config?.displayName || folder;
return (
<div key={folder}>
<button
onClick={() => {
if (folder === 'Contacts') {
setExpandedContacts(!expandedContacts);
} else {
onFolderSelect(folder);
}
}}
className="w-full flex items-center px-2 py-1.5 text-sm rounded-md text-carnet-text-primary hover:bg-carnet-hover"
>
<Icon className="h-3.5 w-3.5 flex-shrink-0" />
<span className="ml-2 truncate">{displayName}</span>
{folder === 'Contacts' && (
<ChevronRight
className={`h-3.5 w-3.5 flex-shrink-0 ml-auto transition-transform ${
expandedContacts ? 'transform rotate-90' : ''
}`}
/>
)}
</div>
)}
</div>
);
})}
</button>
{folder === 'Contacts' && expandedContacts && (
<div className="ml-4 mt-1 space-y-1">
{isLoadingContacts ? (
<div className="px-3 py-2 text-sm text-carnet-text-muted">Chargement...</div>
) : contactFiles.length === 0 ? (
<div className="px-3 py-2 text-sm text-carnet-text-muted">Aucun contact</div>
) : (
contactFiles.map((file) => {
return (
<button
key={file.id}
onClick={() => {
// When clicking a VCF file, we want to select the Contacts folder
// and pass the file information to the parent component
onFolderSelect('Contacts');
// You might want to add a callback here to handle the selected contact
}}
className="w-full flex items-center space-x-2 px-3 py-2 text-sm rounded-md text-carnet-text-muted hover:bg-carnet-hover"
>
<span>{file.basename.replace('.vcf', '')}</span>
</button>
);
})
)}
</div>
)}
</div>
);
})
)}
</div>
</div>
</div>

View File

@ -123,6 +123,8 @@ export async function deleteObject(key: string) {
// Create folder structure (In S3, folders are just prefix notations)
export async function createUserFolderStructure(userId: string) {
try {
console.log(`Starting folder structure creation for user: ${userId}`);
// Define the standard folders to create - use lowercase for consistency with S3 operations
// These are the canonical folder names that match what the frontend expects in the "vues" sidebar
const folders = ['notes', 'diary', 'health', 'contacts'];
@ -130,22 +132,46 @@ export async function createUserFolderStructure(userId: string) {
// Also create capitalized versions for backward compatibility with UI components
const capitalizedFolders = ['Notes', 'Diary', 'Health', 'Contacts'];
// Results tracking
const results = {
lowercase: [] as string[],
capitalized: [] as string[]
};
// For S3, creating a folder means creating an empty object with the folder name as a prefix
// First create lowercase versions (primary storage)
for (const folder of folders) {
const key = `user-${userId}/${folder}/`;
console.log(`Creating folder: ${key}`);
await putObject(key, '', 'application/x-directory');
try {
const key = `user-${userId}/${folder}/`;
console.log(`Creating folder: ${key}`);
await putObject(key, '', 'application/x-directory');
results.lowercase.push(folder);
// Create a placeholder file to ensure the folder is visible
const placeholderKey = `user-${userId}/${folder}/.placeholder`;
await putObject(placeholderKey, 'This is a placeholder file to ensure the folder exists.', 'text/plain');
} catch (error) {
console.error(`Error creating lowercase folder ${folder}:`, error);
}
}
// Then create capitalized versions (for backward compatibility)
for (const folder of capitalizedFolders) {
const key = `user-${userId}/${folder}/`;
console.log(`Creating capitalized folder: ${key}`);
await putObject(key, '', 'application/x-directory');
try {
const key = `user-${userId}/${folder}/`;
console.log(`Creating capitalized folder: ${key}`);
await putObject(key, '', 'application/x-directory');
results.capitalized.push(folder);
// Create a placeholder file to ensure the folder is visible
const placeholderKey = `user-${userId}/${folder}/.placeholder`;
await putObject(placeholderKey, 'This is a placeholder file to ensure the folder exists.', 'text/plain');
} catch (error) {
console.error(`Error creating capitalized folder ${folder}:`, error);
}
}
console.log(`Successfully created folder structure for user: ${userId}`);
console.log(`Successfully created folder structure for user: ${userId}`, results);
return true;
} catch (error) {
console.error('Error creating folder structure:', error);