Add profile image system with MinIO storage
Some checks failed
Build And Push Image / docker (push) Failing after 1m5s

- Implement ProfileAvatar component for user avatars
- Integrate MinIO for profile image storage and management
- Add profile image fields to Member type definition
- Create server utilities and API endpoints for image handling
- Replace basic avatar icon with new ProfileAvatar in dashboard
- Update sharp dependency to v0.34.3
This commit is contained in:
2025-08-14 10:28:40 +02:00
parent 0952d6c381
commit 2ff0c31bbd
11 changed files with 3099 additions and 5 deletions

View File

@@ -0,0 +1,94 @@
import { deleteProfileImage, removeMemberProfileImageUrl } from '~/server/utils/profile-images';
// Authentication utility - we'll need to check if it exists
async function requireAuth(event: any) {
// Check for session-based authentication
const sessionCookie = getCookie(event, 'auth-token') || getCookie(event, 'nuxt-oidc-auth-session');
if (!sessionCookie) {
throw createError({
statusCode: 401,
statusMessage: 'Authentication required',
});
}
// For now, return a basic user object - this should integrate with your existing auth system
const user = event.context.user;
if (!user) {
throw createError({
statusCode: 401,
statusMessage: 'Invalid authentication',
});
}
return user;
}
// Role-based access control
function canEditMember(user: any, targetMemberId: string): boolean {
// Admin can edit anyone
if (user.tier === 'admin' || user.groups?.includes('admin') || user.groups?.includes('monaco-admin')) {
return true;
}
// Board members can edit anyone
if (user.tier === 'board' || user.groups?.includes('board') || user.groups?.includes('monaco-board')) {
return true;
}
// Users can only edit their own profile
return user.email === targetMemberId || user.member_id === targetMemberId;
}
export default defineEventHandler(async (event) => {
try {
// Check authentication
const user = await requireAuth(event);
// Get route parameter
const memberId = getRouterParam(event, 'memberId');
if (!memberId) {
throw createError({
statusCode: 400,
statusMessage: 'Member ID is required',
});
}
// Check permissions
if (!canEditMember(user, memberId)) {
throw createError({
statusCode: 403,
statusMessage: 'You can only delete your own profile image',
});
}
console.log(`[profile-delete] Deleting profile image for member: ${memberId}`);
// Delete image files from MinIO
await deleteProfileImage(memberId);
// Remove image reference from database
await removeMemberProfileImageUrl(memberId);
console.log(`[profile-delete] Successfully deleted profile image for member: ${memberId}`);
return {
success: true,
message: 'Profile image deleted successfully',
memberId,
};
} catch (error: any) {
console.error('[profile-delete] Delete failed:', error);
if (error.statusCode) {
throw error; // Re-throw HTTP errors
}
throw createError({
statusCode: 500,
statusMessage: error.message || 'Failed to delete profile image',
});
}
});

View File

@@ -0,0 +1,61 @@
import { getProfileImageUrl } from '~/server/utils/profile-images';
export default defineEventHandler(async (event) => {
try {
// Get route parameters
const memberId = getRouterParam(event, 'memberId');
const size = getRouterParam(event, 'size') as 'original' | 'small' | 'medium';
if (!memberId) {
throw createError({
statusCode: 400,
statusMessage: 'Member ID is required',
});
}
// Validate size parameter
const validSizes = ['original', 'small', 'medium'];
if (!validSizes.includes(size)) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid size. Must be one of: original, small, medium',
});
}
console.log(`[profile-image] Getting image URL for member: ${memberId}, size: ${size}`);
// Get presigned URL for the image
const imageUrl = await getProfileImageUrl(memberId, size);
if (!imageUrl) {
throw createError({
statusCode: 404,
statusMessage: 'Profile image not found',
});
}
// Set cache headers for better performance
setHeader(event, 'Cache-Control', 'public, max-age=300'); // 5 minutes
setHeader(event, 'Content-Type', 'application/json');
return {
success: true,
imageUrl,
memberId,
size,
expiresIn: 3600, // URLs expire in 1 hour
};
} catch (error: any) {
console.error('[profile-image] Failed to get image URL:', error);
if (error.statusCode) {
throw error; // Re-throw HTTP errors
}
throw createError({
statusCode: 500,
statusMessage: 'Failed to retrieve profile image',
});
}
});