pages s3
This commit is contained in:
parent
6ef3d4791c
commit
1a6d0dd6bf
@ -1,18 +1,16 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
||||||
import { S3Client, ListBucketsCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
import { ListBucketsCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
||||||
import { createUserFolderStructure, listUserObjects } from '@/lib/s3';
|
import { createUserFolderStructure } from '@/lib/s3';
|
||||||
|
|
||||||
// Import the configured S3 client and bucket name from lib/s3.ts
|
// Import the configured S3 client and config from lib/s3.ts
|
||||||
import { s3Client as configuredS3Client } from '@/lib/s3';
|
import { s3Client } from '@/lib/s3';
|
||||||
|
import { S3_CONFIG } from '@/lib/s3';
|
||||||
|
|
||||||
// Get bucket name from environment
|
// Simple in-memory cache with consistent expiration
|
||||||
const S3_BUCKET_NAME = process.env.MINIO_AWS_S3_UPLOAD_BUCKET_NAME || 'pages';
|
const CACHE_TTL = 2 * 60 * 1000; // 2 minutes
|
||||||
|
const folderCache = new Map();
|
||||||
// Cache for folder lists
|
|
||||||
const folderCache = new Map<string, { folders: string[], timestamp: number }>();
|
|
||||||
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
@ -22,88 +20,87 @@ export async function GET() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userId = session.user.id;
|
const userId = session.user.id;
|
||||||
|
const cacheKey = `folders:${userId}`;
|
||||||
|
|
||||||
// Check if we have cached folders for this user
|
// Check cache first
|
||||||
const cachedData = folderCache.get(userId);
|
const cachedData = folderCache.get(cacheKey);
|
||||||
if (cachedData && (Date.now() - cachedData.timestamp < CACHE_TTL)) {
|
if (cachedData && (Date.now() - cachedData.timestamp < CACHE_TTL)) {
|
||||||
console.log(`Returning cached folders for user ${userId}:`, cachedData.folders);
|
console.log(`Using cached folders for user ${userId}`);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
folders: cachedData.folders
|
folders: cachedData.folders
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check S3 connectivity using the configured client
|
// Verify S3 connectivity
|
||||||
try {
|
try {
|
||||||
// Simple check by listing buckets
|
await s3Client.send(new ListBucketsCommand({}));
|
||||||
await configuredS3Client.send(new ListBucketsCommand({}));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('S3 connectivity check failed:', error);
|
console.error('S3 connectivity issue:', error);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
error: 'S3 storage service is not accessible',
|
status: 'error',
|
||||||
status: 'error'
|
error: 'Storage service is unavailable'
|
||||||
}, { status: 503 });
|
}, { status: 503 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct approach - list all folders under the user prefix
|
// List folders in the user's path
|
||||||
try {
|
try {
|
||||||
|
// Using the user prefix to list all folders
|
||||||
const prefix = `user-${userId}/`;
|
const prefix = `user-${userId}/`;
|
||||||
|
|
||||||
// List all objects with this prefix to find folders
|
|
||||||
const command = new ListObjectsV2Command({
|
const command = new ListObjectsV2Command({
|
||||||
Bucket: S3_BUCKET_NAME,
|
Bucket: S3_CONFIG.bucket,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
Delimiter: '/'
|
Delimiter: '/'
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await configuredS3Client.send(command);
|
const response = await s3Client.send(command);
|
||||||
|
const prefixes = response.CommonPrefixes || [];
|
||||||
|
|
||||||
// CommonPrefixes contains the folder paths
|
// Extract folder names and convert to display format
|
||||||
const userPrefixes = response.CommonPrefixes || [];
|
let folders = prefixes
|
||||||
|
.map(prefix => {
|
||||||
|
// Extract folder name from path (e.g., user-123/notes/ → notes)
|
||||||
|
const folderName = prefix.Prefix?.split('/')[1] || '';
|
||||||
|
|
||||||
// Extract folder names from prefixes (e.g., "user-123/notes/" → "Notes")
|
// Format folder name for display (capitalize first letter)
|
||||||
let userFolders = userPrefixes
|
return folderName.charAt(0).toUpperCase() + folderName.slice(1);
|
||||||
.map(prefix => prefix.Prefix?.split('/')[1])
|
})
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean); // Remove any empty strings
|
||||||
|
|
||||||
console.log(`Found ${userFolders.length} folders for user ${userId}:`, userFolders);
|
console.log(`Found ${folders.length} folders for user ${userId}`);
|
||||||
|
|
||||||
// If no folders found, create the standard structure
|
// If no folders, create the standard structure
|
||||||
if (userFolders.length === 0) {
|
if (folders.length === 0) {
|
||||||
console.log(`No folders found for user ${userId}, creating standard structure`);
|
console.log(`No folders found, creating structure for user ${userId}`);
|
||||||
await createUserFolderStructure(userId);
|
await createUserFolderStructure(userId);
|
||||||
userFolders = ['notes', 'diary', 'health', 'contacts'];
|
|
||||||
|
// Use standard folder list for display
|
||||||
|
folders = ['Notes', 'Diary', 'Health', 'Contacts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to Pascal case for backwards compatibility with NextCloud
|
// Update cache with the results
|
||||||
const formattedFolders = userFolders.map(folder =>
|
folderCache.set(cacheKey, {
|
||||||
folder.charAt(0).toUpperCase() + folder.slice(1)
|
folders,
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`Returning formatted folders for user ${userId}:`, formattedFolders);
|
|
||||||
|
|
||||||
// Update cache
|
|
||||||
folderCache.set(userId, {
|
|
||||||
folders: formattedFolders,
|
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Return the folder list
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
folders: formattedFolders
|
folders
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching user folders:', error);
|
console.error('Error listing folders:', error);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
error: 'Failed to fetch folders',
|
status: 'error',
|
||||||
status: 'error'
|
error: 'Failed to list folders'
|
||||||
}, { status: 500 });
|
}, { status: 500 });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in storage status check:', error);
|
console.error('Status endpoint error:', error);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
error: 'Internal server error',
|
status: 'error',
|
||||||
status: 'error'
|
error: 'Internal server error'
|
||||||
}, { status: 500 });
|
}, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,9 +196,12 @@ export default function CarnetPage() {
|
|||||||
try {
|
try {
|
||||||
setIsLoadingContacts(true);
|
setIsLoadingContacts(true);
|
||||||
|
|
||||||
|
// Use lowercase for consistency
|
||||||
|
const folderLowercase = folder.toLowerCase();
|
||||||
|
|
||||||
// First, check if we're looking at a specific VCF file
|
// First, check if we're looking at a specific VCF file
|
||||||
if (folder.endsWith('.vcf')) {
|
if (folder.endsWith('.vcf')) {
|
||||||
const response = await fetch(`/api/nextcloud/files/content?path=${encodeURIComponent(`/files/cube-${session?.user?.id}/Private/Contacts/${folder}`)}`);
|
const response = await fetch(`/api/storage/files/content?path=${encodeURIComponent(`user-${session?.user?.id}/${folderLowercase}/${folder}`)}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const { content } = await response.json();
|
const { content } = await response.json();
|
||||||
const contacts = parseVCardContent(content);
|
const contacts = parseVCardContent(content);
|
||||||
@ -209,22 +212,24 @@ export default function CarnetPage() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If not a VCF file, list all VCF files in the folder
|
// If not a VCF file, list all VCF files in the folder
|
||||||
const response = await fetch(`/api/nextcloud/files?folder=${folder}`);
|
const response = await fetch(`/api/storage/files?folder=${folderLowercase}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const files = await response.json();
|
const files = await response.json();
|
||||||
const vcfFiles = files.filter((file: any) => file.basename.endsWith('.vcf'));
|
const vcfFiles = files.filter((file: any) =>
|
||||||
|
file.basename?.endsWith('.vcf') || file.title?.endsWith('.vcf')
|
||||||
|
);
|
||||||
|
|
||||||
// Parse VCF files and extract contact information
|
// Parse VCF files and extract contact information
|
||||||
const parsedContacts = await Promise.all(
|
const parsedContacts = await Promise.all(
|
||||||
vcfFiles.map(async (file: any) => {
|
vcfFiles.map(async (file: any) => {
|
||||||
try {
|
try {
|
||||||
const contentResponse = await fetch(`/api/nextcloud/files/content?path=${encodeURIComponent(file.filename)}`);
|
const contentResponse = await fetch(`/api/storage/files/content?path=${encodeURIComponent(file.id)}`);
|
||||||
if (contentResponse.ok) {
|
if (contentResponse.ok) {
|
||||||
const { content } = await contentResponse.json();
|
const { content } = await contentResponse.json();
|
||||||
const contacts = parseVCardContent(content);
|
const contacts = parseVCardContent(content);
|
||||||
return contacts.map(contact => ({
|
return contacts.map(contact => ({
|
||||||
...contact,
|
...contact,
|
||||||
group: file.basename.replace('.vcf', '')
|
group: (file.basename || file.title)?.replace('.vcf', '')
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@ -252,12 +257,16 @@ export default function CarnetPage() {
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
// Handle folder paths for S3 format - endpoint will try both cases
|
// Convert folder name to lowercase for consistent storage access
|
||||||
const response = await fetch(`/api/nextcloud/files?folder=${selectedFolder}`);
|
const folderLowercase = selectedFolder.toLowerCase();
|
||||||
|
console.log(`Fetching notes from folder: ${folderLowercase}`);
|
||||||
|
|
||||||
|
// Use direct storage API instead of adapter
|
||||||
|
const response = await fetch(`/api/storage/files?folder=${folderLowercase}`);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(`Fetched ${data.length} notes from ${selectedFolder} folder`);
|
console.log(`Fetched ${data.length} notes from ${folderLowercase} folder`);
|
||||||
setNotes(data);
|
setNotes(data);
|
||||||
} else {
|
} else {
|
||||||
console.error('Error fetching notes:', await response.text());
|
console.error('Error fetching notes:', await response.text());
|
||||||
@ -275,24 +284,20 @@ export default function CarnetPage() {
|
|||||||
const handleSaveNote = async (note: Note) => {
|
const handleSaveNote = async (note: Note) => {
|
||||||
try {
|
try {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
// Construct API payload - ensure folder is properly set
|
// Construct API payload with lowercase folder name
|
||||||
const payload = {
|
const payload = {
|
||||||
id: note.id,
|
id: note.id,
|
||||||
title: note.title,
|
title: note.title,
|
||||||
content: note.content,
|
content: note.content,
|
||||||
folder: selectedFolder.toLowerCase(), // Use lowercase for S3 consistency
|
folder: selectedFolder.toLowerCase(), // Use lowercase for storage consistency
|
||||||
mime: "text/markdown"
|
mime: "text/markdown"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use the API endpoint to save the note
|
// Use direct storage API endpoint
|
||||||
const endpoint = note.id ? '/api/nextcloud/files' : '/api/nextcloud/files';
|
const endpoint = '/api/storage/files';
|
||||||
const method = note.id ? 'PUT' : 'POST';
|
const method = note.id ? 'PUT' : 'POST';
|
||||||
|
|
||||||
console.log(`Saving note to ${selectedFolder} using ${method}:`, {
|
console.log(`Saving note to ${selectedFolder.toLowerCase()} using ${method}`);
|
||||||
id: note.id,
|
|
||||||
title: note.title,
|
|
||||||
folder: selectedFolder.toLowerCase()
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const response = await fetch(endpoint, {
|
||||||
method,
|
method,
|
||||||
|
|||||||
@ -14,13 +14,13 @@ export default function SignIn() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session?.user) {
|
if (session?.user) {
|
||||||
console.log("Session available, checking storage initialization");
|
console.log("Session available, initializing storage");
|
||||||
|
|
||||||
// Initialize storage
|
// Initialize storage using direct API
|
||||||
const initializeStorage = async () => {
|
const initializeStorage = async () => {
|
||||||
try {
|
try {
|
||||||
setInitializationStatus("initializing");
|
setInitializationStatus("initializing");
|
||||||
const response = await fetch('/api/nextcloud/init', {
|
const response = await fetch('/api/storage/init', {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -79,12 +79,10 @@ export default function Navigation({ nextcloudFolders, onFolderSelect }: Navigat
|
|||||||
const fetchContactFiles = async () => {
|
const fetchContactFiles = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoadingContacts(true);
|
setIsLoadingContacts(true);
|
||||||
// Use the consistent folder name case that matches S3 structure
|
// Use the direct storage API endpoint and consistent lowercase folder naming
|
||||||
// The endpoint will try both cases, but we should prefer the consistent one
|
const response = await fetch('/api/storage/files?folder=contacts');
|
||||||
const response = await fetch('/api/nextcloud/files?folder=Contacts');
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const files = await response.json();
|
const files = await response.json();
|
||||||
// Only log the number of files received, not their contents
|
|
||||||
console.log(`Received ${files.length} files from storage`);
|
console.log(`Received ${files.length} files from storage`);
|
||||||
// Filter for VCF files and map to ContactFile interface
|
// Filter for VCF files and map to ContactFile interface
|
||||||
const vcfFiles = files
|
const vcfFiles = files
|
||||||
@ -95,7 +93,6 @@ export default function Navigation({ nextcloudFolders, onFolderSelect }: Navigat
|
|||||||
basename: file.basename || file.title,
|
basename: file.basename || file.title,
|
||||||
lastmod: file.lastmod || file.lastModified
|
lastmod: file.lastmod || file.lastModified
|
||||||
}));
|
}));
|
||||||
// Only log the number of VCF files processed
|
|
||||||
console.log(`Processed ${vcfFiles.length} VCF files`);
|
console.log(`Processed ${vcfFiles.length} VCF files`);
|
||||||
setContactFiles(vcfFiles);
|
setContactFiles(vcfFiles);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
83
lib/s3.ts
83
lib/s3.ts
@ -1,31 +1,33 @@
|
|||||||
import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
|
|
||||||
// Environment variables for S3 configuration
|
// Simplified S3 configuration with consistent naming
|
||||||
const S3_BUCKET_URL = process.env.MINIO_S3_UPLOAD_BUCKET_URL || 'https://dome-api.slm-lab.net/';
|
export const S3_CONFIG = {
|
||||||
const S3_REGION = process.env.MINIO_AWS_REGION || 'eu-east-1';
|
endpoint: process.env.S3_ENDPOINT || 'https://dome-api.slm-lab.net/',
|
||||||
const S3_BUCKET_NAME = process.env.MINIO_AWS_S3_UPLOAD_BUCKET_NAME || 'pages';
|
region: process.env.S3_REGION || 'eu-east-1',
|
||||||
const S3_ACCESS_KEY = process.env.MINIO_ACCESS_KEY || process.env.AWS_ACCESS_KEY_ID;
|
bucket: process.env.S3_BUCKET || 'pages',
|
||||||
const S3_SECRET_KEY = process.env.MINIO_SECRET_KEY || process.env.AWS_SECRET_ACCESS_KEY;
|
accessKey: process.env.S3_ACCESS_KEY,
|
||||||
|
secretKey: process.env.S3_SECRET_KEY,
|
||||||
|
}
|
||||||
|
|
||||||
// Basic client config without credentials
|
// Initialize S3 client with standard configuration
|
||||||
const s3Config = {
|
const s3Config = {
|
||||||
region: S3_REGION,
|
region: S3_CONFIG.region,
|
||||||
endpoint: S3_BUCKET_URL,
|
endpoint: S3_CONFIG.endpoint,
|
||||||
forcePathStyle: true, // Required for MinIO
|
forcePathStyle: true, // Required for MinIO
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add credentials if available
|
// Add credentials if available
|
||||||
if (S3_ACCESS_KEY && S3_SECRET_KEY) {
|
if (S3_CONFIG.accessKey && S3_CONFIG.secretKey) {
|
||||||
Object.assign(s3Config, {
|
Object.assign(s3Config, {
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: S3_ACCESS_KEY,
|
accessKeyId: S3_CONFIG.accessKey,
|
||||||
secretAccessKey: S3_SECRET_KEY
|
secretAccessKey: S3_CONFIG.secretKey
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create S3 client with MinIO configuration
|
// Create S3 client
|
||||||
export const s3Client = new S3Client(s3Config);
|
export const s3Client = new S3Client(s3Config);
|
||||||
|
|
||||||
// Helper functions for S3 operations
|
// Helper functions for S3 operations
|
||||||
@ -35,7 +37,7 @@ export async function listUserObjects(userId: string, folder: string) {
|
|||||||
try {
|
try {
|
||||||
const prefix = `user-${userId}/${folder}/`;
|
const prefix = `user-${userId}/${folder}/`;
|
||||||
const command = new ListObjectsV2Command({
|
const command = new ListObjectsV2Command({
|
||||||
Bucket: S3_BUCKET_NAME,
|
Bucket: S3_CONFIG.bucket,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
Delimiter: '/'
|
Delimiter: '/'
|
||||||
});
|
});
|
||||||
@ -63,7 +65,7 @@ export async function listUserObjects(userId: string, folder: string) {
|
|||||||
export async function getObjectContent(key: string) {
|
export async function getObjectContent(key: string) {
|
||||||
try {
|
try {
|
||||||
const command = new GetObjectCommand({
|
const command = new GetObjectCommand({
|
||||||
Bucket: S3_BUCKET_NAME,
|
Bucket: S3_CONFIG.bucket,
|
||||||
Key: key
|
Key: key
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ export async function getObjectContent(key: string) {
|
|||||||
export async function putObject(key: string, content: string, contentType?: string) {
|
export async function putObject(key: string, content: string, contentType?: string) {
|
||||||
try {
|
try {
|
||||||
const command = new PutObjectCommand({
|
const command = new PutObjectCommand({
|
||||||
Bucket: S3_BUCKET_NAME,
|
Bucket: S3_CONFIG.bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
Body: content,
|
Body: content,
|
||||||
ContentType: contentType || (key.endsWith('.md') ? 'text/markdown' : 'text/plain')
|
ContentType: contentType || (key.endsWith('.md') ? 'text/markdown' : 'text/plain')
|
||||||
@ -108,7 +110,7 @@ export async function putObject(key: string, content: string, contentType?: stri
|
|||||||
export async function deleteObject(key: string) {
|
export async function deleteObject(key: string) {
|
||||||
try {
|
try {
|
||||||
const command = new DeleteObjectCommand({
|
const command = new DeleteObjectCommand({
|
||||||
Bucket: S3_BUCKET_NAME,
|
Bucket: S3_CONFIG.bucket,
|
||||||
Key: key
|
Key: key
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -123,55 +125,32 @@ export async function deleteObject(key: string) {
|
|||||||
// Create folder structure (In S3, folders are just prefix notations)
|
// Create folder structure (In S3, folders are just prefix notations)
|
||||||
export async function createUserFolderStructure(userId: string) {
|
export async function createUserFolderStructure(userId: string) {
|
||||||
try {
|
try {
|
||||||
console.log(`Starting folder structure creation for user: ${userId}`);
|
console.log(`Creating folder structure for user: ${userId}`);
|
||||||
|
|
||||||
// Define the standard folders to create - use lowercase for consistency with S3 operations
|
// Define standard folders - use lowercase only for simplicity and consistency
|
||||||
// These are the canonical folder names that match what the frontend expects in the "vues" sidebar
|
|
||||||
const folders = ['notes', 'diary', 'health', 'contacts'];
|
const folders = ['notes', 'diary', 'health', 'contacts'];
|
||||||
|
|
||||||
// Also create capitalized versions for backward compatibility with UI components
|
// Create folders with placeholders
|
||||||
const capitalizedFolders = ['Notes', 'Diary', 'Health', 'Contacts'];
|
const results = [];
|
||||||
|
|
||||||
// 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) {
|
for (const folder of folders) {
|
||||||
try {
|
try {
|
||||||
|
// Create the folder path (just a prefix in S3)
|
||||||
const key = `user-${userId}/${folder}/`;
|
const key = `user-${userId}/${folder}/`;
|
||||||
console.log(`Creating folder: ${key}`);
|
console.log(`Creating folder: ${key}`);
|
||||||
await putObject(key, '', 'application/x-directory');
|
await putObject(key, '', 'application/x-directory');
|
||||||
results.lowercase.push(folder);
|
|
||||||
|
|
||||||
// Create a placeholder file to ensure the folder is visible
|
// Create a placeholder file to ensure the folder exists and is visible
|
||||||
const placeholderKey = `user-${userId}/${folder}/.placeholder`;
|
const placeholderKey = `user-${userId}/${folder}/.placeholder`;
|
||||||
await putObject(placeholderKey, 'This is a placeholder file to ensure the folder exists.', 'text/plain');
|
await putObject(placeholderKey, 'Folder placeholder', 'text/plain');
|
||||||
|
|
||||||
|
results.push(folder);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error creating lowercase folder ${folder}:`, error);
|
console.error(`Error creating folder ${folder}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then create capitalized versions (for backward compatibility)
|
console.log(`Successfully created ${results.length} folders for user ${userId}: ${results.join(', ')}`);
|
||||||
for (const folder of capitalizedFolders) {
|
|
||||||
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}`, results);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating folder structure:', error);
|
console.error('Error creating folder structure:', error);
|
||||||
@ -183,7 +162,7 @@ export async function createUserFolderStructure(userId: string) {
|
|||||||
export async function generatePresignedUrl(key: string, expiresIn = 3600) {
|
export async function generatePresignedUrl(key: string, expiresIn = 3600) {
|
||||||
try {
|
try {
|
||||||
const command = new PutObjectCommand({
|
const command = new PutObjectCommand({
|
||||||
Bucket: S3_BUCKET_NAME,
|
Bucket: S3_CONFIG.bucket,
|
||||||
Key: key
|
Key: key
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
4
types/vcard-parser.d.ts
vendored
Normal file
4
types/vcard-parser.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module 'vcard-parser' {
|
||||||
|
export function parse(vcard: string): any;
|
||||||
|
export function format(vcard: any): string;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user