Standardize code formatting for consistency
Update all NestJS imports to use double quotes instead of single quotes across multiple files. Adjusted indentation in various files to ensure uniform code style. These changes improve code readability and maintainability.
This commit is contained in:
parent
2aa132e511
commit
fd8ad47cf7
@ -1,20 +1,20 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { AdminController } from './admin.controller';
|
import { AdminController } from "./admin.controller";
|
||||||
import { AdminService } from './admin.service';
|
import { AdminService } from "./admin.service";
|
||||||
|
|
||||||
describe('AdminController', () => {
|
describe("AdminController", () => {
|
||||||
let controller: AdminController;
|
let controller: AdminController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [AdminController],
|
controllers: [AdminController],
|
||||||
providers: [AdminService],
|
providers: [AdminService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<AdminController>(AdminController);
|
controller = module.get<AdminController>(AdminController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Controller } from '@nestjs/common';
|
import { Controller } from "@nestjs/common";
|
||||||
import { AdminService } from './admin.service';
|
import { AdminService } from "./admin.service";
|
||||||
|
|
||||||
@Controller('admin')
|
@Controller("admin")
|
||||||
export class AdminController {
|
export class AdminController {
|
||||||
constructor(private readonly adminService: AdminService) {}
|
constructor(private readonly adminService: AdminService) {}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from "@nestjs/common";
|
||||||
import { AdminService } from './admin.service';
|
import { AdminController } from "./admin.controller";
|
||||||
import { AdminController } from './admin.controller';
|
import { AdminService } from "./admin.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [AdminController],
|
controllers: [AdminController],
|
||||||
providers: [AdminService],
|
providers: [AdminService],
|
||||||
})
|
})
|
||||||
export class AdminModule {}
|
export class AdminModule {}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { AdminService } from './admin.service';
|
import { AdminService } from "./admin.service";
|
||||||
|
|
||||||
describe('AdminService', () => {
|
describe("AdminService", () => {
|
||||||
let service: AdminService;
|
let service: AdminService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [AdminService],
|
providers: [AdminService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<AdminService>(AdminService);
|
service = module.get<AdminService>(AdminService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminService {}
|
export class AdminService {}
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
import { AppController } from './app.controller';
|
import { ConfigModule } from "@nestjs/config";
|
||||||
import { AppService } from './app.service';
|
import { ThrottlerModule } from "@nestjs/throttler";
|
||||||
import { DbModule } from './db/db.module';
|
import { AuthorsModule } from "apps/backend/src/app/authors/authors.module";
|
||||||
import { ThrottlerModule } from '@nestjs/throttler';
|
import { AdminModule } from "./admin/admin.module";
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { AppController } from "./app.controller";
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AppService } from "./app.service";
|
||||||
import { CredentialsModule } from './credentials/credentials.module';
|
import { AuthModule } from "./auth/auth.module";
|
||||||
import { FilesModule } from './files/files.module';
|
import { CredentialsModule } from "./credentials/credentials.module";
|
||||||
import { AdminModule } from './admin/admin.module';
|
import { DbModule } from "./db/db.module";
|
||||||
import { GroupsModule } from './groups/groups.module';
|
import { FilesModule } from "./files/files.module";
|
||||||
import { MachinesModule } from './machines/machines.module';
|
import { GroupsModule } from "./groups/groups.module";
|
||||||
import { AuthorsModule } from 'apps/backend/src/app/authors/authors.module';
|
import { MachinesModule } from "./machines/machines.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ThrottlerModule.forRoot([
|
ThrottlerModule.forRoot([
|
||||||
{
|
{
|
||||||
ttl: 60000,
|
ttl: 60000,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
DbModule,
|
DbModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
CredentialsModule,
|
CredentialsModule,
|
||||||
FilesModule,
|
FilesModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
GroupsModule,
|
GroupsModule,
|
||||||
MachinesModule,
|
MachinesModule,
|
||||||
AuthorsModule,
|
AuthorsModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -1,66 +1,67 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus, Patch,
|
HttpStatus,
|
||||||
Post,
|
Patch,
|
||||||
UnauthorizedException,
|
Post,
|
||||||
UseGuards
|
UnauthorizedException,
|
||||||
|
UseGuards,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto";
|
||||||
|
import { AuthService } from "apps/backend/src/app/auth/auth.service";
|
||||||
import { UserGuard } from "./auth.guard";
|
import { UserGuard } from "./auth.guard";
|
||||||
import { AuthService } from 'apps/backend/src/app/auth/auth.service';
|
|
||||||
import { SignInDto, SignUpDto } from 'apps/backend/src/app/auth/auth.dto';
|
|
||||||
|
|
||||||
@Controller("auth")
|
@Controller("auth")
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly authService: AuthService) { }
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
//TODO Initial account validation for admin privileges
|
//TODO Initial account validation for admin privileges
|
||||||
//POST signup
|
//POST signup
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@Post("signup")
|
@Post("signup")
|
||||||
async signUp(@Body() dto: SignUpDto) {
|
async signUp(@Body() dto: SignUpDto) {
|
||||||
console.log(dto);
|
console.log(dto);
|
||||||
return this.authService.doRegister(dto);
|
return this.authService.doRegister(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
//POST signin
|
//POST signin
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("signin")
|
@Post("signin")
|
||||||
async signIn(@Body() dto: SignInDto) {
|
async signIn(@Body() dto: SignInDto) {
|
||||||
console.log(dto);
|
console.log(dto);
|
||||||
return this.authService.doLogin(dto);
|
return this.authService.doLogin(dto);
|
||||||
}
|
}
|
||||||
//GET me -- Get current user data via jwt
|
//GET me -- Get current user data via jwt
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Get("me")
|
@Get("me")
|
||||||
@UseGuards(UserGuard)
|
@UseGuards(UserGuard)
|
||||||
async getMe(@Body() body: object) {
|
async getMe(@Body() body: object) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const targetId = body.sourceUserId
|
const targetId = body.sourceUserId;
|
||||||
const userData = await this.authService.fetchUserById(targetId)
|
const userData = await this.authService.fetchUserById(targetId);
|
||||||
if (!userData) {
|
if (!userData) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
return userData;
|
return userData;
|
||||||
}
|
}
|
||||||
//DELETE me
|
//DELETE me
|
||||||
@HttpCode(HttpStatus.FOUND)
|
@HttpCode(HttpStatus.FOUND)
|
||||||
@Delete("me")
|
@Delete("me")
|
||||||
@UseGuards(UserGuard)
|
@UseGuards(UserGuard)
|
||||||
async deleteMe(@Body() body: object) {
|
async deleteMe(@Body() body: object) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const targetId = body.sourceUserId
|
const targetId = body.sourceUserId;
|
||||||
try {
|
try {
|
||||||
await this.authService.deleteUser(targetId)
|
await this.authService.deleteUser(targetId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
//PATCH me
|
//PATCH me
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Patch("me")
|
@Patch("me")
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
IsEmail,
|
IsEmail,
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsString,
|
IsString,
|
||||||
IsStrongPassword,
|
IsStrongPassword,
|
||||||
MaxLength,
|
MaxLength,
|
||||||
MinLength,
|
MinLength,
|
||||||
} from "class-validator";
|
} from "class-validator";
|
||||||
|
|
||||||
export class SignUpDto {
|
export class SignUpDto {
|
||||||
/*
|
/*
|
||||||
@MinLength(1)
|
@MinLength(1)
|
||||||
@MaxLength(24)
|
@MaxLength(24)
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@ -22,33 +22,33 @@ export class SignUpDto {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
**/
|
**/
|
||||||
|
|
||||||
@MaxLength(32)
|
@MaxLength(32)
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsStrongPassword({
|
@IsStrongPassword({
|
||||||
minLength: 6,
|
minLength: 6,
|
||||||
minSymbols: 1,
|
minSymbols: 1,
|
||||||
})
|
})
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SignInDto {
|
export class SignInDto {
|
||||||
@MaxLength(32)
|
@MaxLength(32)
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsStrongPassword({
|
@IsStrongPassword({
|
||||||
minLength: 6,
|
minLength: 6,
|
||||||
minSymbols: 1,
|
minSymbols: 1,
|
||||||
})
|
})
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,87 +1,89 @@
|
|||||||
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, Inject } from "@nestjs/common";
|
import {
|
||||||
import type { Request } from "express";
|
CanActivate,
|
||||||
import { eq } from "drizzle-orm";
|
ExecutionContext,
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from "@nestjs/common";
|
||||||
import { Reflector } from "@nestjs/core";
|
import { Reflector } from "@nestjs/core";
|
||||||
import { DbService } from 'apps/backend/src/app/db/db.service';
|
import { CredentialsService } from "apps/backend/src/app/credentials/credentials.service";
|
||||||
import { UsersTable } from 'apps/backend/src/app/db/schema';
|
import { DbService } from "apps/backend/src/app/db/db.service";
|
||||||
import { CredentialsService } from 'apps/backend/src/app/credentials/credentials.service';
|
import { UsersTable } from "apps/backend/src/app/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import type { Request } from "express";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserGuard implements CanActivate {
|
export class UserGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
@Inject(CredentialsService)
|
||||||
|
private readonly credentialService: CredentialsService,
|
||||||
|
@Inject(DbService) private readonly databaseService: DbService,
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
@Inject(CredentialsService) private readonly credentialService: CredentialsService,
|
const request: Request = context.switchToHttp().getRequest();
|
||||||
@Inject(DbService) private readonly databaseService: DbService,
|
const authHeader = request.headers.authorization;
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async canActivate(
|
if (!authHeader)
|
||||||
context: ExecutionContext
|
throw new UnauthorizedException("No authorization header found.");
|
||||||
): Promise<boolean> {
|
|
||||||
const request: Request = context.switchToHttp().getRequest();
|
|
||||||
const authHeader = request.headers.authorization;
|
|
||||||
|
|
||||||
if (!authHeader)
|
const token = authHeader.split(" ")[1];
|
||||||
throw new UnauthorizedException("No authorization header found.");
|
const vToken = await this.credentialService.verifyAuthToken(token);
|
||||||
|
|
||||||
const token = authHeader.split(" ")[1];
|
const user = await this.databaseService
|
||||||
const vToken = await this.credentialService.verifyAuthToken(token);
|
.use()
|
||||||
|
.select()
|
||||||
|
.from(UsersTable)
|
||||||
|
.where(eq(UsersTable.uuid, vToken.payload.sub));
|
||||||
|
|
||||||
const user = await this.databaseService.use()
|
if (user.length !== 1)
|
||||||
.select()
|
throw new UnauthorizedException("No such user found.");
|
||||||
.from(UsersTable)
|
/*
|
||||||
.where(eq(UsersTable.uuid, vToken.payload.sub));
|
|
||||||
|
|
||||||
if (user.length !== 1)
|
|
||||||
throw new UnauthorizedException("No such user found.");
|
|
||||||
/*
|
|
||||||
if (user[0].emailCode)
|
if (user[0].emailCode)
|
||||||
throw new UnauthorizedException("Email not verified.");
|
throw new UnauthorizedException("Email not verified.");
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Inject user ID into request body
|
// Inject user ID into request body
|
||||||
request.body.sourceUserId = vToken.payload.sub;
|
request.body.sourceUserId = vToken.payload.sub;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminGuard implements CanActivate {
|
export class AdminGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
@Inject(CredentialsService)
|
||||||
|
private readonly credentialService: CredentialsService,
|
||||||
|
@Inject(DbService) private readonly databaseService: DbService,
|
||||||
|
) {}
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request: Request = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
constructor(
|
const authHeader = request.headers.authorization;
|
||||||
@Inject(CredentialsService) private readonly credentialService: CredentialsService,
|
if (!authHeader) {
|
||||||
@Inject(DbService) private readonly databaseService: DbService,
|
throw new UnauthorizedException("No authorization header found.");
|
||||||
) {}
|
}
|
||||||
async canActivate(
|
const token = authHeader.split(" ")[1];
|
||||||
context: ExecutionContext
|
const vToken = await this.credentialService.verifyAuthToken(token);
|
||||||
): Promise<boolean> {
|
|
||||||
const request: Request = context.switchToHttp().getRequest();
|
|
||||||
|
|
||||||
const authHeader = request.headers.authorization;
|
const user = await this.databaseService
|
||||||
if (!authHeader) {
|
.use()
|
||||||
throw new UnauthorizedException("No authorization header found.");
|
.select()
|
||||||
|
.from(UsersTable)
|
||||||
|
.where(eq(UsersTable.uuid, vToken.payload.sub));
|
||||||
|
|
||||||
}
|
if (user.length !== 1)
|
||||||
const token = authHeader.split(" ")[1];
|
throw new UnauthorizedException("No such user found.");
|
||||||
const vToken = await this.credentialService.verifyAuthToken(token);
|
|
||||||
|
|
||||||
const user = await this.databaseService.use()
|
if (!user[0].isAdmin) {
|
||||||
.select()
|
throw new UnauthorizedException("Administrator only..");
|
||||||
.from(UsersTable)
|
}
|
||||||
.where(eq(UsersTable.uuid, vToken.payload.sub));
|
|
||||||
|
|
||||||
if (user.length !== 1)
|
// Inject user ID into request body
|
||||||
throw new UnauthorizedException("No such user found.");
|
request.body.sourceUserId = vToken.payload.sub;
|
||||||
|
|
||||||
if (!user[0].isAdmin) {
|
return true;
|
||||||
throw new UnauthorizedException("Administrator only..");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Inject user ID into request body
|
|
||||||
request.body.sourceUserId = vToken.payload.sub;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
import { AdminGuard, UserGuard } from "apps/backend/src/app/auth/auth.guard";
|
||||||
|
import { CredentialsModule } from "apps/backend/src/app/credentials/credentials.module";
|
||||||
|
import { CredentialsService } from "apps/backend/src/app/credentials/credentials.service";
|
||||||
|
import { DbModule } from "apps/backend/src/app/db/db.module";
|
||||||
import { AuthController } from "./auth.controller";
|
import { AuthController } from "./auth.controller";
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
import { DbModule } from 'apps/backend/src/app/db/db.module';
|
|
||||||
import { CredentialsModule } from 'apps/backend/src/app/credentials/credentials.module';
|
|
||||||
import { CredentialsService } from 'apps/backend/src/app/credentials/credentials.service';
|
|
||||||
import { AdminGuard, UserGuard } from 'apps/backend/src/app/auth/auth.guard';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DbModule, CredentialsModule],
|
imports: [DbModule, CredentialsModule],
|
||||||
providers: [AuthService, CredentialsService, AdminGuard, UserGuard],
|
providers: [AuthService, CredentialsService, AdminGuard, UserGuard],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
@ -1,113 +1,113 @@
|
|||||||
import {
|
import {
|
||||||
Injectable,
|
Injectable,
|
||||||
OnModuleInit,
|
OnModuleInit,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto";
|
||||||
|
import { CredentialsService } from "apps/backend/src/app/credentials/credentials.service";
|
||||||
|
import { DbService } from "apps/backend/src/app/db/db.service";
|
||||||
|
import { UsersTable } from "apps/backend/src/app/db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { DbService } from 'apps/backend/src/app/db/db.service';
|
|
||||||
import { CredentialsService } from 'apps/backend/src/app/credentials/credentials.service';
|
|
||||||
import { UsersTable } from 'apps/backend/src/app/db/schema';
|
|
||||||
import { SignInDto, SignUpDto } from 'apps/backend/src/app/auth/auth.dto';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService implements OnModuleInit {
|
export class AuthService implements OnModuleInit {
|
||||||
constructor(
|
constructor(
|
||||||
private db: DbService,
|
private db: DbService,
|
||||||
private credentials: CredentialsService,
|
private credentials: CredentialsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
//TODO Initial account validation for admin privileges
|
//TODO Initial account validation for admin privileges
|
||||||
async doRegister(data: SignUpDto) {
|
async doRegister(data: SignUpDto) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
const existingUser = await this.db
|
const existingUser = await this.db
|
||||||
.use()
|
.use()
|
||||||
.select()
|
.select()
|
||||||
.from(UsersTable)
|
.from(UsersTable)
|
||||||
.where(eq(UsersTable.email, data.email))
|
.where(eq(UsersTable.email, data.email))
|
||||||
.prepare("userByEmail")
|
.prepare("userByEmail")
|
||||||
.execute();
|
.execute();
|
||||||
if (existingUser.length !== 0)
|
if (existingUser.length !== 0)
|
||||||
throw new UnauthorizedException("Already exist");
|
throw new UnauthorizedException("Already exist");
|
||||||
const query = await this.db
|
const query = await this.db
|
||||||
.use()
|
.use()
|
||||||
.insert(UsersTable)
|
.insert(UsersTable)
|
||||||
.values({
|
.values({
|
||||||
//firstName: data.firstName,
|
//firstName: data.firstName,
|
||||||
//lastName: data.lastName,
|
//lastName: data.lastName,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
hash: await this.credentials.hash(data.password),
|
hash: await this.credentials.hash(data.password),
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
.prepare("insertUser")
|
.prepare("insertUser")
|
||||||
.execute()
|
.execute()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
throw new UnauthorizedException(
|
throw new UnauthorizedException(
|
||||||
"Error occurred while inserting user",
|
"Error occurred while inserting user",
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
message: "User created, check your email for validation.",
|
message: "User created, check your email for validation.",
|
||||||
token: await this.credentials.signAuthToken({ sub: query[0].uuid }),
|
token: await this.credentials.signAuthToken({ sub: query[0].uuid }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async doLogin(data: SignInDto) {
|
async doLogin(data: SignInDto) {
|
||||||
const user = await this.db
|
const user = await this.db
|
||||||
.use()
|
.use()
|
||||||
.select()
|
.select()
|
||||||
.from(UsersTable)
|
.from(UsersTable)
|
||||||
.where(eq(UsersTable.email, data.email))
|
.where(eq(UsersTable.email, data.email))
|
||||||
.prepare("userByEmail")
|
.prepare("userByEmail")
|
||||||
.execute();
|
.execute();
|
||||||
if (user.length !== 1)
|
if (user.length !== 1)
|
||||||
throw new UnauthorizedException("Invalid credentials");
|
throw new UnauthorizedException("Invalid credentials");
|
||||||
const passwordMatch = await this.credentials.check(
|
const passwordMatch = await this.credentials.check(
|
||||||
data.password,
|
data.password,
|
||||||
user[0].hash,
|
user[0].hash,
|
||||||
);
|
);
|
||||||
if (!passwordMatch) throw new UnauthorizedException("Invalid credentials");
|
if (!passwordMatch) throw new UnauthorizedException("Invalid credentials");
|
||||||
const token = await this.credentials.signAuthToken({ sub: user[0].uuid });
|
const token = await this.credentials.signAuthToken({ sub: user[0].uuid });
|
||||||
return {
|
return {
|
||||||
message: "Login successful",
|
message: "Login successful",
|
||||||
token: token,
|
token: token,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchUserById(userId: string) {
|
async fetchUserById(userId: string) {
|
||||||
const user = await this.db
|
const user = await this.db
|
||||||
.use()
|
.use()
|
||||||
.select()
|
.select()
|
||||||
.from(UsersTable)
|
.from(UsersTable)
|
||||||
.where(eq(UsersTable.uuid, userId))
|
.where(eq(UsersTable.uuid, userId))
|
||||||
.prepare("userById")
|
.prepare("userById")
|
||||||
.execute();
|
.execute();
|
||||||
if (user.length !== 1) {
|
if (user.length !== 1) {
|
||||||
throw new UnauthorizedException("User not found");
|
throw new UnauthorizedException("User not found");
|
||||||
}
|
}
|
||||||
delete user[0].hash;
|
delete user[0].hash;
|
||||||
//delete user[0].emailCode;
|
//delete user[0].emailCode;
|
||||||
return user[0];
|
return user[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchUsers() {
|
async fetchUsers() {
|
||||||
//TODO Pagination
|
//TODO Pagination
|
||||||
const usersInDb = await this.db.use().select().from(UsersTable);
|
const usersInDb = await this.db.use().select().from(UsersTable);
|
||||||
const result = {
|
const result = {
|
||||||
total: usersInDb.length,
|
total: usersInDb.length,
|
||||||
users: usersInDb.map((user) => {
|
users: usersInDb.map((user) => {
|
||||||
delete user.hash;
|
delete user.hash;
|
||||||
return {
|
return {
|
||||||
...user,
|
...user,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
console.log(result);
|
console.log(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
async updateUser(targetId: string, userData: IUserUpdateData) {
|
async updateUser(targetId: string, userData: IUserUpdateData) {
|
||||||
const validationResult = UserUpdateSchema.safeParse(userData);
|
const validationResult = UserUpdateSchema.safeParse(userData);
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
@ -133,27 +133,26 @@ export class AuthService implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async deleteUser(targetId: string) {
|
async deleteUser(targetId: string) {
|
||||||
await this.db
|
await this.db
|
||||||
.use()
|
.use()
|
||||||
.delete(UsersTable)
|
.delete(UsersTable)
|
||||||
.where(eq(UsersTable.uuid, targetId))
|
.where(eq(UsersTable.uuid, targetId))
|
||||||
.prepare("deleteUserById")
|
.prepare("deleteUserById")
|
||||||
.execute()
|
.execute()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
throw new UnauthorizedException(
|
throw new UnauthorizedException(
|
||||||
"Error occurred while deleting user",
|
"Error occurred while deleting user",
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fetchUsers();
|
this.fetchUsers();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { AuthorsController } from 'apps/backend/src/app/authors/authors.controller';
|
import { AuthorsController } from "apps/backend/src/app/authors/authors.controller";
|
||||||
import { AuthorsService } from 'apps/backend/src/app/authors/authors.service';
|
import { AuthorsService } from "apps/backend/src/app/authors/authors.service";
|
||||||
|
|
||||||
describe('AuthorsController', () => {
|
describe("AuthorsController", () => {
|
||||||
let controller: AuthorsController;
|
let controller: AuthorsController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [AuthorsController],
|
controllers: [AuthorsController],
|
||||||
providers: [AuthorsService],
|
providers: [AuthorsService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<AuthorsController>(AuthorsController);
|
controller = module.get<AuthorsController>(AuthorsController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,41 +1,44 @@
|
|||||||
import { Controller, DefaultValuePipe, Delete, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
|
import {
|
||||||
import { AuthorsService } from 'apps/backend/src/app/authors/authors.service';
|
Controller,
|
||||||
|
DefaultValuePipe,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
ParseIntPipe,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { AuthorsService } from "apps/backend/src/app/authors/authors.service";
|
||||||
|
|
||||||
@Controller('authors')
|
@Controller("authors")
|
||||||
export class AuthorsController {
|
export class AuthorsController {
|
||||||
constructor(private readonly authorService: AuthorsService) {}
|
constructor(private readonly authorService: AuthorsService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async findMany(
|
async findMany(
|
||||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||||
@Query("search", new DefaultValuePipe("")) search: string
|
@Query("search", new DefaultValuePipe("")) search: string,
|
||||||
) {
|
) {
|
||||||
const query = {limit, offset, search}
|
const query = { limit, offset, search };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
//POST a new group
|
||||||
|
@Post("new")
|
||||||
|
async newAuthor() {}
|
||||||
|
|
||||||
//POST a new group
|
//DELETE a group
|
||||||
@Post("new")
|
@Delete(":authorId")
|
||||||
async newAuthor() {
|
async deleteAuthor(@Param("authorId") authorId: string) {}
|
||||||
|
|
||||||
}
|
//GET files associated to authors with limit and offset
|
||||||
|
@Get(":authorId")
|
||||||
//DELETE a group
|
async getForAuthor(
|
||||||
@Delete(":authorId")
|
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||||
async deleteAuthor(@Param('authorId') authorId: string) {
|
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||||
|
@Query("search", new DefaultValuePipe("")) search: string,
|
||||||
}
|
@Param("authorId") authorId: string,
|
||||||
|
) {
|
||||||
//GET files associated to authors with limit and offset
|
const query = { limit, offset, search };
|
||||||
@Get(":authorId")
|
}
|
||||||
async getForAuthor(
|
|
||||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
|
||||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
|
||||||
@Query("search", new DefaultValuePipe("")) search: string,
|
|
||||||
@Param('authorId') authorId: string
|
|
||||||
) {
|
|
||||||
const query = {limit, offset, search}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from "@nestjs/common";
|
||||||
import { AuthorsService } from 'apps/backend/src/app/authors/authors.service';
|
import { AuthorsController } from "apps/backend/src/app/authors/authors.controller";
|
||||||
import { AuthorsController } from 'apps/backend/src/app/authors/authors.controller';
|
import { AuthorsService } from "apps/backend/src/app/authors/authors.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [AuthorsController],
|
controllers: [AuthorsController],
|
||||||
providers: [AuthorsService],
|
providers: [AuthorsService],
|
||||||
})
|
})
|
||||||
export class AuthorsModule {}
|
export class AuthorsModule {}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { AuthorsService } from 'apps/backend/src/app/authors/authors.service';
|
import { AuthorsService } from "apps/backend/src/app/authors/authors.service";
|
||||||
|
|
||||||
describe('AuthorsService', () => {
|
describe("AuthorsService", () => {
|
||||||
let service: AuthorsService;
|
let service: AuthorsService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [AuthorsService],
|
providers: [AuthorsService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<AuthorsService>(AuthorsService);
|
service = module.get<AuthorsService>(AuthorsService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthorsService {}
|
export class AuthorsService {}
|
||||||
|
@ -3,8 +3,8 @@ import { ConfigModule } from "@nestjs/config";
|
|||||||
import { CredentialsService } from "./credentials.service";
|
import { CredentialsService } from "./credentials.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
providers: [CredentialsService],
|
providers: [CredentialsService],
|
||||||
exports: [CredentialsService],
|
exports: [CredentialsService],
|
||||||
})
|
})
|
||||||
export class CredentialsModule {}
|
export class CredentialsModule {}
|
||||||
|
@ -6,52 +6,52 @@ import { JWTPayload, generateSecret } from "jose";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CredentialsService {
|
export class CredentialsService {
|
||||||
constructor(private readonly configService: ConfigService) {}
|
constructor(private readonly configService: ConfigService) {}
|
||||||
|
|
||||||
async hash(plaintextPassword: string) {
|
async hash(plaintextPassword: string) {
|
||||||
console.log(plaintextPassword);
|
console.log(plaintextPassword);
|
||||||
if (plaintextPassword.length < 6)
|
if (plaintextPassword.length < 6)
|
||||||
throw new BadRequestException("Password is not strong enough !");
|
throw new BadRequestException("Password is not strong enough !");
|
||||||
return argon.hash(plaintextPassword, {
|
return argon.hash(plaintextPassword, {
|
||||||
secret: Buffer.from(this.configService.get("APP_HASH_SECRET")),
|
secret: Buffer.from(this.configService.get("APP_HASH_SECRET")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async check(plaintextPassword: string, hashedPassword: string) {
|
async check(plaintextPassword: string, hashedPassword: string) {
|
||||||
return argon.verify(hashedPassword, plaintextPassword, {
|
return argon.verify(hashedPassword, plaintextPassword, {
|
||||||
secret: Buffer.from(this.configService.get("APP_HASH_SECRET")),
|
secret: Buffer.from(this.configService.get("APP_HASH_SECRET")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyAuthToken(token: string) {
|
async verifyAuthToken(token: string) {
|
||||||
try {
|
try {
|
||||||
const result = await jose.jwtVerify(
|
const result = await jose.jwtVerify(
|
||||||
token,
|
token,
|
||||||
Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")),
|
Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")),
|
||||||
{
|
{
|
||||||
audience: "auth:user",
|
audience: "auth:user",
|
||||||
issuer: "FabLab",
|
issuer: "FabLab",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log(result);
|
console.log(result);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
throw new BadRequestException("Invalid token");
|
throw new BadRequestException("Invalid token");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signAuthToken(payload: JWTPayload) {
|
async signAuthToken(payload: JWTPayload) {
|
||||||
console.log(this.configService.get("APP_TOKEN_SECRET"));
|
console.log(this.configService.get("APP_TOKEN_SECRET"));
|
||||||
const token = new jose.SignJWT(payload)
|
const token = new jose.SignJWT(payload)
|
||||||
.setProtectedHeader({ alg: "HS512", enc: "A128CBC-HS512" })
|
.setProtectedHeader({ alg: "HS512", enc: "A128CBC-HS512" })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
.setExpirationTime("5 day")
|
.setExpirationTime("5 day")
|
||||||
.setIssuer("FabLab")
|
.setIssuer("FabLab")
|
||||||
.setAudience("auth:user");
|
.setAudience("auth:user");
|
||||||
console.log(token);
|
console.log(token);
|
||||||
return await token.sign(
|
return await token.sign(
|
||||||
Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")),
|
Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,9 +61,11 @@ export const FilesTable = pgTable("files", {
|
|||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
|
|
||||||
checksum: p.varchar("checksum", {
|
checksum: p
|
||||||
length: 64
|
.varchar("checksum", {
|
||||||
}).notNull(),
|
length: 64,
|
||||||
|
})
|
||||||
|
.notNull(),
|
||||||
|
|
||||||
uploader: p
|
uploader: p
|
||||||
.varchar("uploader", {
|
.varchar("uploader", {
|
||||||
@ -71,9 +73,7 @@ export const FilesTable = pgTable("files", {
|
|||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
|
|
||||||
groupId: p
|
groupId: p.uuid("group_id").references(() => FilesGroupTable.uuid),
|
||||||
.uuid("group_id")
|
|
||||||
.references(()=> FilesGroupTable.uuid),
|
|
||||||
|
|
||||||
fileSize: p.integer("file_size").notNull(),
|
fileSize: p.integer("file_size").notNull(),
|
||||||
|
|
||||||
@ -101,15 +101,15 @@ export const FilesTable = pgTable("files", {
|
|||||||
|
|
||||||
export const FilesGroupTable = pgTable("f_groups", {
|
export const FilesGroupTable = pgTable("f_groups", {
|
||||||
// Unique identifier on a technical aspect.
|
// Unique identifier on a technical aspect.
|
||||||
uuid: p.uuid("uuid").unique().primaryKey().defaultRandom().notNull(),
|
uuid: p.uuid("uuid").unique().primaryKey().defaultRandom().notNull(),
|
||||||
|
|
||||||
groupName: p
|
groupName: p
|
||||||
.varchar("group_name", {
|
.varchar("group_name", {
|
||||||
length: 64,
|
length: 64,
|
||||||
})
|
})
|
||||||
.unique()
|
.unique()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
})
|
});
|
||||||
|
|
||||||
//TODO Files types
|
//TODO Files types
|
||||||
export const FilesTypesTable = pgTable("f_types", {
|
export const FilesTypesTable = pgTable("f_types", {
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { FilesController } from './files.controller';
|
import { FilesController } from "./files.controller";
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from "./files.service";
|
||||||
|
|
||||||
describe('FilesController', () => {
|
describe("FilesController", () => {
|
||||||
let controller: FilesController;
|
let controller: FilesController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [FilesController],
|
controllers: [FilesController],
|
||||||
providers: [FilesService],
|
providers: [FilesService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<FilesController>(FilesController);
|
controller = module.get<FilesController>(FilesController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Controller } from '@nestjs/common';
|
import { Controller } from "@nestjs/common";
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from "./files.service";
|
||||||
|
|
||||||
@Controller('files')
|
@Controller("files")
|
||||||
export class FilesController {
|
export class FilesController {
|
||||||
constructor(private readonly filesService: FilesService) {}
|
constructor(private readonly filesService: FilesService) {}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from "@nestjs/common";
|
||||||
import { FilesService } from './files.service';
|
import { FilesController } from "./files.controller";
|
||||||
import { FilesController } from './files.controller';
|
import { FilesService } from "./files.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [FilesController],
|
controllers: [FilesController],
|
||||||
providers: [FilesService],
|
providers: [FilesService],
|
||||||
})
|
})
|
||||||
export class FilesModule {}
|
export class FilesModule {}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from "./files.service";
|
||||||
|
|
||||||
describe('FilesService', () => {
|
describe("FilesService", () => {
|
||||||
let service: FilesService;
|
let service: FilesService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [FilesService],
|
providers: [FilesService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<FilesService>(FilesService);
|
service = module.get<FilesService>(FilesService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilesService {}
|
export class FilesService {}
|
||||||
|
0
apps/backend/src/app/global.dto.ts
Normal file
0
apps/backend/src/app/global.dto.ts
Normal file
@ -1,20 +1,20 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { GroupsController } from './groups.controller';
|
import { GroupsController } from "./groups.controller";
|
||||||
import { GroupsService } from './groups.service';
|
import { GroupsService } from "./groups.service";
|
||||||
|
|
||||||
describe('GroupsController', () => {
|
describe("GroupsController", () => {
|
||||||
let controller: GroupsController;
|
let controller: GroupsController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [GroupsController],
|
controllers: [GroupsController],
|
||||||
providers: [GroupsService],
|
providers: [GroupsService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<GroupsController>(GroupsController);
|
controller = module.get<GroupsController>(GroupsController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,43 +1,46 @@
|
|||||||
import { Controller, DefaultValuePipe, Delete, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
|
import {
|
||||||
import { GroupsService } from './groups.service';
|
Controller,
|
||||||
import { ISearchQuery } from 'apps/backend/src/app/groups/groups.types';
|
DefaultValuePipe,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
ParseIntPipe,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { ISearchQuery } from "apps/backend/src/app/groups/groups.types";
|
||||||
|
import { GroupsService } from "./groups.service";
|
||||||
|
|
||||||
@Controller('groups')
|
@Controller("groups")
|
||||||
export class GroupsController {
|
export class GroupsController {
|
||||||
constructor(private readonly groupsService: GroupsService) {}
|
constructor(private readonly groupsService: GroupsService) {}
|
||||||
|
|
||||||
//GET all groups with limit and offset + optional search
|
//GET all groups with limit and offset + optional search
|
||||||
@Get()
|
@Get()
|
||||||
async findMany(
|
async findMany(
|
||||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||||
@Query("search", new DefaultValuePipe("")) search: string
|
@Query("search", new DefaultValuePipe("")) search: string,
|
||||||
) {
|
) {
|
||||||
const query = {limit, offset, search}
|
const query = { limit, offset, search };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
//POST a new group
|
||||||
|
@Post("new")
|
||||||
|
async newGroup() {}
|
||||||
|
|
||||||
//POST a new group
|
//DELETE a group
|
||||||
@Post("new")
|
@Delete(":groupId")
|
||||||
async newGroup() {
|
async deleteGroup(@Param("groupId") groupId: string) {}
|
||||||
|
|
||||||
}
|
//GET files associated to group with limit and offset
|
||||||
|
@Get(":groupId")
|
||||||
//DELETE a group
|
async getForGroup(
|
||||||
@Delete(":groupId")
|
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||||
async deleteGroup(@Param('groupId') groupId: string) {
|
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||||
|
@Query("search", new DefaultValuePipe("")) search: string,
|
||||||
}
|
@Param("groupId") groupId: string,
|
||||||
|
) {
|
||||||
//GET files associated to group with limit and offset
|
const query = { limit, offset, search };
|
||||||
@Get(":groupId")
|
}
|
||||||
async getForGroup(
|
|
||||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
|
||||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
|
||||||
@Query("search", new DefaultValuePipe("")) search: string,
|
|
||||||
@Param('groupId') groupId: string
|
|
||||||
) {
|
|
||||||
const query = {limit, offset, search}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from "@nestjs/common";
|
||||||
import { GroupsService } from './groups.service';
|
import { GroupsController } from "./groups.controller";
|
||||||
import { GroupsController } from './groups.controller';
|
import { GroupsService } from "./groups.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [GroupsController],
|
controllers: [GroupsController],
|
||||||
providers: [GroupsService],
|
providers: [GroupsService],
|
||||||
})
|
})
|
||||||
export class GroupsModule {}
|
export class GroupsModule {}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { GroupsService } from './groups.service';
|
import { GroupsService } from "./groups.service";
|
||||||
|
|
||||||
describe('GroupsService', () => {
|
describe("GroupsService", () => {
|
||||||
let service: GroupsService;
|
let service: GroupsService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [GroupsService],
|
providers: [GroupsService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<GroupsService>(GroupsService);
|
service = module.get<GroupsService>(GroupsService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GroupsService {}
|
export class GroupsService {}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export interface ISearchQuery {
|
export interface ISearchQuery {
|
||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
search?: string;
|
search?: string;
|
||||||
}
|
}
|
@ -1,20 +1,20 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { MachinesController } from 'apps/backend/src/app/machines/machines.controller';
|
import { MachinesController } from "apps/backend/src/app/machines/machines.controller";
|
||||||
import { MachinesService } from 'apps/backend/src/app/machines/machines.service';
|
import { MachinesService } from "apps/backend/src/app/machines/machines.service";
|
||||||
|
|
||||||
describe('MachineController', () => {
|
describe("MachineController", () => {
|
||||||
let controller: MachinesController;
|
let controller: MachinesController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [MachinesController],
|
controllers: [MachinesController],
|
||||||
providers: [MachinesService],
|
providers: [MachinesService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<MachinesController>(MachinesController);
|
controller = module.get<MachinesController>(MachinesController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,38 +1,41 @@
|
|||||||
import { Controller, DefaultValuePipe, Delete, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
|
import {
|
||||||
import { MachinesService } from 'apps/backend/src/app/machines/machines.service';
|
Controller,
|
||||||
|
DefaultValuePipe,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
ParseIntPipe,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { MachinesService } from "apps/backend/src/app/machines/machines.service";
|
||||||
|
|
||||||
@Controller('machines')
|
@Controller("machines")
|
||||||
export class MachinesController {
|
export class MachinesController {
|
||||||
constructor(private readonly machineService: MachinesService) {}
|
constructor(private readonly machineService: MachinesService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async findMany(
|
async findMany(
|
||||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||||
@Query("search", new DefaultValuePipe("")) search: string
|
@Query("search", new DefaultValuePipe("")) search: string,
|
||||||
) {
|
) {
|
||||||
const query = {limit, offset, search}
|
const query = { limit, offset, search };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
@Post("new")
|
||||||
|
async newMachine() {}
|
||||||
|
|
||||||
@Post("new")
|
@Delete(":machineId")
|
||||||
async newMachine() {
|
async deleteGroup(@Param("machineId") machineId: string) {}
|
||||||
|
|
||||||
}
|
@Get(":groupId")
|
||||||
|
async getForGroup(
|
||||||
@Delete(":machineId")
|
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
||||||
async deleteGroup(@Param('machineId') machineId: string) {
|
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||||
|
@Query("search", new DefaultValuePipe("")) search: string,
|
||||||
}
|
@Param("machineId") machineId: string,
|
||||||
|
) {
|
||||||
@Get(":groupId")
|
const query = { limit, offset, search };
|
||||||
async getForGroup(
|
}
|
||||||
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
|
|
||||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
|
||||||
@Query("search", new DefaultValuePipe("")) search: string,
|
|
||||||
@Param('machineId') machineId: string
|
|
||||||
) {
|
|
||||||
const query = {limit, offset, search}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from "@nestjs/common";
|
||||||
import { MachinesService } from 'apps/backend/src/app/machines/machines.service';
|
import { MachinesController } from "apps/backend/src/app/machines/machines.controller";
|
||||||
import { MachinesController } from 'apps/backend/src/app/machines/machines.controller';
|
import { MachinesService } from "apps/backend/src/app/machines/machines.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [MachinesController],
|
controllers: [MachinesController],
|
||||||
providers: [MachinesService],
|
providers: [MachinesService],
|
||||||
})
|
})
|
||||||
export class MachinesModule {}
|
export class MachinesModule {}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { MachinesService } from 'apps/backend/src/app/machines/machines.service';
|
import { MachinesService } from "apps/backend/src/app/machines/machines.service";
|
||||||
|
|
||||||
describe('MachinesService', () => {
|
describe("MachinesService", () => {
|
||||||
let service: MachinesService;
|
let service: MachinesService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [MachinesService],
|
providers: [MachinesService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<MachinesService>(MachinesService);
|
service = module.get<MachinesService>(MachinesService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it("should be defined", () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MachinesService {}
|
export class MachinesService {}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
import { DbModule } from "apps/backend/src/app/db/db.module";
|
||||||
import { StorageService } from "apps/backend/src/app/storage/storage.service";
|
import { StorageService } from "apps/backend/src/app/storage/storage.service";
|
||||||
import { DbModule } from 'apps/backend/src/app/db/db.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DbModule],
|
imports: [DbModule],
|
||||||
|
@ -8,11 +8,14 @@ import {
|
|||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import { DbService } from "apps/backend/src/app/db/db.service";
|
||||||
|
import {
|
||||||
|
FilesTypeForMachine,
|
||||||
|
FilesTypesTable,
|
||||||
|
} from "apps/backend/src/app/db/schema";
|
||||||
|
import { IFileInformation } from "apps/backend/src/app/storage/storage.types";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
import FileType from "file-type";
|
import FileType from "file-type";
|
||||||
import { DbService } from 'apps/backend/src/app/db/db.service';
|
|
||||||
import { IFileInformation } from 'apps/backend/src/app/storage/storage.types';
|
|
||||||
import { FilesTypeForMachine, FilesTypesTable } from 'apps/backend/src/app/db/schema';
|
|
||||||
import { eq } from 'drizzle-orm';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StorageService {
|
export class StorageService {
|
||||||
@ -20,7 +23,6 @@ export class StorageService {
|
|||||||
private maxFileSize = 256; // MiB unit
|
private maxFileSize = 256; // MiB unit
|
||||||
constructor(private readonly dbService: DbService) {}
|
constructor(private readonly dbService: DbService) {}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a file to the specified directory.
|
* Save a file to the specified directory.
|
||||||
*
|
*
|
||||||
@ -44,8 +46,10 @@ export class StorageService {
|
|||||||
* @param {Buffer} file - The file to check.
|
* @param {Buffer} file - The file to check.
|
||||||
* @return {Promise<boolean>} - A Promise that resolves to true if the conditions are met, false otherwise.
|
* @return {Promise<boolean>} - A Promise that resolves to true if the conditions are met, false otherwise.
|
||||||
*/
|
*/
|
||||||
private async checkConditions(machineIds: Array<string>,file: Buffer): Promise<boolean> {
|
private async checkConditions(
|
||||||
|
machineIds: Array<string>,
|
||||||
|
file: Buffer,
|
||||||
|
): Promise<boolean> {
|
||||||
/**
|
/**
|
||||||
* Checks if the current MIME type is allowed based on the given set of allowed MIME types.
|
* Checks if the current MIME type is allowed based on the given set of allowed MIME types.
|
||||||
* @param {Set<string>} allowedMime - The set of allowed MIME types.
|
* @param {Set<string>} allowedMime - The set of allowed MIME types.
|
||||||
@ -60,53 +64,55 @@ export class StorageService {
|
|||||||
const fileType = await FileType.fileTypeFromBuffer(file);
|
const fileType = await FileType.fileTypeFromBuffer(file);
|
||||||
|
|
||||||
// Array of MIMEs with possible duplicate field
|
// Array of MIMEs with possible duplicate field
|
||||||
const _mimes: Array<string> = []
|
const _mimes: Array<string> = [];
|
||||||
|
|
||||||
// Fetching MIMEs for the associated machines
|
// Fetching MIMEs for the associated machines
|
||||||
for (const machineId of machineIds) {
|
for (const machineId of machineIds) {
|
||||||
console.debug(`Fetching mimeTypes for machine : ${machineId}`)
|
console.debug(`Fetching mimeTypes for machine : ${machineId}`);
|
||||||
// Get MIMEs associated to a machine
|
// Get MIMEs associated to a machine
|
||||||
const allowedMimeId = this.dbService.use()
|
const allowedMimeId = this.dbService
|
||||||
|
.use()
|
||||||
.select()
|
.select()
|
||||||
.from(FilesTypeForMachine)
|
.from(FilesTypeForMachine)
|
||||||
.where(eq(FilesTypeForMachine.machineId, machineId)).as("allowedMimeId");
|
.where(eq(FilesTypeForMachine.machineId, machineId))
|
||||||
const _allowedMime = await this.dbService.use()
|
.as("allowedMimeId");
|
||||||
|
const _allowedMime = await this.dbService
|
||||||
|
.use()
|
||||||
.select({
|
.select({
|
||||||
slug: FilesTypesTable.mime,
|
slug: FilesTypesTable.mime,
|
||||||
name: FilesTypesTable.typeName
|
name: FilesTypesTable.typeName,
|
||||||
})
|
})
|
||||||
.from(FilesTypesTable)
|
.from(FilesTypesTable)
|
||||||
.leftJoin(allowedMimeId, eq(FilesTypesTable.id, allowedMimeId.fileTypeId))
|
.leftJoin(
|
||||||
console.debug(`Total : ${_allowedMime.length}`)
|
allowedMimeId,
|
||||||
|
eq(FilesTypesTable.id, allowedMimeId.fileTypeId),
|
||||||
|
);
|
||||||
|
console.debug(`Total : ${_allowedMime.length}`);
|
||||||
// Append each MIME of a machine
|
// Append each MIME of a machine
|
||||||
for (const allowedMimeElement of _allowedMime) {
|
for (const allowedMimeElement of _allowedMime) {
|
||||||
_mimes.push(allowedMimeElement.slug)
|
_mimes.push(allowedMimeElement.slug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Store the MIMEs without duplicate
|
//Store the MIMEs without duplicate
|
||||||
const mimeSet = new Set(_mimes)
|
const mimeSet = new Set(_mimes);
|
||||||
console.debug(`Indexed ${mimeSet.size} unique mimeTypes`)
|
console.debug(`Indexed ${mimeSet.size} unique mimeTypes`);
|
||||||
|
|
||||||
//check file size is less than 2mb
|
//check file size is less than 2mb
|
||||||
const fileSize = file.byteLength;
|
const fileSize = file.byteLength;
|
||||||
if (fileSize > this.maxFileSize * (1024 * 1024)) {
|
if (fileSize > this.maxFileSize * (1024 * 1024)) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException("File size to high.", {
|
||||||
"File size to high.",
|
cause: "File size",
|
||||||
{
|
description: `File size exceeds the limit. Maximum file size allowed is ${this.maxFileSize}MiB.`,
|
||||||
cause: "File size",
|
});
|
||||||
description: `File size exceeds the limit. Maximum file size allowed is ${this.maxFileSize}MiB.`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkMime(mimeSet, fileType.mime)) throw new BadRequestException(
|
if (!checkMime(mimeSet, fileType.mime))
|
||||||
{
|
throw new BadRequestException({
|
||||||
cause: "MIME type",
|
cause: "MIME type",
|
||||||
description: `Invalid MIME type. Allowed MIME types are: ${[...mimeSet].join(", ")}.`
|
description: `Invalid MIME type. Allowed MIME types are: ${[...mimeSet].join(", ")}.`,
|
||||||
}
|
});
|
||||||
)
|
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,12 +139,16 @@ export class StorageService {
|
|||||||
public async getChecksum(file: Buffer): Promise<string> {
|
public async getChecksum(file: Buffer): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
try {
|
try {
|
||||||
const checksum = crypto.createHash("sha256").update(file).digest("hex").toLowerCase();
|
const checksum = crypto
|
||||||
resolve(checksum)
|
.createHash("sha256")
|
||||||
|
.update(file)
|
||||||
|
.digest("hex")
|
||||||
|
.toLowerCase();
|
||||||
|
resolve(checksum);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new InternalServerErrorException(err)
|
throw new InternalServerErrorException(err);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,9 +159,13 @@ export class StorageService {
|
|||||||
* @param {boolean} [isDocumentation] - Optional flag to indicate if the file is a documentation file.
|
* @param {boolean} [isDocumentation] - Optional flag to indicate if the file is a documentation file.
|
||||||
* @returns {Promise<IFileInformation>} - A Promise that resolves to the generated file information.
|
* @returns {Promise<IFileInformation>} - A Promise that resolves to the generated file information.
|
||||||
*/
|
*/
|
||||||
public async generateInformation(file: Buffer, fileDisplayName: string, isDocumentation?: boolean): Promise<IFileInformation> {
|
public async generateInformation(
|
||||||
|
file: Buffer,
|
||||||
|
fileDisplayName: string,
|
||||||
|
isDocumentation?: boolean,
|
||||||
|
): Promise<IFileInformation> {
|
||||||
const fileType = await FileType.fileTypeFromBuffer(file);
|
const fileType = await FileType.fileTypeFromBuffer(file);
|
||||||
const checksum = await this.getChecksum(file)
|
const checksum = await this.getChecksum(file);
|
||||||
const fileName = `${isDocumentation ? "doc" : "file"}-${checksum}.${fileType.ext.toLowerCase()}`;
|
const fileName = `${isDocumentation ? "doc" : "file"}-${checksum}.${fileType.ext.toLowerCase()}`;
|
||||||
return {
|
return {
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
@ -159,24 +173,39 @@ export class StorageService {
|
|||||||
fileSize: file.byteLength,
|
fileSize: file.byteLength,
|
||||||
fileChecksum: checksum,
|
fileChecksum: checksum,
|
||||||
fileType: fileType,
|
fileType: fileType,
|
||||||
isDocumentation: isDocumentation || false
|
isDocumentation: isDocumentation || false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async new(fileDisplayName: string, file: Buffer, isDocumentation?: boolean) {
|
public async new(
|
||||||
|
fileDisplayName: string,
|
||||||
|
file: Buffer,
|
||||||
|
isDocumentation?: boolean,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const info = await this.generateInformation(file, fileDisplayName, isDocumentation);
|
const info = await this.generateInformation(
|
||||||
console.log(`Trying to append a new file : "${info.fileDisplayName}"...\n > Checksum SHA-256 : ${info.fileChecksum}\n > Size : ${info.fileSize / (1024*1024)}Mio\n > File format : ${info.fileType.mime}\n`)
|
file,
|
||||||
const condition = await this.checkConditions([/* TODO import autorized file format */], file)
|
fileDisplayName,
|
||||||
|
isDocumentation,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Trying to append a new file : "${info.fileDisplayName}"...\n > Checksum SHA-256 : ${info.fileChecksum}\n > Size : ${info.fileSize / (1024 * 1024)}Mio\n > File format : ${info.fileType.mime}\n`,
|
||||||
|
);
|
||||||
|
const condition = await this.checkConditions(
|
||||||
|
[
|
||||||
|
/* TODO import autorized file format */
|
||||||
|
],
|
||||||
|
file,
|
||||||
|
);
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
console.warn(`File "${info.fileDisplayName}" did not pass the files requirement.\n${info.fileChecksum}`)
|
console.warn(
|
||||||
|
`File "${info.fileDisplayName}" did not pass the files requirement.\n${info.fileChecksum}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Append in DB and save to storage
|
//TODO Append in DB and save to storage
|
||||||
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new BadRequestException(err)
|
throw new BadRequestException(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import FileType from 'file-type';
|
import FileType from "file-type";
|
||||||
|
|
||||||
|
|
||||||
export interface IFileInformation {
|
export interface IFileInformation {
|
||||||
fileDisplayName: string;
|
fileDisplayName: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
fileChecksum: string;
|
fileChecksum: string;
|
||||||
isDocumentation: boolean;
|
isDocumentation: boolean;
|
||||||
fileType: FileType.FileTypeResult;
|
fileType: FileType.FileTypeResult;
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFileWithInformation<AdditionalData> {
|
export interface IFileWithInformation<AdditionalData> {
|
||||||
buffer: Buffer;
|
buffer: Buffer;
|
||||||
info: IFileInformation;
|
info: IFileInformation;
|
||||||
additionalData?: AdditionalData
|
additionalData?: AdditionalData;
|
||||||
}
|
}
|
@ -1,195 +1,192 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
// Inspired by react-hot-toast library
|
// Inspired by react-hot-toast library
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import type {
|
import type { ToastActionElement, ToastProps } from "./toast";
|
||||||
ToastActionElement,
|
|
||||||
ToastProps,
|
|
||||||
} from "./toast"
|
|
||||||
|
|
||||||
const TOAST_LIMIT = 1
|
const TOAST_LIMIT = 1;
|
||||||
const TOAST_REMOVE_DELAY = 1000000
|
const TOAST_REMOVE_DELAY = 1000000;
|
||||||
|
|
||||||
type ToasterToast = ToastProps & {
|
type ToasterToast = ToastProps & {
|
||||||
id: string
|
id: string;
|
||||||
title?: React.ReactNode
|
title?: React.ReactNode;
|
||||||
description?: React.ReactNode
|
description?: React.ReactNode;
|
||||||
action?: ToastActionElement
|
action?: ToastActionElement;
|
||||||
}
|
};
|
||||||
|
|
||||||
const actionTypes = {
|
const actionTypes = {
|
||||||
ADD_TOAST: "ADD_TOAST",
|
ADD_TOAST: "ADD_TOAST",
|
||||||
UPDATE_TOAST: "UPDATE_TOAST",
|
UPDATE_TOAST: "UPDATE_TOAST",
|
||||||
DISMISS_TOAST: "DISMISS_TOAST",
|
DISMISS_TOAST: "DISMISS_TOAST",
|
||||||
REMOVE_TOAST: "REMOVE_TOAST",
|
REMOVE_TOAST: "REMOVE_TOAST",
|
||||||
} as const
|
} as const;
|
||||||
|
|
||||||
let count = 0
|
let count = 0;
|
||||||
|
|
||||||
function genId() {
|
function genId() {
|
||||||
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
||||||
return count.toString()
|
return count.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionType = typeof actionTypes
|
type ActionType = typeof actionTypes;
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| {
|
| {
|
||||||
type: ActionType["ADD_TOAST"]
|
type: ActionType["ADD_TOAST"];
|
||||||
toast: ToasterToast
|
toast: ToasterToast;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["UPDATE_TOAST"]
|
type: ActionType["UPDATE_TOAST"];
|
||||||
toast: Partial<ToasterToast>
|
toast: Partial<ToasterToast>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["DISMISS_TOAST"]
|
type: ActionType["DISMISS_TOAST"];
|
||||||
toastId?: ToasterToast["id"]
|
toastId?: ToasterToast["id"];
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["REMOVE_TOAST"]
|
type: ActionType["REMOVE_TOAST"];
|
||||||
toastId?: ToasterToast["id"]
|
toastId?: ToasterToast["id"];
|
||||||
}
|
};
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
toasts: ToasterToast[]
|
toasts: ToasterToast[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
const addToRemoveQueue = (toastId: string) => {
|
const addToRemoveQueue = (toastId: string) => {
|
||||||
if (toastTimeouts.has(toastId)) {
|
if (toastTimeouts.has(toastId)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
toastTimeouts.delete(toastId)
|
toastTimeouts.delete(toastId);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "REMOVE_TOAST",
|
type: "REMOVE_TOAST",
|
||||||
toastId: toastId,
|
toastId: toastId,
|
||||||
})
|
});
|
||||||
}, TOAST_REMOVE_DELAY)
|
}, TOAST_REMOVE_DELAY);
|
||||||
|
|
||||||
toastTimeouts.set(toastId, timeout)
|
toastTimeouts.set(toastId, timeout);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const reducer = (state: State, action: Action): State => {
|
export const reducer = (state: State, action: Action): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "ADD_TOAST":
|
case "ADD_TOAST":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||||
}
|
};
|
||||||
|
|
||||||
case "UPDATE_TOAST":
|
case "UPDATE_TOAST":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toasts: state.toasts.map((t) =>
|
toasts: state.toasts.map((t) =>
|
||||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
||||||
),
|
),
|
||||||
}
|
};
|
||||||
|
|
||||||
case "DISMISS_TOAST": {
|
case "DISMISS_TOAST": {
|
||||||
const { toastId } = action
|
const { toastId } = action;
|
||||||
|
|
||||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||||
// but I'll keep it here for simplicity
|
// but I'll keep it here for simplicity
|
||||||
if (toastId) {
|
if (toastId) {
|
||||||
addToRemoveQueue(toastId)
|
addToRemoveQueue(toastId);
|
||||||
} else {
|
} else {
|
||||||
for (const toast1 of state.toasts) {
|
for (const toast1 of state.toasts) {
|
||||||
addToRemoveQueue(toast1.id)
|
addToRemoveQueue(toast1.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toasts: state.toasts.map((t) =>
|
toasts: state.toasts.map((t) =>
|
||||||
t.id === toastId || toastId === undefined
|
t.id === toastId || toastId === undefined
|
||||||
? {
|
? {
|
||||||
...t,
|
...t,
|
||||||
open: false,
|
open: false,
|
||||||
}
|
}
|
||||||
: t
|
: t,
|
||||||
),
|
),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
case "REMOVE_TOAST":
|
case "REMOVE_TOAST":
|
||||||
if (action.toastId === undefined) {
|
if (action.toastId === undefined) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toasts: [],
|
toasts: [],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const listeners: Array<(state: State) => void> = []
|
const listeners: Array<(state: State) => void> = [];
|
||||||
|
|
||||||
let memoryState: State = { toasts: [] }
|
let memoryState: State = { toasts: [] };
|
||||||
|
|
||||||
function dispatch(action: Action) {
|
function dispatch(action: Action) {
|
||||||
memoryState = reducer(memoryState, action)
|
memoryState = reducer(memoryState, action);
|
||||||
for (const listener of listeners) {
|
for (const listener of listeners) {
|
||||||
listener(memoryState)
|
listener(memoryState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Toast = Omit<ToasterToast, "id">
|
type Toast = Omit<ToasterToast, "id">;
|
||||||
|
|
||||||
function toast({ ...props }: Toast) {
|
function toast({ ...props }: Toast) {
|
||||||
const id = genId()
|
const id = genId();
|
||||||
|
|
||||||
const update = (props: ToasterToast) =>
|
const update = (props: ToasterToast) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "UPDATE_TOAST",
|
type: "UPDATE_TOAST",
|
||||||
toast: { ...props, id },
|
toast: { ...props, id },
|
||||||
})
|
});
|
||||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "ADD_TOAST",
|
type: "ADD_TOAST",
|
||||||
toast: {
|
toast: {
|
||||||
...props,
|
...props,
|
||||||
id,
|
id,
|
||||||
open: true,
|
open: true,
|
||||||
onOpenChange: (open) => {
|
onOpenChange: (open) => {
|
||||||
if (!open) dismiss()
|
if (!open) dismiss();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
dismiss,
|
dismiss,
|
||||||
update,
|
update,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function useToast() {
|
function useToast() {
|
||||||
const [state, setState] = React.useState<State>(memoryState)
|
const [state, setState] = React.useState<State>(memoryState);
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
listeners.push(setState)
|
listeners.push(setState);
|
||||||
return () => {
|
return () => {
|
||||||
const index = listeners.indexOf(setState)
|
const index = listeners.indexOf(setState);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
listeners.splice(index, 1)
|
listeners.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}, [state])
|
}, [state]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
toast,
|
toast,
|
||||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useToast, toast }
|
export { useToast, toast };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user