init
This commit is contained in:
1 parent
2b9dad1fbf
commit
8cbdde39c1
139 files changed
+19353
No files matched your search
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AdminController } from './admin.controller';
|
||||
|
||||
describe('AdminController', () => {
|
||||
let controller: AdminController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AdminController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AdminController>(AdminController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
Delete,
|
||||
Put,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
ParseIntPipe,
|
||||
UseGuards,
|
||||
Request,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { AdminService } from './admin.service';
|
||||
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';
|
||||
|
||||
@Controller('admin')
|
||||
@UseGuards(JwtAuthGuard, AdminGuard)
|
||||
export class AdminController {
|
||||
constructor(
|
||||
private readonly adminService: AdminService,
|
||||
private readonly s3Service: S3Service,
|
||||
) {}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Put('documents/:id')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
updateDocument(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() updateDocumentDto: UpdateDocumentDto,
|
||||
@UploadedFile() file?: Express.Multer.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')
|
||||
getAllUsers() {
|
||||
return this.adminService.getAllUsers();
|
||||
}
|
||||
|
||||
@Post('users')
|
||||
async createUser(@Body() createUserDto: CreateUserDto) {
|
||||
return this.adminService.createUser(createUserDto);
|
||||
}
|
||||
|
||||
@Post('documents/:id/share')
|
||||
async shareDocument(
|
||||
@Param('id') id: string,
|
||||
@Body() { userIds }: { userIds: number[] },
|
||||
) {
|
||||
return this.adminService.shareDocument(+id, userIds);
|
||||
}
|
||||
|
||||
@Put('documents/:id/status')
|
||||
async updateDocumentStatus(
|
||||
@Param('id') id: string,
|
||||
@Body() { status }: { status: string },
|
||||
) {
|
||||
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);
|
||||
}
|
||||
@Get('test-s3-connection')
|
||||
async testS3Connection() {
|
||||
const isConnected = await this.s3Service.testConnection();
|
||||
if (isConnected) {
|
||||
return { message: 'Successfully connected to S3' };
|
||||
} else {
|
||||
return { message: 'Failed to connect to S3' };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AdminController } from './admin.controller';
|
||||
import { AdminService } from './admin.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { S3Module } from '../s3/s3.module';
|
||||
|
||||
@Module({
|
||||
controllers: [AdminController],
|
||||
providers: [AdminService],
|
||||
imports: [PrismaModule, S3Module],
|
||||
})
|
||||
export class AdminModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AdminService } from './admin.service';
|
||||
|
||||
describe('AdminService', () => {
|
||||
let service: AdminService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AdminService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AdminService>(AdminService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,202 @@
|
||||
import { BadRequestException, 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 { UpdateDocumentDto } from '../dto/update-document.dto';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
@Injectable()
|
||||
export class AdminService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
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,
|
||||
file: Express.Multer.File,
|
||||
) {
|
||||
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 },
|
||||
});
|
||||
|
||||
if (!author) {
|
||||
throw new NotFoundException(`User with email ${clientEmail} 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');
|
||||
|
||||
return this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
content,
|
||||
s3Key,
|
||||
author: { connect: { id: authorId } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async getAllDocuments() {
|
||||
return this.prisma.document.findMany();
|
||||
}
|
||||
|
||||
async updateDocument(
|
||||
id: number,
|
||||
updateDocumentDto: UpdateDocumentDto,
|
||||
file?: Express.Multer.File,
|
||||
) {
|
||||
const { ...documentData } = updateDocumentDto;
|
||||
let s3Key: string | undefined;
|
||||
|
||||
if (file) {
|
||||
const oldDocument = await this.prisma.document.findUnique({
|
||||
where: { id },
|
||||
select: { s3Key: true },
|
||||
});
|
||||
if (oldDocument?.s3Key) {
|
||||
await this.s3Service.deleteFile(oldDocument.s3Key);
|
||||
}
|
||||
s3Key = await this.s3Service.uploadFile(file, 'documents');
|
||||
}
|
||||
|
||||
return this.prisma.document.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...documentData,
|
||||
...(s3Key && { s3Key }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
return this.prisma.user.create({
|
||||
data: {
|
||||
...createUserDto,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async shareDocument(documentId: number, userIds: number[]) {
|
||||
const document = await this.prisma.document.update({
|
||||
where: { id: documentId },
|
||||
data: {
|
||||
sharedWith: {
|
||||
connect: userIds.map((id: number) => ({ id })),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 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) {
|
||||
return this.prisma.document.update({
|
||||
where: { id: documentId },
|
||||
data: { status },
|
||||
});
|
||||
}
|
||||
|
||||
async uploadDocument(
|
||||
file: Express.Multer.File,
|
||||
title: string,
|
||||
authorId: number,
|
||||
) {
|
||||
const key = `documents/${Date.now()}-${file.originalname}`;
|
||||
const s3Key = await this.s3Service.uploadFile(file, key);
|
||||
|
||||
return this.prisma.document.create({
|
||||
data: {
|
||||
title,
|
||||
s3Key,
|
||||
authorId,
|
||||
status: 'completed',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getDocumentUrl(documentId: number) {
|
||||
const document = await this.prisma.document.findUnique({
|
||||
where: { id: documentId },
|
||||
});
|
||||
if (!document) {
|
||||
throw new NotFoundException('Document not found');
|
||||
}
|
||||
return this.s3Service.getFileUrl(document.s3Key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
//import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AdminModule } from './admin/admin.module';
|
||||
import { ClientModule } from './client/client.module';
|
||||
import { UploadService } from './upload/upload.service';
|
||||
import { DocumentsService } from './documents/documents.service';
|
||||
import { S3Service } from './s3/s3.service';
|
||||
import { S3Module } from './s3/s3.module';
|
||||
import { PrismaService } from './prisma/prisma.service';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AuthController } from './auth/auth.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// TypeOrmModule.forRoot({
|
||||
// type: 'postgres',
|
||||
// host: 'localhost',
|
||||
// port: 5432,
|
||||
// username: 'root',
|
||||
// password: 'admin',
|
||||
// database: 'imk',
|
||||
// synchronize: true,
|
||||
// }),
|
||||
ConfigModule.forRoot(),
|
||||
AuthModule,
|
||||
AdminModule,
|
||||
ClientModule,
|
||||
S3Module,
|
||||
PrismaModule,
|
||||
],
|
||||
controllers: [AppController, AuthController],
|
||||
providers: [
|
||||
AppService,
|
||||
UploadService,
|
||||
DocumentsService,
|
||||
S3Service,
|
||||
PrismaService,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class AdminGuard implements CanActivate {
|
||||
private readonly logger = new Logger(AdminGuard.name);
|
||||
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
return this.validateRequest(request);
|
||||
}
|
||||
|
||||
async validateRequest(request: any): Promise<boolean> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: request.user.userId },
|
||||
});
|
||||
return user?.isAdmin === true;
|
||||
}
|
||||
// async validateRequest(request: any): Promise<boolean> {
|
||||
// this.logger.debug(`Validating request for user ID: ${request.user.userId}`);
|
||||
// const user = await this.prisma.user.findUnique({
|
||||
// where: { id: request.user.userId },
|
||||
// });
|
||||
// const isAdmin = user?.isAdmin === true;
|
||||
// this.logger.debug(`User is admin: ${isAdmin}`);
|
||||
// return isAdmin;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
let authService: AuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: {
|
||||
createUser: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AuthController>(AuthController);
|
||||
authService = module.get<AuthService>(AuthService);
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should call authService.createUser with CreateUserDto', async () => {
|
||||
const createUserDto: CreateUserDto = {
|
||||
name: 'testuser',
|
||||
password: 'password123',
|
||||
email: 'test@example.com',
|
||||
};
|
||||
|
||||
await controller.register(createUserDto);
|
||||
|
||||
expect(authService.createUser).toHaveBeenCalledWith(createUserDto);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Body,
|
||||
UnauthorizedException,
|
||||
UseGuards,
|
||||
Get,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LoginDto } from '../dto/login.dto';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
import { AdminGuard } from './admin.guard';
|
||||
//@UseGuards(JwtAuthGuard, AdminGuard)
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() loginDto: LoginDto) {
|
||||
const user = await this.authService.validateUser(
|
||||
loginDto.username,
|
||||
loginDto.password,
|
||||
);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
return this.authService.login(user);
|
||||
}
|
||||
|
||||
@Post('register')
|
||||
async register(@Body() createUserDto: CreateUserDto) {
|
||||
return this.authService.createUser(createUserDto);
|
||||
}
|
||||
|
||||
//@UseGuards(JwtAuthGuard)
|
||||
@Post('create-admin')
|
||||
async createAdmin(@Body() createUserDto: CreateUserDto) {
|
||||
return this.authService.createUser(createUserDto, true);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user-info')
|
||||
async getUserInfo(@Request() req) {
|
||||
return this.authService.getUserInfo(req.user.userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LocalStrategy } from './local.strategy';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
//import { UsersModule } from '../users/users.module';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { jwtConstants } from './constants';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PassportModule,
|
||||
PrismaModule,
|
||||
JwtModule.register({
|
||||
secret: jwtConstants.secret,
|
||||
signOptions: { expiresIn: '60m' },
|
||||
}),
|
||||
],
|
||||
providers: [AuthService, LocalStrategy, JwtStrategy],
|
||||
exports: [AuthService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthService>(AuthService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Injectable, ConflictException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
async validateUser(username: string, password: string): Promise<any> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { email: username },
|
||||
});
|
||||
if (user && (await bcrypt.compare(password, user.password))) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { password, ...result } = user;
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async login(user: any) {
|
||||
const payload = { username: user.email, sub: user.id };
|
||||
return {
|
||||
access_token: this.jwtService.sign(payload),
|
||||
};
|
||||
}
|
||||
|
||||
async createUser(
|
||||
createUserDto: CreateUserDto,
|
||||
isAdmin: boolean = false,
|
||||
): Promise<any> {
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { email: createUserDto.email },
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
throw new ConflictException('Email already exists');
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
|
||||
|
||||
const newUser = await this.prisma.user.create({
|
||||
data: {
|
||||
email: createUserDto.email,
|
||||
password: hashedPassword,
|
||||
name: createUserDto.name,
|
||||
isAdmin: isAdmin,
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { password, ...result } = newUser;
|
||||
console.log(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getUserInfo(userId: number) {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export const jwtConstants = {
|
||||
secret: '1234',
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { jwtConstants } from './constants';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: jwtConstants.secret,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
return { userId: payload.sub, username: payload.username };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Strategy } from 'passport-local';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private authService: AuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): Promise<any> {
|
||||
const user = await this.authService.validateUser(username, password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ClientController } from './client.controller';
|
||||
|
||||
describe('ClientController', () => {
|
||||
let controller: ClientController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ClientController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ClientController>(ClientController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
import { ClientService } from './client.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { User } from '../decorators/user.decorator';
|
||||
|
||||
@Controller('client')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class ClientController {
|
||||
constructor(private readonly clientService: ClientService) {}
|
||||
|
||||
@Get('documents')
|
||||
async getClientDocuments(@User() user) {
|
||||
return this.clientService.getClientDocuments(user.id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClientService } from './client.service';
|
||||
import { ClientController } from './client.controller';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
providers: [ClientService],
|
||||
controllers: [ClientController],
|
||||
imports: [PrismaModule],
|
||||
})
|
||||
export class ClientModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ClientService } from './client.service';
|
||||
|
||||
describe('ClientService', () => {
|
||||
let service: ClientService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ClientService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ClientService>(ClientService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ClientService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getClientDocuments(userId: string) {
|
||||
return this.prisma.document.findMany({
|
||||
where: {
|
||||
authorId: Number(userId),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
|
||||
export const User = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
return request.user;
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DocumentsService } from './documents.service';
|
||||
|
||||
describe('DocumentsService', () => {
|
||||
let service: DocumentsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [DocumentsService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DocumentsService>(DocumentsService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { Document } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class DocumentsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findAllForClient(clientId: number): Promise<Document[]> {
|
||||
return this.prisma.document.findMany({
|
||||
where: { authorId: clientId },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class LoginDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IsString, IsDate, IsUrl } from 'class-validator';
|
||||
|
||||
export class ClientDocumentDto {
|
||||
@IsString()
|
||||
id: string;
|
||||
|
||||
@IsString()
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
content: string;
|
||||
|
||||
@IsDate()
|
||||
createdAt: Date;
|
||||
|
||||
@IsUrl()
|
||||
fileUrl: string;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IsString, IsOptional, IsEmail } from 'class-validator';
|
||||
|
||||
export class CreateDocumentDto {
|
||||
@IsString()
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
content?: string;
|
||||
|
||||
@IsEmail()
|
||||
@IsOptional()
|
||||
clientEmail?: string;
|
||||
|
||||
file: Express.Multer.File;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { IsString, IsEmail, MinLength } from 'class-validator';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class LoginDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IsString, IsNotEmpty, IsEmail, MinLength } from 'class-validator';
|
||||
|
||||
export class RegisterDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
password: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
role: 'admin' | 'client';
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateDocumentDto } from './create-document.dto';
|
||||
|
||||
export class UpdateDocumentDto extends PartialType(CreateDocumentDto) {}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors();
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
describe('PrismaService', () => {
|
||||
let service: PrismaService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [PrismaService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PrismaService>(PrismaService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService
|
||||
extends PrismaClient
|
||||
implements OnModuleInit, OnModuleDestroy
|
||||
{
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { S3Service } from './s3.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
providers: [S3Service],
|
||||
exports: [S3Service],
|
||||
})
|
||||
export class S3Module {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { S3Service } from './s3.service';
|
||||
|
||||
describe('S3Service', () => {
|
||||
let service: S3Service;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [S3Service],
|
||||
}).compile();
|
||||
|
||||
service = module.get<S3Service>(S3Service);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
// import { Injectable } from '@nestjs/common';
|
||||
// import {
|
||||
// S3Client,
|
||||
// PutObjectCommand,
|
||||
// DeleteObjectCommand,
|
||||
// } from '@aws-sdk/client-s3';
|
||||
// import { Upload } from '@aws-sdk/lib-storage';
|
||||
|
||||
// @Injectable()
|
||||
// export class S3Service {
|
||||
// private s3Client: S3Client;
|
||||
|
||||
// constructor() {
|
||||
// this.s3Client = new S3Client({
|
||||
// region: process.env.AWS_REGION,
|
||||
// credentials: {
|
||||
// accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
// secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async uploadFile(file: Express.Multer.File, key: string): Promise<string> {
|
||||
// const upload = new Upload({
|
||||
// client: this.s3Client,
|
||||
// params: {
|
||||
// Bucket: process.env.AWS_S3_BUCKET_NAME,
|
||||
// Key: key,
|
||||
// Body: file.buffer,
|
||||
// },
|
||||
// });
|
||||
|
||||
// await upload.done();
|
||||
// return key;
|
||||
// }
|
||||
|
||||
// async deleteFile(key: string): Promise<void> {
|
||||
// const command = new DeleteObjectCommand({
|
||||
// Bucket: process.env.AWS_S3_BUCKET_NAME,
|
||||
// Key: key,
|
||||
// });
|
||||
|
||||
// await this.s3Client.send(command);
|
||||
// }
|
||||
// }
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
S3Client,
|
||||
DeleteObjectCommand,
|
||||
GetObjectCommand,
|
||||
ListObjectsCommand,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { Upload } from '@aws-sdk/lib-storage';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class S3Service {
|
||||
private s3Client: S3Client;
|
||||
private readonly logger = new Logger(S3Service.name);
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
this.s3Client = new S3Client({
|
||||
region: this.configService.get('AWS_REGION'),
|
||||
endpoint: this.configService.get('AWS_ENDPOINT_URL'),
|
||||
credentials: {
|
||||
accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'),
|
||||
secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'),
|
||||
},
|
||||
forcePathStyle: true, // Needed for non-AWS S3 compatible services
|
||||
});
|
||||
}
|
||||
|
||||
// async uploadFile(file: Express.Multer.File, key: string): Promise<string> {
|
||||
// const upload = new Upload({
|
||||
// client: this.s3Client,
|
||||
// params: {
|
||||
// Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
|
||||
// Key: key,
|
||||
// Body: file.buffer,
|
||||
// ContentType: file.mimetype,
|
||||
// },
|
||||
// });
|
||||
|
||||
// await upload.done();
|
||||
// return key;
|
||||
// }
|
||||
async uploadFile(file: Express.Multer.File, key: string): Promise<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 result = await upload.done();
|
||||
console.log(`File uploaded successfully: ${uniqueKey}`);
|
||||
console.log(result);
|
||||
return uniqueKey;
|
||||
} catch (error) {
|
||||
console.error(`Error uploading file: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFile(key: string): Promise<void> {
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
|
||||
Key: key,
|
||||
});
|
||||
|
||||
await this.s3Client.send(command);
|
||||
}
|
||||
|
||||
async getFileUrl(key: string): Promise<string> {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.configService.get('AWS_S3_BUCKET_NAME'),
|
||||
Key: key,
|
||||
});
|
||||
|
||||
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.`,
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to connect to S3', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UploadService } from './upload.service';
|
||||
|
||||
describe('UploadService', () => {
|
||||
let service: UploadService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [UploadService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UploadService>(UploadService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { S3Client, S3ClientConfig } from '@aws-sdk/client-s3';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Upload } from '@aws-sdk/lib-storage';
|
||||
|
||||
@Injectable()
|
||||
export class UploadService {
|
||||
private s3Client: S3Client;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
this.s3Client = new S3Client({
|
||||
region: this.configService.get('AWS_REGION'),
|
||||
credentials: {
|
||||
accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'),
|
||||
secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'),
|
||||
},
|
||||
endpoint: this.configService.get('S3_ENDPOINT'),
|
||||
});
|
||||
}
|
||||
|
||||
async uploadFile(file: Express.Multer.File, key: string): Promise<string> {
|
||||
const upload = new Upload({
|
||||
client: this.s3Client,
|
||||
params: {
|
||||
Bucket: this.configService.get('S3_BUCKET'),
|
||||
Key: key,
|
||||
Body: file.buffer,
|
||||
},
|
||||
});
|
||||
const result = await upload.done();
|
||||
return result.Location;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user