This commit is contained in:
Dimitar765 committed 2024-10-28 18:43:22 +01:00
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!');
});
});
});
+12
View File
@@ -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();
}
}
+44
View File
@@ -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 {}
+8
View File
@@ -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 },
});
}
}
+11
View File
@@ -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;
}
+11
View File
@@ -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) {}
+9
View File
@@ -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();
}
}
+10
View File
@@ -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();
});
});
+145
View File
@@ -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;
}
}