Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions docs/backend/backend_python/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1117,9 +1117,14 @@
"in": "query",
"required": false,
"schema": {
"$ref": "#/components/schemas/InputType",
"allOf": [
{
"$ref": "#/components/schemas/InputType"
}
],
"description": "Choose input type: 'path' or 'base64'",
"default": "path"
"default": "path",
"title": "Input Type"
},
"description": "Choose input type: 'path' or 'base64'"
}
Expand Down Expand Up @@ -2199,7 +2204,6 @@
"metadata": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
Expand Down
123 changes: 123 additions & 0 deletions frontend/src/api/api-functions/albums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { albumsEndpoints } from '../apiEndpoints';
import { apiClient } from '../axiosConfig';

interface CreateAlbumPayload {
name: string;
description?: string;
is_hidden?: boolean;
password?: string;
}
Comment on lines +4 to +9
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Export CreateAlbumPayload for type safety.

The CreateAlbumPayload interface is not exported, but UpdateAlbumRequest (Line 102) is. For consistency and to enable type-safe usage in consuming components, this interface should also be exported.

🔎 Proposed fix
-interface CreateAlbumPayload {
+export interface CreateAlbumPayload {
  name: string;
  description?: string;
  is_hidden?: boolean;
  password?: string;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface CreateAlbumPayload {
name: string;
description?: string;
is_hidden?: boolean;
password?: string;
}
export interface CreateAlbumPayload {
name: string;
description?: string;
is_hidden?: boolean;
password?: string;
}
🤖 Prompt for AI Agents
In frontend/src/api/api-functions/albums.ts around lines 4 to 9, the
CreateAlbumPayload interface is declared but not exported; export it so
consuming modules and components can use the type safely. Modify the declaration
to export the interface (export interface CreateAlbumPayload { ... }) and then
update any local references/imports as needed to use the exported type
consistently with UpdateAlbumRequest.


export interface Album {
album_id: string;
album_name: string;
description: string;
is_hidden: boolean;
}

interface GetAlbumsResponse {
success: boolean;
albums: Album[];
}

export const createAlbum = async (payload: CreateAlbumPayload) => {
try {
const response = await apiClient.post(albumsEndpoints.createAlbum, payload);
return response.data;
} catch (error) {
throw error;
}
};

export const getAlbums = async (): Promise<GetAlbumsResponse> => {
try {
const response = await apiClient.get(albumsEndpoints.getAlbums);
return response.data;
} catch (error) {
throw error;
}
};

export const addImagesToAlbum = async (albumId: string, imageIds: string[]) => {
try {
const response = await apiClient.post(
albumsEndpoints.addImagesToAlbum(albumId),
{
image_ids: imageIds,
},
);
return response.data;
} catch (error) {
throw error;
}
};

interface GetAlbumImagesResponse {
success: boolean;
image_ids: string[];
}

export const getAlbumImages = async (
albumId: string,
password?: string,
): Promise<GetAlbumImagesResponse> => {
try {
const response = await apiClient.post(
albumsEndpoints.getAlbumImages(albumId),
{
password,
},
);
return response.data;
} catch (error) {
throw error;
}
};

export const removeImageFromAlbum = async (
albumId: string,
imageId: string,
) => {
try {
const response = await apiClient.delete(
albumsEndpoints.removeImageFromAlbum(albumId, imageId),
);
return response.data;
} catch (error) {
throw error;
}
};

export const deleteAlbum = async (albumId: string) => {
try {
const response = await apiClient.delete(
albumsEndpoints.deleteAlbum(albumId),
);
return response.data;
} catch (error) {
throw error;
}
};

interface UpdateAlbumRequest {
name: string;
description?: string;
is_hidden: boolean;
current_password?: string;
password?: string;
}

export const updateAlbum = async (
albumId: string,
payload: UpdateAlbumRequest,
) => {
try {
const response = await apiClient.put(
albumsEndpoints.updateAlbum(albumId),
payload,
);
return response.data;
} catch (error) {
throw error;
}
};
1 change: 1 addition & 0 deletions frontend/src/api/api-functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './face_clusters';
export * from './images';
export * from './folders';
export * from './user_preferences';
export * from './albums';
export * from './health';
11 changes: 11 additions & 0 deletions frontend/src/api/apiEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ export const userPreferencesEndpoints = {
export const healthEndpoints = {
healthCheck: '/health',
};

export const albumsEndpoints = {
createAlbum: '/albums/',
getAlbums: '/albums/',
addImagesToAlbum: (albumId: string) => `/albums/${albumId}/images`,
getAlbumImages: (albumId: string) => `/albums/${albumId}/images/get`,
updateAlbum: (albumId: string) => `/albums/${albumId}`,
removeImageFromAlbum: (albumId: string, imageId: string) =>
`/albums/${albumId}/images/${imageId}`,
deleteAlbum: (albumId: string) => `/albums/${albumId}`,
};
93 changes: 93 additions & 0 deletions frontend/src/components/Dialog/AddToAlbumDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useState, useEffect } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { getAlbums, addImagesToAlbum, Album } from '@/api/api-functions';

interface AddToAlbumDialogProps {
isOpen: boolean;
onClose: () => void;
imageIds: string[];
}

export function AddToAlbumDialog({
isOpen,
onClose,
imageIds,
}: AddToAlbumDialogProps) {
const [albums, setAlbums] = useState<Album[]>([]);
const [loading, setLoading] = useState(false);

useEffect(() => {
if (isOpen) {
fetchAlbums();
}
}, [isOpen]);

const fetchAlbums = async () => {
try {
const response = await getAlbums();
if (response.success) {
setAlbums(response.albums);
}
} catch (error) {
console.error('Failed to fetch albums:', error);
}
};

const handleAddToAlbum = async (albumId: string) => {
try {
setLoading(true);
await addImagesToAlbum(albumId, imageIds);
// alert('Images added to album successfully!');
onClose();
} catch (error) {
console.error('Failed to add images to album:', error);
alert('Failed to add images to album');
} finally {
setLoading(false);
}
};

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Add to Album</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
{albums.length === 0 ? (
<p className="text-muted-foreground text-center">
No albums found.
</p>
) : (
<div className="grid gap-2">
{albums.map((album) => (
<Button
key={album.album_id}
variant="outline"
className="justify-start text-left"
onClick={() => handleAddToAlbum(album.album_id)}
disabled={loading}
>
<div className="flex flex-col items-start gap-1">
<span className="font-semibold">{album.album_name}</span>
{album.description && (
<span className="text-muted-foreground text-xs">
{album.description}
</span>
)}
</div>
</Button>
))}
</div>
)}
</div>
</DialogContent>
</Dialog>
);
}
Loading