user reaearch and fix function delete
This commit is contained in:
@@ -1,12 +1,35 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { getDatabase } from '@/lib/db/mongodb';
|
import { getDatabase } from "@/lib/db/mongodb";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
|
||||||
const ALLOWED_COLLECTIONS = [
|
const ALLOWED_COLLECTIONS = [
|
||||||
'accessroles', 'aclentries', 'actions', 'agentcategories', 'agents',
|
"accessroles",
|
||||||
'assistants', 'balances', 'banners', 'conversations', 'conversationtags',
|
"aclentries",
|
||||||
'files', 'groups', 'keys', 'memoryentries', 'messages', 'pluginauths',
|
"actions",
|
||||||
'presets', 'projects', 'promptgroups', 'prompts', 'roles', 'sessions',
|
"agentcategories",
|
||||||
'sharedlinks', 'tokens', 'toolcalls', 'transactions', 'users'
|
"agents",
|
||||||
|
"assistants",
|
||||||
|
"balances",
|
||||||
|
"banners",
|
||||||
|
"conversations",
|
||||||
|
"conversationtags",
|
||||||
|
"files",
|
||||||
|
"groups",
|
||||||
|
"keys",
|
||||||
|
"memoryentries",
|
||||||
|
"messages",
|
||||||
|
"pluginauths",
|
||||||
|
"presets",
|
||||||
|
"projects",
|
||||||
|
"promptgroups",
|
||||||
|
"prompts",
|
||||||
|
"roles",
|
||||||
|
"sessions",
|
||||||
|
"sharedlinks",
|
||||||
|
"tokens",
|
||||||
|
"toolcalls",
|
||||||
|
"transactions",
|
||||||
|
"users",
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
@@ -14,32 +37,53 @@ export async function GET(
|
|||||||
{ params }: { params: Promise<{ collection: string }> }
|
{ params }: { params: Promise<{ collection: string }> }
|
||||||
) {
|
) {
|
||||||
const { collection } = await params;
|
const { collection } = await params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (!ALLOWED_COLLECTIONS.includes(collection)) {
|
if (!ALLOWED_COLLECTIONS.includes(collection)) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Collection non autorisée' },
|
{ error: "Collection non autorisée" },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const page = parseInt(searchParams.get('page') || '1');
|
const page = parseInt(searchParams.get("page") || "1");
|
||||||
const limit = parseInt(searchParams.get('limit') || '20');
|
const limit = parseInt(searchParams.get("limit") || "20");
|
||||||
const filter = JSON.parse(searchParams.get('filter') || '{}');
|
const filter = JSON.parse(searchParams.get("filter") || "{}");
|
||||||
|
|
||||||
|
// Gestion spéciale pour la collection users avec recherche par email ou id
|
||||||
|
if (collection === "users") {
|
||||||
|
const email = searchParams.get("email");
|
||||||
|
const id = searchParams.get("id");
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
filter.email = email.toLowerCase();
|
||||||
|
} else if (id) {
|
||||||
|
// Vérifier si l'ID est un ObjectId valide
|
||||||
|
if (ObjectId.isValid(id)) {
|
||||||
|
filter._id = new ObjectId(id);
|
||||||
|
} else {
|
||||||
|
// Si l'ID n'est pas valide, retourner une erreur
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "ID utilisateur invalide" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const db = await getDatabase();
|
const db = await getDatabase();
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
db.collection(collection)
|
db
|
||||||
|
.collection(collection)
|
||||||
.find(filter)
|
.find(filter)
|
||||||
.skip(skip)
|
.skip(skip)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.toArray(),
|
.toArray(),
|
||||||
db.collection(collection).countDocuments(filter)
|
db.collection(collection).countDocuments(filter),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
@@ -47,13 +91,10 @@ export async function GET(
|
|||||||
total,
|
total,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
totalPages: Math.ceil(total / limit)
|
totalPages: Math.ceil(total / limit),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Erreur lors de la récupération de ${collection}:`, error);
|
console.error(`Erreur lors de la récupération de ${collection}:`, error);
|
||||||
return NextResponse.json(
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
{ error: 'Erreur serveur' },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ import {
|
|||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ChevronLeft, ChevronRight, Search } from "lucide-react";
|
||||||
import { formatDate } from "@/lib/utils";
|
import { formatDate } from "@/lib/utils";
|
||||||
import { LibreChatUser, LibreChatBalance } from "@/lib/types";
|
import { LibreChatUser, LibreChatBalance } from "@/lib/types";
|
||||||
|
|
||||||
|
|
||||||
export function UsersTable() {
|
export function UsersTable() {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const limit = 20;
|
const limit = 20;
|
||||||
|
|
||||||
// Charger les utilisateurs
|
// Charger les utilisateurs
|
||||||
@@ -46,6 +47,20 @@ export function UsersTable() {
|
|||||||
return map;
|
return map;
|
||||||
}, [balances]);
|
}, [balances]);
|
||||||
|
|
||||||
|
// Filtrer les utilisateurs selon le terme de recherche
|
||||||
|
const filteredUsers = useMemo(() => {
|
||||||
|
if (!searchTerm.trim()) {
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchLower = searchTerm.toLowerCase();
|
||||||
|
return users.filter(
|
||||||
|
(user) =>
|
||||||
|
user.name?.toLowerCase().includes(searchLower) ||
|
||||||
|
user.email?.toLowerCase().includes(searchLower)
|
||||||
|
);
|
||||||
|
}, [users, searchTerm]);
|
||||||
|
|
||||||
const totalPages = Math.ceil(total / limit);
|
const totalPages = Math.ceil(total / limit);
|
||||||
|
|
||||||
const handlePrevPage = () => {
|
const handlePrevPage = () => {
|
||||||
@@ -76,7 +91,20 @@ export function UsersTable() {
|
|||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Liste des utilisateurs ({total})</CardTitle>
|
<div className="flex flex-col space-y-4 sm:flex-row sm:items-center sm:justify-between sm:space-y-0">
|
||||||
|
<CardTitle>
|
||||||
|
Liste des utilisateurs ({searchTerm ? filteredUsers.length : total})
|
||||||
|
</CardTitle>
|
||||||
|
<div className="relative w-full sm:w-80">
|
||||||
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Rechercher par nom ou email..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
@@ -93,78 +121,108 @@ export function UsersTable() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.map((user) => {
|
{filteredUsers.length > 0 ? (
|
||||||
const userCredits = creditsMap.get(user._id) || 0;
|
filteredUsers.map((user) => {
|
||||||
const isActive = new Date(user.updatedAt || user.createdAt) >
|
const userCredits = creditsMap.get(user._id) || 0;
|
||||||
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 jours en millisecondes
|
const isActive =
|
||||||
|
new Date(user.updatedAt || user.createdAt) >
|
||||||
return (
|
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 jours en millisecondes
|
||||||
<TableRow key={user._id}>
|
|
||||||
<TableCell>
|
return (
|
||||||
<span className="font-mono text-xs">
|
<TableRow key={user._id}>
|
||||||
{user._id.slice(-8)}
|
<TableCell>
|
||||||
</span>
|
<span className="font-mono text-xs">
|
||||||
</TableCell>
|
{user._id.slice(-8)}
|
||||||
<TableCell>
|
</span>
|
||||||
<span className="font-medium">{user.name}</span>
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell>
|
||||||
<TableCell>
|
<span className="font-medium">{user.name}</span>
|
||||||
<span className="text-sm">{user.email}</span>
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell>
|
||||||
<TableCell>
|
<span className="text-sm">{user.email}</span>
|
||||||
<Badge variant={user.role === 'ADMIN' ? 'default' : 'secondary'}>
|
</TableCell>
|
||||||
{user.role}
|
<TableCell>
|
||||||
</Badge>
|
<Badge
|
||||||
</TableCell>
|
variant={
|
||||||
<TableCell>
|
user.role === "ADMIN" ? "default" : "secondary"
|
||||||
<span className="font-semibold">
|
}
|
||||||
{userCredits.toLocaleString()} crédits
|
>
|
||||||
</span>
|
{user.role}
|
||||||
</TableCell>
|
</Badge>
|
||||||
<TableCell>
|
</TableCell>
|
||||||
<Badge variant={isActive ? 'default' : 'secondary'}>
|
<TableCell>
|
||||||
{isActive ? 'Actif' : 'Inactif'}
|
<span className="font-semibold">
|
||||||
</Badge>
|
{userCredits.toLocaleString()} crédits
|
||||||
</TableCell>
|
</span>
|
||||||
<TableCell>
|
</TableCell>
|
||||||
<span className="text-sm text-muted-foreground">
|
<TableCell>
|
||||||
{formatDate(user.createdAt)}
|
<span className="text-sm text-muted-foreground">
|
||||||
</span>
|
<Badge variant={isActive ? "default" : "secondary"}>
|
||||||
</TableCell>
|
{isActive ? "Actif" : "Inactif"}
|
||||||
</TableRow>
|
</Badge>
|
||||||
);
|
</span>
|
||||||
})}
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{formatDate(user.createdAt)}
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={7}
|
||||||
|
className="text-center py-8 text-muted-foreground"
|
||||||
|
>
|
||||||
|
{searchTerm
|
||||||
|
? `Aucun utilisateur trouvé pour "${searchTerm}"`
|
||||||
|
: "Aucun utilisateur trouvé"}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination - masquée lors de la recherche */}
|
||||||
<div className="flex items-center justify-between space-x-2 py-4">
|
{!searchTerm && (
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="flex items-center justify-between space-x-2 py-4">
|
||||||
Page {page} sur {totalPages} ({total} éléments au total)
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Page {page} sur {totalPages} ({total} éléments au total)
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handlePrevPage}
|
||||||
|
disabled={page <= 1}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
Précédent
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleNextPage}
|
||||||
|
disabled={page >= totalPages}
|
||||||
|
>
|
||||||
|
Suivant
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
)}
|
||||||
<Button
|
|
||||||
variant="outline"
|
{/* Info de recherche */}
|
||||||
size="sm"
|
{searchTerm && (
|
||||||
onClick={handlePrevPage}
|
<div className="py-4 text-sm text-muted-foreground">
|
||||||
disabled={page <= 1}
|
{filteredUsers.length} résultat(s) trouvé(s) pour "{searchTerm}
|
||||||
>
|
"
|
||||||
<ChevronLeft className="h-4 w-4" />
|
|
||||||
Précédent
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleNextPage}
|
|
||||||
disabled={page >= totalPages}
|
|
||||||
>
|
|
||||||
Suivant
|
|
||||||
<ChevronRight className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user