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", "tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3" "typescript": "^5.1.3"
}, },
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [
"js", "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 @@
/* -- CreateTable
Warnings: 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")
);
*/ -- CreateTable
-- AlterTable CREATE TABLE "User" (
ALTER TABLE "Document" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "id" SERIAL NOT NULL,
ADD COLUMN "status" TEXT NOT NULL DEFAULT 'pending', "email" TEXT NOT NULL,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; "name" TEXT,
"password" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable -- CreateTable
CREATE TABLE "Notification" ( CREATE TABLE "Notification" (
@ -26,12 +41,18 @@ CREATE TABLE "_SharedDocuments" (
"B" INTEGER NOT NULL "B" INTEGER NOT NULL
); );
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex -- CreateIndex
CREATE UNIQUE INDEX "_SharedDocuments_AB_unique" ON "_SharedDocuments"("A", "B"); CREATE UNIQUE INDEX "_SharedDocuments_AB_unique" ON "_SharedDocuments"("A", "B");
-- CreateIndex -- CreateIndex
CREATE INDEX "_SharedDocuments_B_index" ON "_SharedDocuments"("B"); 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 -- AddForeignKey
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 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 { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
} }
@ -13,30 +7,26 @@ datasource db {
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
model Document { model Document {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
title String title String
content String? content String?
published Boolean @default(false) s3Key String
authorId Int status String @default("pending")
author User @relation(fields: [authorId], references: [id]) createdAt DateTime @default(now())
s3Key String updatedAt DateTime @updatedAt
status String @default("pending") // pending, uploading, completed, failed sharedWithId Int
sharedWith User[] @relation("SharedDocuments") sharedWith User @relation("SharedDocuments", fields: [sharedWithId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
} }
model User { model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
email String @unique email String @unique
name String? name String?
password String password String
isAdmin Boolean @default(false) isAdmin Boolean @default(false)
authoredDocuments Document[] @relation("AuthoredDocuments") sharedDocuments Document[] @relation("SharedDocuments")
sharedDocuments Document[] @relation("SharedDocuments") notifications Notification[]
notifications Notification[]
} }
model Notification { model Notification {
@ -44,8 +34,6 @@ model Notification {
message String message String
read Boolean @default(false) read Boolean @default(false)
userId Int userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now()) 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, Post,
Body, Body,
Param, Param,
Delete,
Put, Put,
UseInterceptors, UseInterceptors,
UploadedFile, UploadedFile,
ParseIntPipe, ParseIntPipe,
UseGuards, UseGuards,
Request,
} from '@nestjs/common'; } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express'; import { FileInterceptor } from '@nestjs/platform-express';
import { AdminService } from './admin.service'; 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 { UpdateDocumentDto } from '../dto/update-document.dto';
import { AdminGuard } from '../auth/admin.guard'; import { AdminGuard } from '../auth/admin.guard';
import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { CreateUserDto } from '../dto/create-user.dto'; import { CreateUserDto } from '../dto/create-user.dto';
import { S3Service } from 'src/s3/s3.service'; import { S3Service } from 'src/s3/s3.service';
import { PrismaService } from 'src/prisma/prisma.service';
@Controller('admin') @Controller('admin')
@UseGuards(JwtAuthGuard, AdminGuard) @UseGuards(JwtAuthGuard, AdminGuard)
@ -27,27 +26,9 @@ export class AdminController {
constructor( constructor(
private readonly adminService: AdminService, private readonly adminService: AdminService,
private readonly s3Service: S3Service, 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') @Get('documents')
getAllDocuments() { getAllDocuments() {
return this.adminService.getAllDocuments(); return this.adminService.getAllDocuments();
@ -63,16 +44,27 @@ export class AdminController {
return this.adminService.updateDocument(id, updateDocumentDto, file); 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') @Get('users')
getAllUsers() { getAllUsers() {
return this.adminService.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') @Post('users')
async createUser(@Body() createUserDto: CreateUserDto) { async createUser(@Body() createUserDto: CreateUserDto) {
@ -82,9 +74,9 @@ export class AdminController {
@Post('documents/:id/share') @Post('documents/:id/share')
async shareDocument( async shareDocument(
@Param('id') id: string, @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') @Put('documents/:id/status')
@ -95,25 +87,33 @@ export class AdminController {
return this.adminService.updateDocumentStatus(+id, status); return this.adminService.updateDocumentStatus(+id, status);
} }
// async uploadDocument( @Post('documents')
// @UploadedFile() file: Express.Multer.File, @UseInterceptors(FileInterceptor('file'))
// @Body('title') title: string,
// @Request() req,
// ) {
// return this.adminService.uploadDocument(file, title, req.user.userId);
// }
async uploadDocument( async uploadDocument(
@UploadedFile() file: Express.Multer.File, @UploadedFile() file: Express.Multer.File,
@Body('title') title: string, @Body('title') title: string,
@Body('description') description: number[], @Body('sharedWith') sharedWithId: string,
@Request() req,
) { ) {
return this.adminService.uploadDocument( console.log('Received upload request:', {
file, fileExists: !!file,
fileSize: file?.size,
title, title,
req.user.userId, sharedWithId,
description, });
);
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') @Get('test-s3-connection')
async testS3Connection() { async testS3Connection() {

View File

@ -1,7 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { S3Service } from '../s3/s3.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 { UpdateDocumentDto } from '../dto/update-document.dto';
import { CreateUserDto } from '../dto/create-user.dto'; import { CreateUserDto } from '../dto/create-user.dto';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
@ -13,113 +13,89 @@ export class AdminService {
private s3Service: S3Service, private s3Service: S3Service,
) {} ) {}
// async createDocument( async uploadDocument(
// 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,
file: Express.Multer.File, file: Express.Multer.File,
title: string,
sharedWithId: number,
) { ) {
const { title, content, clientEmail } = createDocumentDto; try {
// First verify the user exists
console.log('Received createDocumentDto:', createDocumentDto); console.log('Verifying user:', sharedWithId);
const user = await this.prisma.user.findUnique({
let authorId: number; where: { id: sharedWithId },
if (clientEmail) {
const author = await this.prisma.user.findUnique({
where: { email: clientEmail },
}); });
if (!author) { if (!user) {
throw new NotFoundException(`User with email ${clientEmail} not found`); 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({ // Upload file to S3
data: { 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, title,
content,
s3Key, s3Key,
author: { connect: { id: authorId } }, sharedWithId,
}, });
});
}
// async getAllDocuments() { const document = await this.prisma.document.create({
// return this.prisma.document.findMany({ data: {
// include: { title,
// sharedWith: { s3Key,
// select: { status: 'completed',
// id: true, sharedWithId,
// name: true, },
// email: true, 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() { async getAllDocuments() {
return this.prisma.document.findMany({ try {
include: { const documents = await this.prisma.document.findMany({
author: { include: {
select: { sharedWith: {
id: true, select: {
name: true, id: true,
email: true, name: true,
email: true,
},
}, },
}, },
sharedWith: { orderBy: {
select: { createdAt: 'desc',
id: true,
name: true,
email: true,
},
}, },
}, });
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( 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) { async createUser(createUserDto: CreateUserDto) {
const hashedPassword = await bcrypt.hash(createUserDto.password, 10); const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
@ -211,25 +136,22 @@ export class AdminService {
}); });
} }
async shareDocument(documentId: number, userIds: number[]) { async shareDocument(documentId: number, userId: number) {
const document = await this.prisma.document.update({ return this.prisma.document.update({
where: { id: documentId }, where: { id: documentId },
data: { data: {
sharedWithId: userId,
},
include: {
sharedWith: { 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) { 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) { async getDocumentUrl(documentId: number) {
const document = await this.prisma.document.findUnique({ const document = await this.prisma.document.findUnique({
where: { id: documentId }, where: { id: documentId },
@ -342,4 +170,34 @@ export class AdminService {
} }
return this.s3Service.getFileUrl(document.s3Key); 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 { PrismaModule } from './prisma/prisma.module';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { AuthController } from './auth/auth.controller'; import { AuthController } from './auth/auth.controller';
import { DocumentsController } from './documents/documents.controller';
@Module({ @Module({
imports: [ imports: [
@ -32,7 +33,7 @@ import { AuthController } from './auth/auth.controller';
S3Module, S3Module,
PrismaModule, PrismaModule,
], ],
controllers: [AppController, AuthController], controllers: [AppController, AuthController, DocumentsController],
providers: [ providers: [
AppService, AppService,
UploadService, UploadService,

View File

@ -10,6 +10,6 @@ export class ClientController {
@Get('documents') @Get('documents')
async getClientDocuments(@User() user) { 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 { export class ClientService {
constructor(private prisma: PrismaService) {} 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({ return this.prisma.document.findMany({
where: { 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 { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { Document } from '@prisma/client'; //import { Document } from '@prisma/client';
@Injectable() @Injectable()
export class DocumentsService { export class DocumentsService {
constructor(private readonly prisma: PrismaService) {} 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({ return this.prisma.document.findMany({
where: { authorId: clientId }, where: {
sharedWithId: clientId,
},
orderBy: {
createdAt: 'desc',
},
}); });
} }
} }

View File

@ -148,8 +148,11 @@ function AdminPanel() {
{doc.status} {doc.status}
</span> </span>
</td> </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'} {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>
<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">
{new Date(doc.createdAt).toLocaleDateString()} {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() { function Clients() {
return ( const [documents, setDocuments] = useState([]);
<div> 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 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> </div>
) );
} }
export default Clients export default Clients;

View File

@ -27,8 +27,11 @@ function DocumentUpload() {
setFile(selectedFile); setFile(selectedFile);
}; };
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault();
console.log('Form submission:', { file, title, selectedUsers });
if (!file || !title || selectedUsers.length === 0) { if (!file || !title || selectedUsers.length === 0) {
setErrorMessage('Please provide a title, file, and select at least one user'); setErrorMessage('Please provide a title, file, and select at least one user');
return; return;
@ -40,21 +43,29 @@ function DocumentUpload() {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('title', title); formData.append('title', title);
formData.append('sharedWith', JSON.stringify(selectedUsers)); formData.append('sharedWith', selectedUsers[0]);
try { 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'); setStatus('completed');
setTitle(''); setTitle('');
setFile(null); setFile(null);
setSelectedUsers([]); setSelectedUsers([]);
event.target.reset(); event.target.reset();
} catch (error) { } catch (error) {
console.error('Upload error:', error);
setStatus('failed'); setStatus('failed');
setErrorMessage(error.response?.data?.message || 'Upload failed'); setErrorMessage(error.response?.data?.message || 'Upload failed');
} }
}; };
return ( return (
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md"> <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> <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_URL = 'http://localhost:3000';
const api = axios.create({ const api = axios.create({
baseURL: API_URL, baseURL: API_URL
}); });
api.interceptors.request.use((config) => { 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 createUser = (userData) => api.post('/admin/users', userData);
export const login = (username, password) => api.post('/auth/login', { username, password }); 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 shareDocument = (documentId, userIds) => api.post(`/admin/documents/${documentId}/share`, { userIds });
export const updateDocumentStatus = (documentId, status) => api.put(`/admin/documents/${documentId}/status`, { status }); 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) => { export const uploadDocument = async (formData) => {
const response = await api.post('/admin/documents', formData, { try {
headers: { const response = await api.post('/admin/documents', formData, {
'Content-Type': 'multipart/form-data', headers: {
}, 'Content-Type': 'multipart/form-data',
}); },
return response.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'); export const getUserInfo = () => api.get('/auth/user-info');
// ... existing code ...
export const getAllDocuments = () => api.get('/admin/documents'); export const getAllDocuments = () => api.get('/admin/documents');
export const getSharedDocuments = (userId) => api.get(`/documents/shared/${userId}`);
export const getAllUsers = () => api.get('/admin/users'); export const getAllUsers = () => api.get('/admin/users');
export default api; export default api;