feat: implemente authorization type to client

master
adriano 2025-09-03 11:50:16 -03:00
parent e8cdc558ff
commit 5bbafd983f
7 changed files with 75 additions and 49 deletions

View File

@ -40,7 +40,7 @@ class UpdateProduct(Resource):
@billing_ns.route('/products') @billing_ns.route('/products')
class ListProducts(Resource): class ListProducts(Resource):
@jwt_required() @jwt_required()
@role_required('admin', 'user') @role_required('admin', 'user', 'client')
def get(self): def get(self):
response = requests.get(url=f'{BILLING_API_URL}/billing/products', headers=HEADERS) response = requests.get(url=f'{BILLING_API_URL}/billing/products', headers=HEADERS)
return response.json(), response.status_code return response.json(), response.status_code

View File

@ -28,7 +28,7 @@ class TranscriptionExport(Resource):
@usage_ns.response(400, 'Validation error') @usage_ns.response(400, 'Validation error')
@usage_ns.response(404, 'File not found') @usage_ns.response(404, 'File not found')
@jwt_required() @jwt_required()
@role_required('admin', 'user') @role_required('admin', 'user', 'client')
def get(self): def get(self):
""" """
Export transcription report in XLSX. Export transcription report in XLSX.
@ -88,7 +88,7 @@ class TranscriptionUsageData(Resource):
@usage_ns.response(200, 'success') @usage_ns.response(200, 'success')
@usage_ns.response(400, 'Validation error') @usage_ns.response(400, 'Validation error')
@jwt_required() @jwt_required()
@role_required('admin', 'user') @role_required('admin', 'user', 'client')
def get(self): def get(self):
""" """
Get transcription report data. Get transcription report data.

View File

@ -4,4 +4,4 @@ from typing import List, Literal
class SigUpRequest(BaseModel): class SigUpRequest(BaseModel):
email: str email: str
password: str password: str
roles: List[Literal["admin", "user"]] roles: List[Literal["admin", "user", "client"]]

View File

@ -10,7 +10,7 @@ import TranscriptionTable from "@/components/transcription-table"
import ModelPricesTable from "@/components/model-prices-table" import ModelPricesTable from "@/components/model-prices-table"
import ProductManagement from "@/components/product-management" import ProductManagement from "@/components/product-management"
import UserManagement from "@/components/user-management" import UserManagement from "@/components/user-management"
import { isAdmin, isTokenExpired, getCurrentUserEmail, getUserRoles } from "@/lib/auth" import { isAdmin, isTokenExpired, getCurrentUserEmail, getUserRoles, isClient } from "@/lib/auth"
export default function Dashboard() { export default function Dashboard() {
const [isAuthenticated, setIsAuthenticated] = useState(false) const [isAuthenticated, setIsAuthenticated] = useState(false)
@ -18,6 +18,7 @@ export default function Dashboard() {
const [userIsAdmin, setUserIsAdmin] = useState(false) const [userIsAdmin, setUserIsAdmin] = useState(false)
const [userEmail, setUserEmail] = useState<string | null>(null) const [userEmail, setUserEmail] = useState<string | null>(null)
const [userRoles, setUserRoles] = useState<string[]>([]) const [userRoles, setUserRoles] = useState<string[]>([])
const [userIsClient, setUserIsClient] = useState(false)
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
@ -34,10 +35,12 @@ export default function Dashboard() {
const email = getCurrentUserEmail() const email = getCurrentUserEmail()
const roles = getUserRoles() const roles = getUserRoles()
const adminStatus = isAdmin() const adminStatus = isAdmin()
const clientStatus = isClient()
setUserEmail(email) setUserEmail(email)
setUserRoles(roles) setUserRoles(roles)
setUserIsAdmin(adminStatus) setUserIsAdmin(adminStatus)
setUserIsClient(clientStatus)
setIsAuthenticated(true) setIsAuthenticated(true)
setIsLoading(false) setIsLoading(false)
}, [router]) }, [router])
@ -83,9 +86,11 @@ export default function Dashboard() {
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8"> <main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<Tabs defaultValue="transcription" className="space-y-6"> <Tabs defaultValue="transcription" className="space-y-6">
<TabsList className={`grid w-full ${userIsAdmin ? "grid-cols-4" : "grid-cols-3"}`}> <TabsList
className={`grid w-full ${userIsAdmin ? "grid-cols-4" : userIsClient ? "grid-cols-2" : "grid-cols-3"}`}
>
<TabsTrigger value="transcription">Dados de Transcrição</TabsTrigger> <TabsTrigger value="transcription">Dados de Transcrição</TabsTrigger>
<TabsTrigger value="models">Preços dos Modelos</TabsTrigger> {!userIsClient && <TabsTrigger value="models">Preços dos Modelos</TabsTrigger>}
<TabsTrigger value="products">Produtos</TabsTrigger> <TabsTrigger value="products">Produtos</TabsTrigger>
{userIsAdmin && <TabsTrigger value="users">Usuários</TabsTrigger>} {userIsAdmin && <TabsTrigger value="users">Usuários</TabsTrigger>}
</TabsList> </TabsList>
@ -102,17 +107,19 @@ export default function Dashboard() {
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="models"> {!userIsClient && (
<Card> <TabsContent value="models">
<CardHeader> <Card>
<CardTitle>Preços dos Modelos</CardTitle> <CardHeader>
<CardDescription>Gerencie os preços dos modelos de IA disponíveis</CardDescription> <CardTitle>Preços dos Modelos</CardTitle>
</CardHeader> <CardDescription>Gerencie os preços dos modelos de IA disponíveis</CardDescription>
<CardContent> </CardHeader>
<ModelPricesTable /> <CardContent>
</CardContent> <ModelPricesTable />
</Card> </CardContent>
</TabsContent> </Card>
</TabsContent>
)}
<TabsContent value="products"> <TabsContent value="products">
<Card> <Card>

View File

@ -9,12 +9,13 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription } from "@/components/ui/alert" import { Alert, AlertDescription } from "@/components/ui/alert"
import { Download, Search, Loader2, DollarSign, TrendingUp, Calculator } from "lucide-react" import { Download, Search, Loader2, DollarSign, TrendingUp, Calculator } from "lucide-react"
import { isClient } from "@/lib/auth"
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1" const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1"
function formatDateBr(dateStr: string) { function formatDateBr(dateStr: string) {
const [year, month, day] = dateStr.split("-"); const [year, month, day] = dateStr.split("-")
return `${day}/${month}/${year}`; return `${day}/${month}/${year}`
} }
export function formatDateTime(raw?: string): string { export function formatDateTime(raw?: string): string {
@ -64,7 +65,7 @@ interface HitTranscriptionData {
tts_cost: number // Custo total dos minutos do audio em texto tts_cost: number // Custo total dos minutos do audio em texto
tts_usage: number // Tempo total de trascrição do audio em segundos tts_usage: number // Tempo total de trascrição do audio em segundos
total_min: number, // Minutos de ligação total_min: number // Minutos de ligação
custo_hit: string // Custo HIT custo_hit: string // Custo HIT
client_total_cost: string // Custo Cliente client_total_cost: string // Custo Cliente
client_price: string // Preço Cliente por Minuto client_price: string // Preço Cliente por Minuto
@ -140,6 +141,8 @@ export default function TranscriptionTable() {
const [currentPage, setCurrentPage] = useState(1) const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(20) const [pageSize, setPageSize] = useState(20)
const [userIsClient, setUserIsClient] = useState(false)
const getAuthHeaders = () => { const getAuthHeaders = () => {
const token = localStorage.getItem("access_token") const token = localStorage.getItem("access_token")
return { return {
@ -295,12 +298,19 @@ export default function TranscriptionTable() {
// Buscar cotação do dólar quando o componente for montado // Buscar cotação do dólar quando o componente for montado
useEffect(() => { useEffect(() => {
fetchExchangeRate() fetchExchangeRate()
const clientStatus = isClient()
setUserIsClient(clientStatus)
// Forçar "client" para usuários client
if (clientStatus) {
setWho("client")
}
}, []) }, [])
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Card de Informações de Custo */} {/* Card de Informações de Custo */}
{costInfo && exchangeRate && ( {!userIsClient && costInfo && exchangeRate && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@ -389,7 +399,7 @@ export default function TranscriptionTable() {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="client">Cliente</SelectItem> <SelectItem value="client">Cliente</SelectItem>
<SelectItem value="hit">HIT</SelectItem> {!userIsClient && <SelectItem value="hit">HIT</SelectItem>}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@ -482,8 +492,8 @@ export default function TranscriptionTable() {
<TableHead>Preço Final LLM ($)</TableHead> <TableHead>Preço Final LLM ($)</TableHead>
<TableHead>Provider LLM</TableHead> <TableHead>Provider LLM</TableHead>
{/* <TableHead>Model TTS</TableHead> */} {/* <TableHead>Model TTS</TableHead> */}
<TableHead>Provider TTS</TableHead> <TableHead>Provider STT</TableHead>
<TableHead>Preço Final TTS ($)</TableHead> <TableHead>Preço Final STT ($)</TableHead>
<TableHead>Segundos Transcritos</TableHead> <TableHead>Segundos Transcritos</TableHead>
</> </>
) : ( ) : (
@ -563,8 +573,6 @@ export default function TranscriptionTable() {
? `R$ ${Number((item as ClientTranscriptionData).client_total_cost).toFixed(2)}` ? `R$ ${Number((item as ClientTranscriptionData).client_total_cost).toFixed(2)}`
: "-"} : "-"}
</TableCell> </TableCell>
</> </>
)} )}
</TableRow> </TableRow>

View File

@ -1,5 +1,7 @@
"use client" "use client"
import { DialogTrigger } from "@/components/ui/dialog"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
@ -9,14 +11,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription } from "@/components/ui/alert" import { Alert, AlertDescription } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox" import { Checkbox } from "@/components/ui/checkbox"
import { import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Edit2, Plus, Save, X, Loader2, RefreshCw, Users, Shield, Trash2 } from "lucide-react" import { Edit2, Plus, Save, X, Loader2, RefreshCw, Users, Shield, Trash2 } from "lucide-react"
import { isAdmin, getCurrentUserEmail } from "@/lib/auth" import { isAdmin, getCurrentUserEmail } from "@/lib/auth"
@ -47,10 +42,21 @@ interface UpdateUserRequest {
roles: string[] roles: string[]
} }
// interface DeleteUserResponse {
// success: boolean
// message: string
// }
// interface UpdateUserResponse {
// success: boolean
// message: string
// }
// Roles disponíveis no sistema // Roles disponíveis no sistema
const AVAILABLE_ROLES = [ const AVAILABLE_ROLES = [
{ id: "admin", label: "Administrador", description: "Acesso total ao sistema" }, { id: "admin", label: "Administrador", description: "Acesso total ao sistema" },
{ id: "user", label: "Usuário", description: "Acesso parcial, não pode gerenciar usuários" }, { id: "user", label: "Usuário", description: "Acesso parcial, não pode gerenciar usuários" },
{ id: "client", label: "Cliente", description: "Acesso limitado, apenas visualização de dados de cliente" },
] ]
export default function UserManagement() { export default function UserManagement() {
@ -105,8 +111,7 @@ export default function UserManagement() {
const errorData = await response.json() const errorData = await response.json()
setError(errorData.message || "Erro ao buscar usuários") setError(errorData.message || "Erro ao buscar usuários")
} }
} catch (err: unknown) { } catch {
console.log(err)
setError("Erro de conexão com o servidor") setError("Erro de conexão com o servidor")
} finally { } finally {
setIsLoading(false) setIsLoading(false)
@ -131,6 +136,7 @@ export default function UserManagement() {
}) })
if (response.ok) { if (response.ok) {
// const result = await response.json()
setSuccess("Usuário criado com sucesso!") setSuccess("Usuário criado com sucesso!")
setCreateForm({ email: "", password: "", roles: [] }) setCreateForm({ email: "", password: "", roles: [] })
setIsCreateDialogOpen(false) setIsCreateDialogOpen(false)
@ -139,8 +145,7 @@ export default function UserManagement() {
const errorData = await response.json() const errorData = await response.json()
setError(errorData.message || "Erro ao criar usuário") setError(errorData.message || "Erro ao criar usuário")
} }
} catch (err:unknown) { } catch {
console.log(err)
setError("Erro de conexão com o servidor") setError("Erro de conexão com o servidor")
} finally { } finally {
setIsCreating(false) setIsCreating(false)
@ -175,6 +180,7 @@ export default function UserManagement() {
}) })
if (response.ok) { if (response.ok) {
// const result: UpdateUserResponse = await response.json()
setSuccess("Usuário atualizado com sucesso!") setSuccess("Usuário atualizado com sucesso!")
setEditingUser(null) setEditingUser(null)
setEditForm({ email: "", password: "", roles: [] }) setEditForm({ email: "", password: "", roles: [] })
@ -183,8 +189,7 @@ export default function UserManagement() {
const errorData = await response.json() const errorData = await response.json()
setError(errorData.message || "Erro ao atualizar usuário") setError(errorData.message || "Erro ao atualizar usuário")
} }
} catch (err) { } catch {
console.log(err)
setError("Erro de conexão com o servidor") setError("Erro de conexão com o servidor")
} finally { } finally {
setIsUpdating(false) setIsUpdating(false)
@ -207,14 +212,14 @@ export default function UserManagement() {
}) })
if (response.ok) { if (response.ok) {
// const result: DeleteUserResponse = await response.json()
setSuccess("Usuário excluído com sucesso!") setSuccess("Usuário excluído com sucesso!")
await fetchUsers() await fetchUsers()
} else { } else {
const errorData = await response.json() const errorData = await response.json()
setError(errorData.message || "Erro ao excluir usuário") setError(errorData.message || "Erro ao excluir usuário")
} }
} catch (err:unknown) { } catch {
console.log(err)
setError("Erro de conexão com o servidor") setError("Erro de conexão com o servidor")
} finally { } finally {
setIsDeleting(null) setIsDeleting(null)
@ -259,6 +264,8 @@ export default function UserManagement() {
return "default" as const return "default" as const
case "user": case "user":
return "secondary" as const return "secondary" as const
case "client":
return "outline" as const
default: default:
return "outline" as const return "outline" as const
} }

View File

@ -61,6 +61,10 @@ export function isAdmin(): boolean {
return hasRole("admin") return hasRole("admin")
} }
export function isClient(): boolean {
return hasRole("client")
}
export function getCurrentUserEmail(): string | null { export function getCurrentUserEmail(): string | null {
try { try {
const token = localStorage.getItem("access_token") const token = localStorage.getItem("access_token")