download working

This commit is contained in:
dimitar 2024-11-02 00:08:04 +01:00
parent ed1a580b09
commit bc47e1d39a
11 changed files with 544 additions and 355 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ backend/imk-backend/dist
backend/imk-backend/test
frontend/imk/node_modules
frontend/imk/dist
node_modules

View File

@ -1,19 +1,33 @@
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
import { Controller, Get, Param, Req, Res, UseGuards } from '@nestjs/common';
import { Response } from 'express';
import { DocumentsService } from './documents.service';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
@Controller('documents')
export class DocumentsController {
constructor(private readonly documentsService: DocumentsService) {}
@Get('shared/:userId')
async getSharedDocuments(@Param('userId') userId: string) {
console.log('userId', userId);
return this.documentsService.getClientDocuments(parseInt(userId));
}
@Get('shared/download/:key')
@UseGuards(JwtAuthGuard)
async downloadDocument(@Param('key') key: string, @Req() req) {
return this.documentsService.downloadDocument(key, req.user.id);
async downloadDocument(
@Param('key') key: string,
@Req() req,
@Res() res: Response
) {
const file = await this.documentsService.downloadDocument(key, req.user.id);
res.set({
'Content-Type': file.contentType,
'Content-Length': file.contentLength,
'Content-Disposition': `attachment; filename="${file.fileName}"`,
});
res.send(file.buffer);
}
}

View File

@ -1,14 +1,72 @@
// import { Injectable } from '@nestjs/common';
// // import { Injectable } from '@nestjs/common';
// // import { PrismaService } from '../prisma/prisma.service';
// // //import { Document } from '@prisma/client';
// // @Injectable()
// // export class DocumentsService {
// // downloadDocument(key: string, id: any) {
// // throw new Error('Method not implemented.');
// // }
// // constructor(private readonly prisma: PrismaService) {}
// // async getClientDocuments(clientId: number) {
// // // return this.prisma.document.findMany({
// // // where: {
// // // sharedWithId: clientId,
// // // },
// // // include: {
// // // sharedWith: {
// // // select: {
// // // id: true,
// // // name: true,
// // // email: true,
// // // },
// // // },
// // // },
// // // });
// // return this.prisma.document.findMany({
// // where: {
// // sharedWithId: clientId,
// // },
// // orderBy: {
// // createdAt: 'desc',
// // },
// // });
// // }
// // }
// import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
// import { PrismaService } from '../prisma/prisma.service';
// //import { Document } from '@prisma/client';
// import { S3Service } from '../s3/s3.service';
// import { Document, User } from '@prisma/client';
// @Injectable()
// export class DocumentsService {
// downloadDocument(key: string, id: any) {
// throw new Error('Method not implemented.');
// }
// constructor(private readonly prisma: PrismaService) {}
// constructor(
// private prisma: PrismaService,
// private s3Service: S3Service,
// ) {}
// async findAllByClient(userId: number): Promise<Document[]> {
// const documents = await this.prisma.document.findMany({
// where: {
// sharedWithId: userId,
// },
// include: {
// sharedWith: {
// select: {
// id: true,
// name: true,
// email: true,
// },
// },
// },
// orderBy: {
// createdAt: 'desc',
// },
// });
// return documents;
// }
// async getClientDocuments(clientId: number) {
// // return this.prisma.document.findMany({
// // where: {
@ -33,44 +91,52 @@
// },
// });
// }
// }
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { S3Service } from '../s3/s3.service';
import { Document, User } from '@prisma/client';
@Injectable()
export class DocumentsService {
constructor(
private prisma: PrismaService,
private s3Service: S3Service,
) {}
async findAllByClient(userId: number): Promise<Document[]> {
const documents = await this.prisma.document.findMany({
where: {
sharedWithId: userId,
},
include: {
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
return documents;
}
async getClientDocuments(clientId: number) {
// return this.prisma.document.findMany({
// async downloadDocument(s3Key: string, userId: number): Promise<any> {
// // Verify document exists and user has access
// const document = await this.prisma.document.findFirst({
// where: {
// sharedWithId: clientId,
// s3Key: s3Key,
// sharedWithId: userId,
// },
// });
// if (!document) {
// throw new NotFoundException('Document not found or access denied');
// }
// try {
// // Get document stream from S3
// const fileStream = await this.s3Service.getObject(s3Key).createReadStream();
// return fileStream;
// } catch (error) {
// console.error('Error downloading document:', error);
// throw new NotFoundException('Document file not found in storage');
// }
// }
// async getAllDocuments(): Promise<Document[]> {
// return this.prisma.document.findMany({
// include: {
// sharedWith: {
// select: {
// id: true,
// name: true,
// email: true,
// },
// },
// },
// orderBy: {
// createdAt: 'desc',
// },
// });
// }
// async shareDocument(documentId: number, userId: number): Promise<Document> {
// const document = await this.prisma.document.update({
// where: { id: documentId },
// data: {
// sharedWithId: userId,
// },
// include: {
// sharedWith: {
@ -82,6 +148,40 @@ export class DocumentsService {
// },
// },
// });
// return document;
// }
// async updateStatus(documentId: number, status: string): Promise<Document> {
// return this.prisma.document.update({
// where: { id: documentId },
// data: { status },
// include: {
// sharedWith: {
// select: {
// id: true,
// name: true,
// email: true,
// },
// },
// },
// });
// }
// }
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { S3Service } from '../s3/s3.service';
import { Document, User } from '@prisma/client';
import { Readable } from 'stream';
@Injectable()
export class DocumentsService {
constructor(
private readonly prisma: PrismaService,
private readonly s3Service: S3Service,
) {}
async getClientDocuments(clientId: number) {
return this.prisma.document.findMany({
where: {
sharedWithId: clientId,
@ -92,8 +192,8 @@ export class DocumentsService {
});
}
async downloadDocument(s3Key: string, userId: number): Promise<any> {
// Verify document exists and user has access
async downloadDocument(s3Key: string, userId: number) {
// Verify document access
const document = await this.prisma.document.findFirst({
where: {
s3Key: s3Key,
@ -106,65 +206,31 @@ export class DocumentsService {
}
try {
// Get document stream from S3
const fileStream = await this.s3Service.getObject(s3Key).createReadStream();
return fileStream;
const s3Response = await this.s3Service.getObject(s3Key);
if (!s3Response.Body) {
throw new Error('No file content received from S3');
}
// Convert the response body to a buffer
const streamBody = s3Response.Body as Readable;
const chunks: Buffer[] = [];
for await (const chunk of streamBody) {
chunks.push(Buffer.from(chunk));
}
const fileBuffer = Buffer.concat(chunks);
return {
buffer: fileBuffer,
contentType: s3Response.ContentType || 'application/octet-stream',
contentLength: s3Response.ContentLength,
fileName: s3Key.split('/').pop() || 'download'
};
} catch (error) {
console.error('Error downloading document:', error);
throw new NotFoundException('Document file not found in storage');
console.error('Error downloading from S3:', error);
throw new NotFoundException('Failed to download file');
}
}
async getAllDocuments(): Promise<Document[]> {
return this.prisma.document.findMany({
include: {
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
}
async shareDocument(documentId: number, userId: number): Promise<Document> {
const document = await this.prisma.document.update({
where: { id: documentId },
data: {
sharedWithId: userId,
},
include: {
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
return document;
}
async updateStatus(documentId: number, status: string): Promise<Document> {
return this.prisma.document.update({
where: { id: documentId },
data: { status },
include: {
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
}
}

View File

@ -5,6 +5,7 @@ import {
DeleteObjectCommand,
GetObjectCommand,
ListObjectsCommand,
PutObjectCommand,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { Upload } from '@aws-sdk/lib-storage';
@ -14,7 +15,6 @@ import { ConfigService } from '@nestjs/config';
export class S3Service {
private s3Client: S3Client;
private readonly logger = new Logger(S3Service.name);
s3: any;
constructor(private configService: ConfigService) {
this.s3Client = new S3Client({
@ -28,42 +28,51 @@ export class S3Service {
});
}
async uploadFile(file: Express.Multer.File, key: string): Promise<string> {
async getObject(key: string) {
try {
const uniqueKey = `${Date.now()}-${key}`; // Ensure unique keys
const upload = new Upload({
client: this.s3Client,
params: {
Bucket: process.env.AWS_S3_BUCKET_NAME,
Key: uniqueKey,
Body: file.buffer,
},
const command = new GetObjectCommand({
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
Key: key,
});
const result = await upload.done();
console.log(`File uploaded successfully: ${uniqueKey}`);
console.log(result);
return uniqueKey;
const response = await this.s3Client.send(command);
return response;
} catch (error) {
console.error(`Error uploading file: ${error.message}`);
this.logger.error(`Error getting object from S3: ${error.message}`);
throw error;
}
}
async uploadFile(file: Express.Multer.File, folder: string): Promise<string> {
try {
const key = `${folder}/${Date.now()}-${file.originalname}`;
const command = new PutObjectCommand({
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
Key: key,
Body: file.buffer,
ContentType: file.mimetype,
});
await this.s3Client.send(command);
return key;
} catch (error) {
this.logger.error(`Error uploading file to S3: ${error.message}`);
throw error;
}
}
async deleteFile(key: string): Promise<void> {
try {
const command = new DeleteObjectCommand({
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
Key: key,
});
await this.s3Client.send(command);
} catch (error) {
this.logger.error(`Error deleting file from S3: ${error.message}`);
throw error;
}
getObject(key: string) {
return this.s3.getObject({
Bucket: process.env.AWS_S3_BUCKET,
Key: key,
});
}
async getFileUrl(key: string): Promise<string> {
@ -75,21 +84,20 @@ export class S3Service {
const url = await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); // URL expires in 1 hour
return url;
}
async testConnection(): Promise<boolean> {
try {
const command = new ListObjectsCommand({
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
MaxKeys: 1,
});
const response = await this.s3Client.send(command);
this.logger.log(
`Successfully connected to S3. Bucket contains ${response.Contents?.length || 0} objects.`,
);
await this.getObject('test-connection');
return true;
} catch (error) {
if (error.name === 'NoSuchKey') {
// This is expected as we're just testing connection
return true;
}
this.logger.error('Failed to connect to S3', error);
return false;
}
}
}

View File

@ -1,4 +1,5 @@
import './App.css'
// import './App.css'
import { AuthProvider } from './hooks/useAuth';
import Navbar from './components/navbar/Navbar'
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from './pages/homepage/Home'
@ -12,13 +13,14 @@ import Gallery from './components/gallery/Gallery.jsx';
import Certificates from './components/Certificates/Certificates.jsx';
import Clients from './components/clients/clients';
import AdminPanel from './components/adminPanel/AdminPanel';
import Login from './components/login/login';
import Dashboard from './components/dashboard/Dashboard';
import Login from './components/login/login';
import ProtectedRoute from './components/protectedRoute/ProtectedRoute';
function App() {
return (
<AuthProvider>
<div >
<BrowserRouter >
<Navbar />
@ -31,9 +33,13 @@ function App() {
<Route path='/ultrasound' element={<UltraSound />} />
<Route path='/gallery' element={<Gallery />} />
<Route path='/certificates' element={<Certificates />} />
<Route path='/clients' element={<Clients />} />
<Route path='/clients' element={
<ProtectedRoute>
<Clients />
</ProtectedRoute>
} />
<Route path='/admin' element={
<ProtectedRoute adminOnly={true}>
<ProtectedRoute>
<AdminPanel />
</ProtectedRoute>
} />
@ -47,6 +53,7 @@ function App() {
<Footer />
</BrowserRouter>
</div>
</AuthProvider>
)
}

View File

@ -4,6 +4,7 @@ import { format } from 'date-fns';
import { FiFolder, FiDownload, FiChevronRight, FiChevronDown } from 'react-icons/fi';
import { useAuth } from '../../hooks/useAuth';
import { getSharedDocuments } from '../../services/api';
import api from '../../services/api';
function Dashboard() {
const [documents, setDocuments] = useState([]);
@ -42,15 +43,51 @@ function Dashboard() {
}, {});
};
// const handleDownload = async (s3Key, fileName) => {
// try {
// const response = await api.get(`/documents/shared/download/${s3Key}`, {
// responseType: 'blob',
// });
// const url = window.URL.createObjectURL(new Blob([response.data]));
// const link = document.createElement('a');
// link.href = url;
// link.setAttribute('download', fileName); // or use the actual filename
// document.body.appendChild(link);
// link.click();
// link.parentNode.removeChild(link);
// } catch (err) {
// console.error('Error downloading document:', err);
// alert('Failed to download document. Please try again.');
// }
// };
// const groupDocumentsByCompanyAndDate = (docs) => {
// return docs.reduce((acc, doc) => {
// const folderName = `${doc.sharedWith?.name || 'Unknown'}-${format(new Date(doc.createdAt), 'yyyy-MM-dd')}`;
// if (!acc[folderName]) {
// acc[folderName] = [];
// }
// acc[folderName].push(doc);
// return acc;
// }, {});
// };
const handleDownload = async (s3Key, fileName) => {
try {
window.open(`http://localhost:3000/documents/shared/download/${s3Key}`, '_blank');
const response = await api.get(`/documents/shared/download/${s3Key}`, {
responseType: 'blob',
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName); // or use the actual filename
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
} catch (err) {
console.error('Error downloading document:', err);
alert('Failed to download document. Please try again.');
}
};
const toggleFolder = (folderName) => {
setExpandedFolders(prev => ({
...prev,

View File

@ -1,157 +1,73 @@
import { useState } from 'react';
import { useAuth } from '../../hooks/useAuth';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import axios from 'axios';
const Login = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [username, setUsername] = useState(''); // Changed from email to username
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
setError('');
};
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError('');
try {
const response = await axios.post('http://localhost:3000/auth/login', formData, {
headers: {
'Content-Type': 'application/json',
},
});
if (response.data.access_token) {
// Store the token
localStorage.setItem('token', response.data.access_token);
// Redirect based on user role (assuming you have this info)
const isAdmin = response.data.isAdmin; // Adjust based on your API response
navigate(isAdmin ? '/admin' : '/dashboard');
} else {
setError('Invalid response from server');
}
await login(username, password); // Changed to pass username and password separately
navigate('/dashboard');
} catch (err) {
setError(
err.response?.data?.message ||
'Неуспешна најава. Проверете ги вашите податоци.'
);
} finally {
setIsLoading(false);
setError('Invalid credentials');
}
};
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white flex items-center justify-center px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-md w-full space-y-8 bg-white p-8 rounded-xl shadow-lg"
>
{/* Header */}
<div className="text-center">
<h2 className="text-3xl font-bold text-gray-900">
Најавете се
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
<p className="mt-2 text-sm text-gray-600">
Внесете ги вашите податоци за најава
</p>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
<div className="space-y-4">
{/* Username Field */}
<div>
<label
htmlFor="username"
className="block text-sm font-medium text-gray-700"
>
Корисничко име
</label>
<input
id="username"
name="username"
type="text"
required
value={formData.username}
onChange={handleInputChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm
focus:border-blue-500 focus:ring-blue-500 sm:text-sm
px-3 py-2 border"
aria-label="Username"
/>
</div>
{/* Password Field */}
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Лозинка
</label>
<input
id="password"
name="password"
type="password"
required
value={formData.password}
onChange={handleInputChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm
focus:border-blue-500 focus:ring-blue-500 sm:text-sm
px-3 py-2 border"
aria-label="Password"
/>
</div>
</div>
{/* Error Message */}
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
{error && (
<div
className="text-red-500 text-sm text-center bg-red-50 p-2 rounded"
role="alert"
>
<div className="text-red-500 text-center text-sm">
{error}
</div>
)}
<div className="rounded-md shadow-sm -space-y-px">
<div>
<input
type="text"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<input
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
</div>
{/* Submit Button */}
<div>
<button
type="submit"
disabled={isLoading}
className="w-full flex justify-center py-3 px-4 border border-transparent
rounded-full text-sm font-semibold text-white bg-blue-600
hover:bg-blue-700 focus:outline-none focus:ring-2
focus:ring-offset-2 focus:ring-blue-500 transition-colors
duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{isLoading ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Најава во тек...
</span>
) : (
'Најави се'
)}
Sign in
</button>
</div>
</form>
</motion.div>
</div>
</div>
);
};

View File

@ -1,23 +1,22 @@
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth';
// eslint-disable-next-line react/prop-types
function ProtectedRoute({ children, adminOnly = false }) {
const { user, loading } = useAuth();
const location = useLocation();
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
if (loading) {
const ProtectedRoute = ({ children }) => {
const { user, isLoading } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (!isLoading && !user) {
navigate('/login');
}
}, [user, isLoading, navigate]);
if (isLoading) {
return <div>Loading...</div>;
}
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (adminOnly && !user.isAdmin) {
return <Navigate to="/dashboard" replace />;
}
return children;
}
return user ? children : null;
};
export default ProtectedRoute;

View File

@ -1,29 +0,0 @@
import { useState, useEffect } from 'react';
import { getUserInfo } from '../services/api';
import * as jwtDecode from 'jwt-decode';
export function useAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
try {
const token = localStorage.getItem('token');
if (token) {
const decodedToken = jwtDecode.jwtDecode(token);
const response = await getUserInfo(decodedToken.sub);
setUser(response.data);
}
} catch (error) {
console.error('Failed to fetch user info:', error);
} finally {
setLoading(false);
}
}
fetchUser();
}, []);
return { user, loading };
}

View File

@ -0,0 +1,84 @@
import { createContext, useContext, useState, useEffect } from 'react';
import api from '../services/api';
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchUser = async () => {
try {
const token = localStorage.getItem('token');
if (!token) {
setIsLoading(false);
return;
}
const response = await api.get('/auth/user-info'); // Updated endpoint
setUser(response.data);
} catch (error) {
console.error('Failed to fetch user info:', error);
localStorage.removeItem('token');
} finally {
setIsLoading(false);
}
};
fetchUser();
}, []);
const login = async (username, password) => {
try {
const response = await api.post('/auth/login', { username, password });
const { access_token } = response.data; // Make sure this matches your backend response
localStorage.setItem('token', access_token);
// After setting token, fetch user info
const userResponse = await api.get('/auth/user-info');
setUser(userResponse.data);
return userResponse.data;
} catch (error) {
console.error('Login error:', error);
throw error;
}
};
// const login = async (username, password) => { // Changed parameters
// try {
// const response = await api.post('/auth/login', { username, password });
// const { token } = response.data; // Updated to match backend response
// localStorage.setItem('token', token);
// // Fetch user info after successful login
// const userResponse = await api.get('/auth/user-info');
// setUser(userResponse.data);
// return userResponse.data;
// } catch (error) {
// console.error('Login error:', error);
// throw error;
// }
// };
const logout = () => {
localStorage.removeItem('token');
setUser(null);
};
return (
<AuthContext.Provider value={{ user, isLoading, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@ -4,17 +4,102 @@ import axios from 'axios';
const API_URL = 'http://localhost:3000';
const api = axios.create({
baseURL: API_URL
baseURL: API_URL,
// withCredentials: true,
});
api.interceptors.request.use((config) => {
// const api = axios.create({
// baseURL: 'http://localhost:3000',
// });
// Add a request interceptor
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
config.headers['Authorization'] = `Bearer ${token}`; // Make sure this matches your backend expectation
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
export const downloadDocument = async (documentId) => {
try {
const response = await api.get(`/documents/download/${documentId}`, {
responseType: 'blob',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
}
});
return response.data;
} catch (error) {
console.error('Download error:', error);
throw error;
}
};
// Request interceptor
// api.interceptors.request.use((config) => {
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// return config;
// });
// // Response interceptor
// api.interceptors.response.use(
// (response) => response,
// (error) => {
// if (error.response?.status === 401) {
// localStorage.removeItem('token');
// }
// return Promise.reject(error);
// }
// );
// const api = axios.create({
// baseURL: API_URL,
// withCredentials: true,
// });
// api.interceptors.request.use((config) => {
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// return config;
// });
// export const createUser = (userData) => api.post('/admin/users', userData);
// const api = axios.create({
// baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
// withCredentials: true,
// });
// // Request interceptor
// api.interceptors.request.use((config) => {
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// return config;
// });
// // Response interceptor
// api.interceptors.response.use(
// (response) => response,
// (error) => {
// if (error.response?.status === 401) {
// localStorage.removeItem('token');
// window.location.href = '/login';
// }
// return Promise.reject(error);
// }
// );
export const createUser = (userData) => {
return api.post('/admin/users', {
name: userData.name,