clients page

This commit is contained in:
Dimitar765 2024-10-29 06:47:15 +01:00
parent 3d2b0a5d6d
commit 1a7f2afd79
20 changed files with 458 additions and 402 deletions

View File

@ -71,6 +71,10 @@
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"jest": {
"moduleFileExtensions": [
"js",

View File

@ -1,27 +0,0 @@
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
"password" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Document" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT,
"published" BOOLEAN NOT NULL DEFAULT false,
"authorId" INTEGER NOT NULL,
CONSTRAINT "Document_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- AddForeignKey
ALTER TABLE "Document" ADD CONSTRAINT "Document_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "s3Key" TEXT;

View File

@ -1,8 +0,0 @@
/*
Warnings:
- Made the column `s3Key` on table `Document` required. This step will fail if there are existing NULL values in that column.
*/
-- AlterTable
ALTER TABLE "Document" ALTER COLUMN "s3Key" SET NOT NULL;

View File

@ -1,13 +1,28 @@
/*
Warnings:
-- CreateTable
CREATE TABLE "Document" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT,
"published" BOOLEAN NOT NULL DEFAULT false,
"authorId" INTEGER NOT NULL,
"s3Key" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
- Added the required column `updatedAt` to the `Document` table without a default value. This is not possible if the table is not empty.
CONSTRAINT "Document_pkey" PRIMARY KEY ("id")
);
*/
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "status" TEXT NOT NULL DEFAULT 'pending',
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
"password" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Notification" (
@ -26,12 +41,18 @@ CREATE TABLE "_SharedDocuments" (
"B" INTEGER NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "_SharedDocuments_AB_unique" ON "_SharedDocuments"("A", "B");
-- CreateIndex
CREATE INDEX "_SharedDocuments_B_index" ON "_SharedDocuments"("B");
-- AddForeignKey
ALTER TABLE "Document" ADD CONSTRAINT "Document_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,28 @@
/*
Warnings:
- You are about to drop the column `authorId` on the `Document` table. All the data in the column will be lost.
- You are about to drop the column `published` on the `Document` table. All the data in the column will be lost.
- You are about to drop the `_SharedDocuments` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `sharedWithId` to the `Document` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "Document" DROP CONSTRAINT "Document_authorId_fkey";
-- DropForeignKey
ALTER TABLE "_SharedDocuments" DROP CONSTRAINT "_SharedDocuments_A_fkey";
-- DropForeignKey
ALTER TABLE "_SharedDocuments" DROP CONSTRAINT "_SharedDocuments_B_fkey";
-- AlterTable
ALTER TABLE "Document" DROP COLUMN "authorId",
DROP COLUMN "published",
ADD COLUMN "sharedWithId" INTEGER NOT NULL;
-- DropTable
DROP TABLE "_SharedDocuments";
-- AddForeignKey
ALTER TABLE "Document" ADD CONSTRAINT "Document_sharedWithId_fkey" FOREIGN KEY ("sharedWithId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -1,9 +1,3 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
@ -13,30 +7,26 @@ datasource db {
url = env("DATABASE_URL")
}
model Document {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
s3Key String
status String @default("pending") // pending, uploading, completed, failed
sharedWith User[] @relation("SharedDocuments")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id Int @id @default(autoincrement())
title String
content String?
s3Key String
status String @default("pending")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sharedWithId Int
sharedWith User @relation("SharedDocuments", fields: [sharedWithId], references: [id])
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
password String
isAdmin Boolean @default(false)
authoredDocuments Document[] @relation("AuthoredDocuments")
sharedDocuments Document[] @relation("SharedDocuments")
notifications Notification[]
id Int @id @default(autoincrement())
email String @unique
name String?
password String
isAdmin Boolean @default(false)
sharedDocuments Document[] @relation("SharedDocuments")
notifications Notification[]
}
model Notification {
@ -44,8 +34,6 @@ model Notification {
message String
read Boolean @default(false)
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}

View File

@ -0,0 +1,30 @@
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
const hashedPassword = await bcrypt.hash('admin123', 10);
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
password: hashedPassword,
isAdmin: true,
},
});
console.log({ admin });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -4,22 +4,21 @@ import {
Post,
Body,
Param,
Delete,
Put,
UseInterceptors,
UploadedFile,
ParseIntPipe,
UseGuards,
Request,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AdminService } from './admin.service';
import { CreateDocumentDto } from '../dto/create-document.dto';
//import { CreateDocumentDto } from '../dto/create-document.dto';
import { UpdateDocumentDto } from '../dto/update-document.dto';
import { AdminGuard } from '../auth/admin.guard';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { CreateUserDto } from '../dto/create-user.dto';
import { S3Service } from 'src/s3/s3.service';
import { PrismaService } from 'src/prisma/prisma.service';
@Controller('admin')
@UseGuards(JwtAuthGuard, AdminGuard)
@ -27,27 +26,9 @@ export class AdminController {
constructor(
private readonly adminService: AdminService,
private readonly s3Service: S3Service,
private readonly prisma: PrismaService,
) {}
@Post('documents')
@UseInterceptors(FileInterceptor('file'))
createDocument(
@Body() createDocumentDto: CreateDocumentDto,
@UploadedFile() file: Express.Multer.File,
) {
return this.adminService.createDocument(createDocumentDto, file);
}
// @Post('document')
// @UseInterceptors(FileInterceptor('file'))
// async createDocument(
// @Body() createDocumentDto: CreateDocumentDto,
// @UploadedFile() file: Express.Multer.File,
// ) {
// // The actual file upload is handled by the FileInterceptor
// // We just need to pass the file to the service
// return this.adminService.createDocument(createDocumentDto, file);
// }
@Get('documents')
getAllDocuments() {
return this.adminService.getAllDocuments();
@ -63,16 +44,27 @@ export class AdminController {
return this.adminService.updateDocument(id, updateDocumentDto, file);
}
@Delete('documents/:id')
async deleteDocument(@Param('id') id: string) {
await this.adminService.deleteDocument(+id);
return { message: 'Document deleted successfully' };
}
@Get('users')
getAllUsers() {
return this.adminService.getAllUsers();
}
@Post('test-document')
async testDocumentCreation() {
try {
const document = await this.prisma.document.create({
data: {
title: 'Test Document',
s3Key: 'test-key',
status: 'completed',
sharedWithId: 2, // ID of 'pero' user
},
});
return document;
} catch (error) {
console.error('Test document creation error:', error);
throw error;
}
}
@Post('users')
async createUser(@Body() createUserDto: CreateUserDto) {
@ -82,9 +74,9 @@ export class AdminController {
@Post('documents/:id/share')
async shareDocument(
@Param('id') id: string,
@Body() { userIds }: { userIds: number[] },
@Body() { userId }: { userId: number },
) {
return this.adminService.shareDocument(+id, userIds);
return this.adminService.shareDocument(+id, userId);
}
@Put('documents/:id/status')
@ -95,25 +87,33 @@ export class AdminController {
return this.adminService.updateDocumentStatus(+id, status);
}
// async uploadDocument(
// @UploadedFile() file: Express.Multer.File,
// @Body('title') title: string,
// @Request() req,
// ) {
// return this.adminService.uploadDocument(file, title, req.user.userId);
// }
@Post('documents')
@UseInterceptors(FileInterceptor('file'))
async uploadDocument(
@UploadedFile() file: Express.Multer.File,
@Body('title') title: string,
@Body('description') description: number[],
@Request() req,
@Body('sharedWith') sharedWithId: string,
) {
return this.adminService.uploadDocument(
file,
console.log('Received upload request:', {
fileExists: !!file,
fileSize: file?.size,
title,
req.user.userId,
description,
);
sharedWithId,
});
try {
const document = await this.adminService.uploadDocument(
file,
title,
Number(sharedWithId),
);
console.log('Document created:', document);
return document;
} catch (error) {
console.error('Upload error:', error);
throw error;
}
}
@Get('test-s3-connection')
async testS3Connection() {

View File

@ -1,7 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { S3Service } from '../s3/s3.service';
import { CreateDocumentDto } from '../dto/create-document.dto';
//import { CreateDocumentDto } from '../dto/create-document.dto';
import { UpdateDocumentDto } from '../dto/update-document.dto';
import { CreateUserDto } from '../dto/create-user.dto';
import * as bcrypt from 'bcrypt';
@ -13,113 +13,89 @@ export class AdminService {
private s3Service: S3Service,
) {}
// async createDocument(
// createDocumentDto: CreateDocumentDto,
// file: Express.Multer.File,
// ) {
// const { title, content, clientEmail } = createDocumentDto;
// console.log('Received createDocumentDto:', createDocumentDto);
// let author;
// if (clientEmail) {
// author = await this.prisma.user.findUnique({
// where: { email: clientEmail },
// });
// if (!author) {
// throw new NotFoundException(`User with email ${clientEmail} not found`);
// }
// }
// const s3Key = await this.s3Service.uploadFile(file, 'documents');
// return this.prisma.document.create({
// data: {
// title,
// content,
// s3Key,
// ...(author && { author: { connect: { id: author.id } } }),
// },
// });
// }
async createDocument(
createDocumentDto: CreateDocumentDto,
async uploadDocument(
file: Express.Multer.File,
title: string,
sharedWithId: number,
) {
const { title, content, clientEmail } = createDocumentDto;
console.log('Received createDocumentDto:', createDocumentDto);
let authorId: number;
if (clientEmail) {
const author = await this.prisma.user.findUnique({
where: { email: clientEmail },
try {
// First verify the user exists
console.log('Verifying user:', sharedWithId);
const user = await this.prisma.user.findUnique({
where: { id: sharedWithId },
});
if (!author) {
throw new NotFoundException(`User with email ${clientEmail} not found`);
if (!user) {
throw new NotFoundException(`User with ID ${sharedWithId} not found`);
}
authorId = author.id;
} else {
// If no clientEmail is provided, we'll use a default admin user
// You should replace this with an appropriate default user ID
const defaultAdmin = await this.prisma.user.findFirst({
where: { isAdmin: true },
});
if (!defaultAdmin) {
throw new NotFoundException('No default admin user found');
}
authorId = defaultAdmin.id;
}
const s3Key = await this.s3Service.uploadFile(file, 'documents');
console.log('Found user:', user);
return this.prisma.document.create({
data: {
// Upload file to S3
console.log('Uploading file to S3...');
const s3Key = await this.s3Service.uploadFile(file, 'documents');
console.log('File uploaded to S3:', s3Key);
console.log('Creating document record with data:', {
title,
content,
s3Key,
author: { connect: { id: authorId } },
},
});
}
sharedWithId,
});
// async getAllDocuments() {
// return this.prisma.document.findMany({
// include: {
// sharedWith: {
// select: {
// id: true,
// name: true,
// email: true,
// },
// },
// },
// });
// }
const document = await this.prisma.document.create({
data: {
title,
s3Key,
status: 'completed',
sharedWithId,
},
include: {
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
console.log('Created document:', document);
return document;
} catch (error) {
console.error('Error in uploadDocument:', error);
if (error.code === 'P2002') {
console.error('Unique constraint violation');
}
if (error.code === 'P2003') {
console.error('Foreign key constraint violation');
}
throw error;
}
}
async getAllDocuments() {
return this.prisma.document.findMany({
include: {
author: {
select: {
id: true,
name: true,
email: true,
try {
const documents = await this.prisma.document.findMany({
include: {
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
},
},
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
orderBy: {
createdAt: 'desc',
},
},
orderBy: {
createdAt: 'desc',
},
});
});
console.log('Retrieved documents with shared users:', documents);
return documents;
} catch (error) {
console.error('Error fetching documents:', error);
throw error;
}
}
async updateDocument(
@ -149,57 +125,6 @@ export class AdminService {
},
});
}
async uploadDocument(
file: Express.Multer.File,
title: string,
userId: number,
sharedWith: number[],
) {
let s3Key;
try {
s3Key = await this.s3Service.uploadFile(file, 'documents');
// Log the sharedWith array to verify the data
console.log('Sharing document with users:', sharedWith);
const document = await this.prisma.document.create({
data: {
title,
authorId: userId,
s3Key,
status: 'completed',
sharedWith: {
connect: sharedWith.map((id) => ({ id })),
},
},
include: {
sharedWith: true, // Include this to verify the relation was created
},
});
console.log('Created document:', document);
return document;
} catch (error) {
console.error('Error in uploadDocument:', error);
throw error;
}
}
async deleteDocument(id: number) {
const document = await this.prisma.document.findUnique({
where: { id },
select: { s3Key: true },
});
if (document?.s3Key) {
await this.s3Service.deleteFile(document.s3Key);
}
return this.prisma.document.delete({ where: { id } });
}
async getAllUsers() {
return this.prisma.user.findMany();
}
async createUser(createUserDto: CreateUserDto) {
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
@ -211,25 +136,22 @@ export class AdminService {
});
}
async shareDocument(documentId: number, userIds: number[]) {
const document = await this.prisma.document.update({
async shareDocument(documentId: number, userId: number) {
return this.prisma.document.update({
where: { id: documentId },
data: {
sharedWithId: userId,
},
include: {
sharedWith: {
connect: userIds.map((id: number) => ({ id })),
select: {
id: true,
name: true,
email: true,
},
},
},
});
// Create notifications for shared users
await this.prisma.notification.createMany({
data: userIds.map((userId: number) => ({
userId,
message: `A new document "${document.title}" has been shared with you.`,
})),
});
return document;
}
async updateDocumentStatus(documentId: number, status: string) {
@ -239,100 +161,6 @@ export class AdminService {
});
}
// async uploadDocument(
// file: Express.Multer.File,
// title: string,
// userId: number,
// ) {
// try {
// // First create document with pending status
// const document = await this.prisma.document.create({
// data: {
// title,
// authorId: userId,
// status: 'pending',
// s3Key: '', // Temporary empty key
// },
// });
// // Update status to uploading
// await this.prisma.document.update({
// where: { id: document.id },
// data: { status: 'uploading' },
// });
// // Upload to S3
// const s3Key = await this.s3Service.uploadFile(file, 'documents');
// // Update document with s3Key and completed status
// return this.prisma.document.update({
// where: { id: document.id },
// data: {
// s3Key,
// status: 'completed',
// },
// });
// } catch (error) {
// // If anything fails, update status to failed
// const document = await this.prisma.document.findFirst({
// where: { title, authorId: userId },
// });
// if (document) {
// await this.prisma.document.update({
// where: { id: document.id },
// data: { status: 'failed' },
// });
// }
// throw error;
// }
// }
// problem whith upload status writing to db, i will fix it later
// async uploadDocument(
// file: Express.Multer.File,
// title: string,
// userId: number,
// sharedWith: number[],
// ) {
// let s3Key;
// try {
// // First upload to S3
// s3Key = await this.s3Service.uploadFile(file, 'documents');
// // Then create document with completed status and s3Key
// const document = await this.prisma.document.create({
// data: {
// title,
// authorId: userId,
// s3Key,
// status: 'completed', // Set status to completed immediately after successful upload
// sharedWith: {
// connect: sharedWith.map((id: number) => ({ id })),
// },
// },
// });
// return document;
// } catch (error) {
// // Create document with failed status if upload fails
// if (title && userId) {
// await this.prisma.document.create({
// data: {
// title,
// authorId: userId,
// s3Key: s3Key || '',
// status: 'failed',
// sharedWith: {
// connect: sharedWith.map((id: number) => ({ id })),
// },
// },
// });
// }
// throw error;
// }
// }
async getDocumentUrl(documentId: number) {
const document = await this.prisma.document.findUnique({
where: { id: documentId },
@ -342,4 +170,34 @@ export class AdminService {
}
return this.s3Service.getFileUrl(document.s3Key);
}
async getUserWithDocuments(userId: number) {
return this.prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
name: true,
email: true,
sharedDocuments: true,
},
});
}
async getAllUsers() {
try {
const users = await this.prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
isAdmin: true,
sharedDocuments: true,
},
});
console.log('All users:', users);
return users;
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
}
}

View File

@ -13,6 +13,7 @@ import { PrismaService } from './prisma/prisma.service';
import { PrismaModule } from './prisma/prisma.module';
import { ConfigModule } from '@nestjs/config';
import { AuthController } from './auth/auth.controller';
import { DocumentsController } from './documents/documents.controller';
@Module({
imports: [
@ -32,7 +33,7 @@ import { AuthController } from './auth/auth.controller';
S3Module,
PrismaModule,
],
controllers: [AppController, AuthController],
controllers: [AppController, AuthController, DocumentsController],
providers: [
AppService,
UploadService,

View File

@ -10,6 +10,6 @@ export class ClientController {
@Get('documents')
async getClientDocuments(@User() user) {
return this.clientService.getClientDocuments(user.id);
return this.clientService.getDocuments(user.id);
}
}

View File

@ -5,10 +5,26 @@ import { PrismaService } from '../prisma/prisma.service';
export class ClientService {
constructor(private prisma: PrismaService) {}
async getClientDocuments(userId: string) {
// async getClientDocuments(userId: string) {
// return this.prisma.document.findMany({
// where: {
// authorId: Number(userId),
// },
// });
// }
async getDocuments(userId: string) {
return this.prisma.document.findMany({
where: {
authorId: Number(userId),
sharedWithId: Number(userId),
},
include: {
sharedWith: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DocumentsController } from './documents.controller';
describe('DocumentsController', () => {
let controller: DocumentsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [DocumentsController],
}).compile();
controller = module.get<DocumentsController>(DocumentsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,12 @@
import { Controller, Get, Param } from '@nestjs/common';
import { DocumentsService } from './documents.service';
@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));
}
}

View File

@ -1,14 +1,33 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Document } from '@prisma/client';
//import { Document } from '@prisma/client';
@Injectable()
export class DocumentsService {
constructor(private readonly prisma: PrismaService) {}
async findAllForClient(clientId: number): Promise<Document[]> {
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: { authorId: clientId },
where: {
sharedWithId: clientId,
},
orderBy: {
createdAt: 'desc',
},
});
}
}

View File

@ -148,8 +148,11 @@ function AdminPanel() {
{doc.status}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{/* <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{doc.sharedWith?.map((user) => user.name).join(', ') || 'None'}
</td> */}
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{doc.sharedWith ? `${doc.sharedWith.name} (${doc.sharedWith.email})` : 'None'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(doc.createdAt).toLocaleDateString()}

View File

@ -1,11 +1,93 @@
import React from 'react'
import { useState, useEffect } from 'react'
import { useAuth } from '../../hooks/useAuth';
import { getSharedDocuments } from '../../services/api';
function Clients() {
const [documents, setDocuments] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const { user } = useAuth();
useEffect(() => {
const fetchDocuments = async () => {
try {
const response = await getSharedDocuments(user.id);
setDocuments(response.data);
} catch (err) {
setError('Failed to fetch documents');
console.error('Error fetching documents:', err);
} finally {
setLoading(false);
}
};
if (user) {
fetchDocuments();
}
}, [user]);
if (loading) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
</div>
);
}
if (error) {
return (
<div className="text-center text-red-600 p-4">
{error}
</div>
);
}
return (
<div>
<div className="container mx-auto px-4 py-8">
<h1 className="text-2xl font-bold mb-6">Your Documents</h1>
{documents.length === 0 ? (
<p className="text-gray-600">No documents have been shared with you yet.</p>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{documents.map((doc) => (
<div
key={doc.id}
className="bg-white p-6 rounded-lg shadow-md hover:shadow-lg transition-shadow"
>
<div className="flex justify-between items-start mb-4">
<h2 className="text-xl font-semibold">{doc.title}</h2>
<span className={`px-2 py-1 text-xs rounded-full ${
doc.status === 'completed'
? 'bg-green-100 text-green-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
{doc.status}
</span>
</div>
<div className="text-sm text-gray-600">
<p>Created: {new Date(doc.createdAt).toLocaleDateString()}</p>
{doc.content && (
<p className="mt-2">{doc.content}</p>
)}
</div>
<div className="mt-4 flex justify-end">
<button
onClick={() => window.open(`/api/documents/download/${doc.s3Key}`, '_blank')}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors"
>
Download
</button>
</div>
</div>
))}
</div>
)}
</div>
)
);
}
export default Clients
export default Clients;

View File

@ -27,34 +27,45 @@ function DocumentUpload() {
setFile(selectedFile);
};
const handleSubmit = async (event) => {
const handleSubmit = async (event) => {
event.preventDefault();
console.log('Form submission:', { file, title, selectedUsers });
if (!file || !title || selectedUsers.length === 0) {
setErrorMessage('Please provide a title, file, and select at least one user');
return;
}
setStatus('uploading');
setErrorMessage('');
const formData = new FormData();
formData.append('file', file);
formData.append('title', title);
formData.append('sharedWith', JSON.stringify(selectedUsers));
formData.append('sharedWith', selectedUsers[0]);
try {
await uploadDocument(formData);
console.log('Sending request with:', {
title,
sharedWith: selectedUsers[0],
fileSize: file.size
});
const response = await uploadDocument(formData);
console.log('Upload response:', response);
setStatus('completed');
setTitle('');
setFile(null);
setSelectedUsers([]);
event.target.reset();
} catch (error) {
console.error('Upload error:', error);
setStatus('failed');
setErrorMessage(error.response?.data?.message || 'Upload failed');
}
};
return (
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6">Upload Document</h2>

View File

@ -4,7 +4,7 @@ import axios from 'axios';
const API_URL = 'http://localhost:3000';
const api = axios.create({
baseURL: API_URL,
baseURL: API_URL
});
api.interceptors.request.use((config) => {
@ -16,26 +16,28 @@ api.interceptors.request.use((config) => {
});
export const createUser = (userData) => api.post('/admin/users', userData);
export const login = (username, password) => api.post('/auth/login', { username, password });
// export const getAllUsers = () => api.get('/admin/users');
export const shareDocument = (documentId, userIds) => api.post(`/admin/documents/${documentId}/share`, { userIds });
export const updateDocumentStatus = (documentId, status) => api.put(`/admin/documents/${documentId}/status`, { status });
// export const uploadDocument = (formData) => api.post('/admin/documents', formData, {
// headers: { 'Content-Type': 'multipart/form-data' },
// });
export const uploadDocument = async (formData) => {
const response = await api.post('/admin/documents', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
try {
const response = await api.post('/admin/documents', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
console.log('Upload API response:', response.data);
return response.data;
} catch (error) {
console.error('Upload API error:', error);
throw error;
}
};
export const getUserInfo = () => api.get('/auth/user-info');
// ... existing code ...
export const getAllDocuments = () => api.get('/admin/documents');
export const getSharedDocuments = (userId) => api.get(`/documents/shared/${userId}`);
export const getAllUsers = () => api.get('/admin/users');
export default api;