mirror of
https://github.com/CaramelFur/Picsur.git
synced 2026-06-23 18:21:22 +02:00
upload works
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src"
|
||||
"sourceRoot": "src",
|
||||
"generateOptions": {
|
||||
"spec": false
|
||||
},
|
||||
"exec": "pog"
|
||||
}
|
||||
|
||||
17
package.json
17
package.json
@@ -6,13 +6,14 @@
|
||||
"license": "GPL-3.0",
|
||||
"repository": "https://github.com/rubikscraft/Imagur-Backend",
|
||||
"author": "Rubikscraft <contact@rubikscraft.nl>",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "nest build",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"start": "nest start --exec \"node --experimental-specifier-resolution=node\"",
|
||||
"start:dev": "nest start --watch --exec \"node --experimental-specifier-resolution=node\"",
|
||||
"start:debug": "nest start --debug --watch --exec \"node --experimental-specifier-resolution=node\"",
|
||||
"start:prod": "node --experimental-specifier-resolution=node dist/main",
|
||||
"devdb:start": "podman-compose -f ./dev/docker-compose.yml up -d",
|
||||
"devdb:stop": "podman-compose -f ./dev/docker-compose.yml down",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
@@ -28,6 +29,9 @@
|
||||
"bcrypt": "^5.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"fastify": "^3.27.2",
|
||||
"fastify-multipart": "^5.3.1",
|
||||
"file-type": "^17.1.1",
|
||||
"passport": "^0.5.2",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
@@ -42,7 +46,7 @@
|
||||
"@nestjs/schematics": "^8.0.4",
|
||||
"@nestjs/testing": "^8.1.1",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/passport-jwt": "^3.0.6",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
@@ -54,11 +58,10 @@
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"prettier": "^2.4.1",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.1.6",
|
||||
"ts-loader": "^9.2.6",
|
||||
"ts-node": "^10.3.0",
|
||||
"tsconfig-paths": "^3.11.0",
|
||||
"typescript": "^4.4.4",
|
||||
"typescript": "^4.7.0-dev.20220221",
|
||||
"webpack": "^5.69.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { UserEntity } from './users/user.entity';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { RootModule } from './root/root.module';
|
||||
import Config from './env';
|
||||
import { ImageEntity } from './images/image.entity';
|
||||
import { SafeImagesModule } from './safeimages/safeimages.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -14,14 +17,14 @@ import Config from './env';
|
||||
username: Config.database.username,
|
||||
password: Config.database.password,
|
||||
database: Config.database.database,
|
||||
|
||||
autoLoadEntities: true,
|
||||
synchronize: true,
|
||||
|
||||
entities: [UserEntity],
|
||||
entities: [UserEntity, ImageEntity],
|
||||
}),
|
||||
AuthModule,
|
||||
UsersModule,
|
||||
RootModule,
|
||||
SafeImagesModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -5,6 +5,9 @@ import {
|
||||
Request,
|
||||
Body,
|
||||
Get,
|
||||
ConflictException,
|
||||
NotFoundException,
|
||||
InternalServerErrorException,
|
||||
} from '@nestjs/common';
|
||||
import { LocalAuthGuard } from './local-auth.guard';
|
||||
import {
|
||||
@@ -15,8 +18,7 @@ import {
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtAuthGuard } from './jwt.guard';
|
||||
import { AdminGuard } from './admin.guard';
|
||||
import { Nothing, AsyncMaybe, Maybe } from 'src/lib/maybe';
|
||||
import { User } from 'src/users/user.dto';
|
||||
import { HasFailed } from 'src/lib/maybe';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
@@ -40,9 +42,7 @@ export class AuthController {
|
||||
register.password,
|
||||
);
|
||||
|
||||
if (user === Nothing) {
|
||||
throw new Error('User already exists');
|
||||
}
|
||||
if (HasFailed(user)) throw new ConflictException('User already exists');
|
||||
|
||||
if (register.isAdmin) {
|
||||
await this.authService.makeAdmin(user);
|
||||
@@ -55,10 +55,7 @@ export class AuthController {
|
||||
@Post('delete')
|
||||
async delete(@Request() req, @Body() deleteData: DeleteRequestDto) {
|
||||
const user = await this.authService.deleteUser(deleteData.username);
|
||||
|
||||
if (user === Nothing) {
|
||||
throw new Error('User does not exist');
|
||||
}
|
||||
if (HasFailed(user)) throw new NotFoundException('User does not exist');
|
||||
|
||||
return this.authService.userEntityToUser(user);
|
||||
}
|
||||
@@ -66,6 +63,11 @@ export class AuthController {
|
||||
@UseGuards(JwtAuthGuard, AdminGuard)
|
||||
@Get('list')
|
||||
async listUsers(@Request() req) {
|
||||
return this.authService.listUsers();
|
||||
const users = this.authService.listUsers();
|
||||
|
||||
if (HasFailed(users))
|
||||
throw new InternalServerErrorException('Could not list users');
|
||||
|
||||
return users;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { AsyncMaybe, Nothing } from 'src/lib/maybe';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'src/lib/maybe';
|
||||
import { User } from 'src/users/user.dto';
|
||||
import { UserEntity } from 'src/users/user.entity';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
@@ -14,29 +14,35 @@ export class AuthService {
|
||||
private jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
async createUser(username: string, password: string): AsyncMaybe<UserEntity> {
|
||||
async createUser(
|
||||
username: string,
|
||||
password: string,
|
||||
): AsyncFailable<UserEntity> {
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
return this.usersService.createUser(username, hashedPassword);
|
||||
}
|
||||
|
||||
async deleteUser(user: string | UserEntity): AsyncMaybe<UserEntity> {
|
||||
async deleteUser(user: string | UserEntity): AsyncFailable<UserEntity> {
|
||||
return this.usersService.removeUser(user);
|
||||
}
|
||||
|
||||
async listUsers(): Promise<User[]> {
|
||||
async listUsers(): AsyncFailable<User[]> {
|
||||
const users = await this.usersService.findAll();
|
||||
if (HasFailed(users)) return users;
|
||||
|
||||
return users.map((user) => this.userEntityToUser(user));
|
||||
}
|
||||
|
||||
async authenticate(
|
||||
username: string,
|
||||
password: string,
|
||||
): AsyncMaybe<UserEntity> {
|
||||
): AsyncFailable<UserEntity> {
|
||||
const user = await this.usersService.findOne(username);
|
||||
|
||||
if (user === Nothing) return Nothing;
|
||||
if (HasFailed(user)) return user;
|
||||
|
||||
if (!(await bcrypt.compare(password, user.password))) return Nothing;
|
||||
if (!(await bcrypt.compare(password, user.password)))
|
||||
return Fail('Wrong password');
|
||||
|
||||
return user;
|
||||
}
|
||||
@@ -52,11 +58,11 @@ export class AuthService {
|
||||
return this.jwtService.signAsync(jwtData);
|
||||
}
|
||||
|
||||
async makeAdmin(user: string | UserEntity): Promise<boolean> {
|
||||
async makeAdmin(user: string | UserEntity): AsyncFailable<true> {
|
||||
return this.usersService.modifyAdmin(user, true);
|
||||
}
|
||||
|
||||
async revokeAdmin(user: string | UserEntity): Promise<boolean> {
|
||||
async revokeAdmin(user: string | UserEntity): AsyncFailable<true> {
|
||||
return this.usersService.modifyAdmin(user, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Strategy } from 'passport-local';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AsyncMaybe, Nothing } from 'src/lib/maybe';
|
||||
import { AsyncFailable, HasFailed } from 'src/lib/maybe';
|
||||
import { UserEntity } from 'src/users/user.entity';
|
||||
|
||||
@Injectable()
|
||||
@@ -11,9 +11,12 @@ export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
super();
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): AsyncMaybe<UserEntity> {
|
||||
async validate(
|
||||
username: string,
|
||||
password: string,
|
||||
): AsyncFailable<UserEntity> {
|
||||
const user = await this.authService.authenticate(username, password);
|
||||
if (user === Nothing) {
|
||||
if (HasFailed(user)) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
|
||||
19
src/images/image.entity.ts
Normal file
19
src/images/image.entity.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { SupportedMime, SupportedMimes } from './mimes.service';
|
||||
|
||||
@Entity()
|
||||
export class ImageEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Index()
|
||||
@Column({ unique: true })
|
||||
hash: string;
|
||||
|
||||
// Binary data
|
||||
@Column({ type: 'bytea', nullable: false })
|
||||
data: Buffer;
|
||||
|
||||
@Column({ enum: SupportedMimes })
|
||||
mime: SupportedMime;
|
||||
}
|
||||
12
src/images/images.module.ts
Normal file
12
src/images/images.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ImageEntity } from './image.entity';
|
||||
import { ImagesService } from './images.service';
|
||||
import { MimesService } from './mimes.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([ImageEntity])],
|
||||
providers: [ImagesService, MimesService],
|
||||
exports: [ImagesService, MimesService],
|
||||
})
|
||||
export class ImagesModule {}
|
||||
56
src/images/images.service.ts
Normal file
56
src/images/images.service.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ImageEntity } from './image.entity';
|
||||
import Crypto from 'crypto';
|
||||
import { AsyncFailable, Fail, HasFailed, HasSuccess } from 'src/lib/maybe';
|
||||
import { SupportedMime } from './mimes.service';
|
||||
|
||||
@Injectable()
|
||||
export class ImagesService {
|
||||
constructor(
|
||||
@InjectRepository(ImageEntity)
|
||||
private imageRepository: Repository<ImageEntity>,
|
||||
) {}
|
||||
|
||||
async create(image: Buffer, type: SupportedMime): AsyncFailable<ImageEntity> {
|
||||
const hash = this.hash(image);
|
||||
const find = await this.findOne(hash);
|
||||
if (HasSuccess(find)) return find;
|
||||
|
||||
const imageEntity = new ImageEntity();
|
||||
imageEntity.data = image;
|
||||
imageEntity.mime = type;
|
||||
imageEntity.hash = hash;
|
||||
try {
|
||||
await this.imageRepository.save(imageEntity);
|
||||
} catch (e) {
|
||||
return Fail(e.message);
|
||||
}
|
||||
|
||||
return imageEntity;
|
||||
}
|
||||
|
||||
async findOne(hash: string): AsyncFailable<ImageEntity> {
|
||||
const found = await this.imageRepository.findOne({ where: { hash } });
|
||||
if (found === undefined) return Fail('Image not found');
|
||||
return found;
|
||||
}
|
||||
|
||||
async delete(hash: string): AsyncFailable<true> {
|
||||
const image = await this.findOne(hash);
|
||||
|
||||
if (HasFailed(image)) return image;
|
||||
|
||||
try {
|
||||
await this.imageRepository.delete(image);
|
||||
} catch (e) {
|
||||
return Fail(e.message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
hash(image: Buffer): string {
|
||||
return Crypto.createHash('sha256').update(image).digest('hex');
|
||||
}
|
||||
}
|
||||
47
src/images/mimes.service.ts
Normal file
47
src/images/mimes.service.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Fail, Failable } from 'src/lib/maybe';
|
||||
|
||||
const tuple = <T extends string[]>(...args: T): T => args;
|
||||
|
||||
// Config
|
||||
|
||||
const SupportedImageMimesTuple = tuple(
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
'image/tiff',
|
||||
'image/bmp',
|
||||
'image/x-icon',
|
||||
);
|
||||
|
||||
const SupportedAnimMimesTuple = tuple('image/apng', 'image/gif');
|
||||
|
||||
// Derivatives
|
||||
|
||||
export const SupportedImageMimes: string[] = SupportedImageMimesTuple;
|
||||
export const SupportedAnimMimes: string[] = SupportedAnimMimesTuple;
|
||||
|
||||
export const SupportedMimes: string[] = [
|
||||
...SupportedImageMimes,
|
||||
...SupportedAnimMimes,
|
||||
];
|
||||
export type SupportedMime = typeof SupportedMimes[number];
|
||||
export type SupportedMimeCategory = 'image' | 'anim';
|
||||
|
||||
export interface FullMime {
|
||||
mime: SupportedMime;
|
||||
type: SupportedMimeCategory;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MimesService {
|
||||
getFullMime(mime: string): Failable<FullMime> {
|
||||
if (SupportedImageMimes.includes(mime)) {
|
||||
return { mime, type: 'image' };
|
||||
}
|
||||
if (SupportedAnimMimes.includes(mime)) {
|
||||
return { mime, type: 'anim' };
|
||||
}
|
||||
return Fail('Unsupported mime type');
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,23 @@
|
||||
export const Nothing = undefined;
|
||||
export type Nothing = undefined;
|
||||
export type Maybe<T> = T | Nothing;
|
||||
export class Failure {
|
||||
constructor(private readonly reason?: string) {}
|
||||
|
||||
export type AsyncMaybe<T> = Promise<Maybe<T>>;
|
||||
getReason(): string {
|
||||
return this.reason ?? 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export function Fail(reason?: string): Failure {
|
||||
return new Failure(reason);
|
||||
}
|
||||
|
||||
export type Failable<T> = T | Failure;
|
||||
|
||||
export type AsyncFailable<T> = Promise<Failable<T>>;
|
||||
|
||||
export function HasFailed<T>(failable: Failable<T>): failable is Failure {
|
||||
return failable instanceof Failure;
|
||||
}
|
||||
|
||||
export function HasSuccess<T>(failable: Failable<T>): failable is T {
|
||||
return !(failable instanceof Failure);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,15 @@ import {
|
||||
} from '@nestjs/platform-fastify';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
import * as multipart from 'fastify-multipart';
|
||||
|
||||
async function bootstrap() {
|
||||
const fastifyAdapter = new FastifyAdapter();
|
||||
fastifyAdapter.register(multipart as any);
|
||||
|
||||
const app = await NestFactory.create<NestFastifyApplication>(
|
||||
AppModule,
|
||||
new FastifyAdapter(),
|
||||
fastifyAdapter,
|
||||
);
|
||||
app.useGlobalPipes(new ValidationPipe({ disableErrorMessages: true }));
|
||||
await app.listen(3000);
|
||||
|
||||
66
src/root/root.controller.ts
Normal file
66
src/root/root.controller.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { isArray } from 'class-validator';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { Multipart, MultipartFields, MultipartFile } from 'fastify-multipart';
|
||||
import { HasFailed } from 'src/lib/maybe';
|
||||
import { SafeImagesService } from 'src/safeimages/safeimages.service';
|
||||
|
||||
@Controller()
|
||||
export class RootController {
|
||||
constructor(private readonly imagesService: SafeImagesService) {}
|
||||
|
||||
@Get('i/:hash')
|
||||
async getImage(
|
||||
@Res({ passthrough: true }) res: FastifyReply,
|
||||
@Param('hash') hash: string,
|
||||
) {
|
||||
if (!this.imagesService.validateHash(hash))
|
||||
throw new BadRequestException('Invalid hash');
|
||||
|
||||
const image = await this.imagesService.retrieveImage(hash);
|
||||
if (HasFailed(image))
|
||||
throw new NotFoundException('Failed to retrieve image');
|
||||
|
||||
res.type(image.mime);
|
||||
return image.data;
|
||||
}
|
||||
|
||||
@Post('i')
|
||||
async uploadImage(@Req() req: FastifyRequest) {
|
||||
if (!req.isMultipart())
|
||||
throw new BadRequestException('Not a multipart request');
|
||||
|
||||
const file = await req.file({ limits: {} });
|
||||
if (file === undefined) throw new BadRequestException('No file uploaded');
|
||||
|
||||
const allFields: Multipart[] = Object.values(file.fields).filter(
|
||||
(entry) => entry,
|
||||
) as any;
|
||||
|
||||
const options = allFields.filter((entry) => entry.file === undefined);
|
||||
const files = allFields.filter((entry) => entry.file !== undefined);
|
||||
|
||||
if (files.length !== 1) throw new BadRequestException('Invalid file');
|
||||
|
||||
const image = await files[0].toBuffer();
|
||||
|
||||
const hash = await this.imagesService.uploadImage(image);
|
||||
if (HasFailed(hash))
|
||||
throw new InternalServerErrorException('Failed to upload image');
|
||||
|
||||
return { hash };
|
||||
}
|
||||
}
|
||||
10
src/root/root.module.ts
Normal file
10
src/root/root.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ImagesModule } from 'src/images/images.module';
|
||||
import { SafeImagesModule } from 'src/safeimages/safeimages.module';
|
||||
import { RootController } from './root.controller';
|
||||
|
||||
@Module({
|
||||
imports: [SafeImagesModule],
|
||||
controllers: [RootController],
|
||||
})
|
||||
export class RootModule {}
|
||||
10
src/safeimages/safeimages.module.ts
Normal file
10
src/safeimages/safeimages.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ImagesModule } from 'src/images/images.module';
|
||||
import { SafeImagesService } from './safeimages.service';
|
||||
|
||||
@Module({
|
||||
imports: [ImagesModule],
|
||||
providers: [SafeImagesService],
|
||||
exports: [SafeImagesService],
|
||||
})
|
||||
export class SafeImagesModule {}
|
||||
44
src/safeimages/safeimages.service.ts
Normal file
44
src/safeimages/safeimages.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { fileTypeFromBuffer } from 'file-type';
|
||||
import { ImageEntity } from 'src/images/image.entity';
|
||||
import { ImagesService } from 'src/images/images.service';
|
||||
import { FullMime, MimesService } from 'src/images/mimes.service';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'src/lib/maybe';
|
||||
|
||||
@Injectable()
|
||||
export class SafeImagesService {
|
||||
constructor(
|
||||
private readonly imagesService: ImagesService,
|
||||
private readonly mimesService: MimesService,
|
||||
) {}
|
||||
|
||||
async uploadImage(image: Buffer): AsyncFailable<string> {
|
||||
const { mime } = await fileTypeFromBuffer(image);
|
||||
const fullMime = await this.mimesService.getFullMime(mime);
|
||||
if (HasFailed(fullMime)) return fullMime;
|
||||
|
||||
const processedImage: Buffer = await this.processImage(image, fullMime);
|
||||
|
||||
const imageEntity = await this.imagesService.create(
|
||||
processedImage,
|
||||
fullMime.mime,
|
||||
);
|
||||
if (HasFailed(imageEntity)) return imageEntity;
|
||||
|
||||
return imageEntity.hash;
|
||||
}
|
||||
|
||||
private async processImage(image: Buffer, mime: FullMime): Promise<Buffer> {
|
||||
return image;
|
||||
}
|
||||
|
||||
async retrieveImage(hash: string): AsyncFailable<ImageEntity> {
|
||||
if (!this.validateHash(hash)) return Fail('Invalid hash');
|
||||
|
||||
return await this.imagesService.findOne(hash);
|
||||
}
|
||||
|
||||
validateHash(hash: string): boolean {
|
||||
return /^[a-f0-9]{64}$/.test(hash);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class UserEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Index()
|
||||
@Column({ unique: true })
|
||||
username: string;
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AsyncMaybe, Nothing } from 'src/lib/maybe';
|
||||
import { Not, Repository } from 'typeorm';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
Failure,
|
||||
HasFailed,
|
||||
HasSuccess,
|
||||
} from 'src/lib/maybe';
|
||||
import { Repository } from 'typeorm';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Injectable()
|
||||
@@ -14,46 +20,65 @@ export class UsersService {
|
||||
async createUser(
|
||||
username: string,
|
||||
hashedPassword: string,
|
||||
): AsyncMaybe<UserEntity> {
|
||||
if (await this.exists(username)) return Nothing;
|
||||
): AsyncFailable<UserEntity> {
|
||||
if (await this.exists(username)) return Fail('User already exists');
|
||||
|
||||
const user = new UserEntity();
|
||||
user.username = username;
|
||||
user.password = hashedPassword;
|
||||
await this.usersRepository.save(user);
|
||||
|
||||
try {
|
||||
await this.usersRepository.save(user);
|
||||
} catch (e) {
|
||||
return Fail(e.message);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async removeUser(user: string | UserEntity): AsyncMaybe<UserEntity> {
|
||||
async removeUser(user: string | UserEntity): AsyncFailable<UserEntity> {
|
||||
const userToModify = await this.resolveUser(user);
|
||||
|
||||
if (user === Nothing) return Nothing;
|
||||
if (HasFailed(userToModify)) return userToModify;
|
||||
|
||||
await this.usersRepository.remove(userToModify);
|
||||
try {
|
||||
await this.usersRepository.remove(userToModify);
|
||||
} catch (e) {
|
||||
return Fail(e.message);
|
||||
}
|
||||
|
||||
return userToModify;
|
||||
}
|
||||
|
||||
async findOne(username: string): AsyncMaybe<UserEntity> {
|
||||
return await this.usersRepository.findOne({ where: { username } });
|
||||
async findOne(username: string): AsyncFailable<UserEntity> {
|
||||
try {
|
||||
const found = await this.usersRepository.findOne({ where: { username } });
|
||||
if (!found) return Fail('User not found');
|
||||
return found;
|
||||
} catch (e) {
|
||||
return Fail(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(): Promise<UserEntity[]> {
|
||||
return await this.usersRepository.find();
|
||||
async findAll(): AsyncFailable<UserEntity[]> {
|
||||
try {
|
||||
return await this.usersRepository.find();
|
||||
} catch (e) {
|
||||
return Fail(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async exists(username: string): Promise<boolean> {
|
||||
return (await this.findOne(username)) !== Nothing;
|
||||
return HasSuccess(await this.findOne(username));
|
||||
}
|
||||
|
||||
async modifyAdmin(
|
||||
user: string | UserEntity,
|
||||
admin: boolean,
|
||||
): Promise<boolean> {
|
||||
): AsyncFailable<true> {
|
||||
const userToModify = await this.resolveUser(user);
|
||||
|
||||
if (userToModify === Nothing) return false;
|
||||
if (HasFailed(userToModify)) return userToModify;
|
||||
|
||||
userToModify.isAdmin = admin;
|
||||
await this.usersRepository.save(userToModify);
|
||||
@@ -61,7 +86,9 @@ export class UsersService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async resolveUser(user: string | UserEntity): Promise<UserEntity> {
|
||||
private async resolveUser(
|
||||
user: string | UserEntity,
|
||||
): AsyncFailable<UserEntity> {
|
||||
if (typeof user === 'string') {
|
||||
return await this.findOne(user);
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2017",
|
||||
"target": "es2020",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
|
||||
267
yarn.lock
267
yarn.lock
@@ -122,6 +122,13 @@
|
||||
dependencies:
|
||||
ajv "^6.12.6"
|
||||
|
||||
"@fastify/busboy@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-1.0.0.tgz#f73182e61955ab91f8ec5a137fda2c9cee366dbd"
|
||||
integrity sha512-tzTXX1TFEjWCseEsNdIlXXkD+48uJoN+zpqIojUX4pSoMscsbhO/UuVEB5SzJucexqDWOo2ma0ECwdD7hZdrzg==
|
||||
dependencies:
|
||||
text-decoding "^1.0.0"
|
||||
|
||||
"@humanwhocodes/config-array@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
|
||||
@@ -289,6 +296,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20"
|
||||
integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==
|
||||
|
||||
"@tokenizer/token@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
|
||||
integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
|
||||
@@ -371,7 +383,7 @@
|
||||
"@types/qs" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express@*", "@types/express@^4.17.13":
|
||||
"@types/express@*":
|
||||
version "4.17.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
|
||||
integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
|
||||
@@ -410,6 +422,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
||||
|
||||
"@types/multer@^1.4.7":
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e"
|
||||
integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "17.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074"
|
||||
@@ -870,21 +889,11 @@ array-union@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
|
||||
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
|
||||
|
||||
asap@^2.0.0:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
astral-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
||||
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||
|
||||
at-least-node@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
@@ -1001,14 +1010,6 @@ buffer@^6.0.3:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
call-bind@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.0.2"
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
@@ -1173,13 +1174,6 @@ colors@1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
||||
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||
@@ -1190,11 +1184,6 @@ commander@^2.20.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
component-emitter@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
@@ -1215,11 +1204,6 @@ cookie@^0.4.0:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
||||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||
|
||||
cookiejar@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc"
|
||||
integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==
|
||||
|
||||
cosmiconfig@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
|
||||
@@ -1245,7 +1229,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3:
|
||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
@@ -1269,11 +1253,6 @@ defaults@^1.0.3:
|
||||
dependencies:
|
||||
clone "^1.0.2"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
delegates@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
@@ -1284,14 +1263,6 @@ detect-libc@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
||||
|
||||
dezalgo@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
|
||||
integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=
|
||||
dependencies:
|
||||
asap "^2.0.0"
|
||||
wrappy "1"
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
@@ -1333,7 +1304,7 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
end-of-stream@^1.1.0:
|
||||
end-of-stream@^1.1.0, end-of-stream@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
@@ -1595,7 +1566,7 @@ fast-redact@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.0.tgz#37c26cda9cab70bc04393f7ba1feb2d176da6c6b"
|
||||
integrity sha512-dir8LOnvialLxiXDPESMDHGp82CHi6ZEYTVkcvdn5d7psdv9ZkkButXrOeXST4aqreIRR+N7CYlsrwFuorurVg==
|
||||
|
||||
fast-safe-stringify@2.1.1, fast-safe-stringify@^2.0.8, fast-safe-stringify@^2.1.1:
|
||||
fast-safe-stringify@2.1.1, fast-safe-stringify@^2.0.8:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
|
||||
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||
@@ -1620,6 +1591,20 @@ fastify-formbody@5.2.0:
|
||||
dependencies:
|
||||
fastify-plugin "^3.0.0"
|
||||
|
||||
fastify-multipart@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/fastify-multipart/-/fastify-multipart-5.3.1.tgz#05254d8aa43dc02af6ce01f4e513a6c30bea2886"
|
||||
integrity sha512-c2pnGfkJmiNpYqzFYT2QfBg/06AxG531O+n1elqc8YUbWPRzufdqn3yfGAIV3RA7J4Vnf7Pfvgx0iaWqaRTOVA==
|
||||
dependencies:
|
||||
"@fastify/busboy" "^1.0.0"
|
||||
deepmerge "^4.2.2"
|
||||
end-of-stream "^1.4.4"
|
||||
fastify-error "^0.3.0"
|
||||
fastify-plugin "^3.0.0"
|
||||
hexoid "^1.0.0"
|
||||
secure-json-parse "^2.4.0"
|
||||
stream-wormhole "^1.1.0"
|
||||
|
||||
fastify-plugin@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-3.0.1.tgz#79e84c29f401020f38b524f59f2402103fd21ed2"
|
||||
@@ -1651,6 +1636,27 @@ fastify@3.27.1:
|
||||
semver "^7.3.2"
|
||||
tiny-lru "^7.0.0"
|
||||
|
||||
fastify@^3.27.2:
|
||||
version "3.27.2"
|
||||
resolved "https://registry.yarnpkg.com/fastify/-/fastify-3.27.2.tgz#61fd226dd72b2d8b6b82e6bf71c18e495026545d"
|
||||
integrity sha512-InZSbbfdBV8yfsTzX0Ei7aF3r7FjC+DPIf27IlTP5EIhSsvTjvlRNwxDPYYGi2NX2K654Vh+zCGCy/GaSigIuw==
|
||||
dependencies:
|
||||
"@fastify/ajv-compiler" "^1.0.0"
|
||||
abstract-logging "^2.0.0"
|
||||
avvio "^7.1.2"
|
||||
fast-json-stringify "^2.5.2"
|
||||
fastify-error "^0.3.0"
|
||||
find-my-way "^4.5.0"
|
||||
flatstr "^1.0.12"
|
||||
light-my-request "^4.2.0"
|
||||
pino "^6.13.0"
|
||||
process-warning "^1.0.0"
|
||||
proxy-addr "^2.0.7"
|
||||
rfdc "^1.1.4"
|
||||
secure-json-parse "^2.0.0"
|
||||
semver "^7.3.2"
|
||||
tiny-lru "^8.0.1"
|
||||
|
||||
fastq@^1.6.0, fastq@^1.6.1:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
|
||||
@@ -1672,6 +1678,15 @@ file-entry-cache@^6.0.1:
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
|
||||
file-type@^17.1.1:
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.1.tgz#24c59bc663df0c0c181b31dfacde25e06431afbe"
|
||||
integrity sha512-heRUMZHby2Qj6wZAA3YHeMlRmZNQTcb6VxctkGmM+mcM6ROQKvHpr7SS6EgdfEhH+s25LDshBjvPx/Ecm+bOVQ==
|
||||
dependencies:
|
||||
readable-web-to-node-stream "^3.0.2"
|
||||
strtok3 "^7.0.0-alpha.7"
|
||||
token-types "^5.0.0-alpha.2"
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
@@ -1731,25 +1746,6 @@ fork-ts-checker-webpack-plugin@6.5.0:
|
||||
semver "^7.3.2"
|
||||
tapable "^1.0.0"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formidable@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff"
|
||||
integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==
|
||||
dependencies:
|
||||
dezalgo "1.0.3"
|
||||
hexoid "1.0.0"
|
||||
once "1.4.0"
|
||||
qs "6.9.3"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
@@ -1826,15 +1822,6 @@ get-caller-file@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-intrinsic@^1.0.2:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
|
||||
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.1"
|
||||
|
||||
get-stream@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
|
||||
@@ -1900,11 +1887,6 @@ has-flag@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
has-symbols@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
|
||||
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
|
||||
|
||||
has-unicode@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
@@ -1917,7 +1899,7 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hexoid@1.0.0:
|
||||
hexoid@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
|
||||
@@ -2360,11 +2342,6 @@ merge2@^1.3.0, merge2@^1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
methods@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||
|
||||
micromatch@^4.0.0, micromatch@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
||||
@@ -2387,18 +2364,13 @@ mime-db@1.51.0:
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
|
||||
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
|
||||
|
||||
mime-types@^2.1.12, mime-types@^2.1.27:
|
||||
mime-types@^2.1.27:
|
||||
version "2.1.34"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
|
||||
integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
|
||||
dependencies:
|
||||
mime-db "1.51.0"
|
||||
|
||||
mime@^2.5.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
@@ -2533,12 +2505,7 @@ object-hash@2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
|
||||
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
||||
|
||||
object-inspect@^1.9.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
|
||||
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
|
||||
|
||||
once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
@@ -2699,6 +2666,11 @@ pause@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
|
||||
integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=
|
||||
|
||||
peek-readable@^5.0.0-alpha.5:
|
||||
version "5.0.0-alpha.5"
|
||||
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0-alpha.5.tgz#ace5dfedf7bc33f17c9b5170b9d54f69a4fba79b"
|
||||
integrity sha512-pJohF/tDwV3ntnT5+EkUo4E700q/j/OCDuPxtM+5/kFGjyOai/sK4/We4Cy1MB2OiTQliWU5DxPvYIKQAdPqAA==
|
||||
|
||||
pg-connection-string@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
||||
@@ -2853,18 +2825,6 @@ punycode@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qs@6.9.3:
|
||||
version "6.9.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e"
|
||||
integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==
|
||||
|
||||
qs@^6.10.1:
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
|
||||
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
queue-microtask@^1.1.2, queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
@@ -2891,6 +2851,13 @@ readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-web-to-node-stream@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb"
|
||||
integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==
|
||||
dependencies:
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
@@ -3035,7 +3002,7 @@ schema-utils@^3.1.0, schema-utils@^3.1.1:
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
secure-json-parse@^2.0.0:
|
||||
secure-json-parse@^2.0.0, secure-json-parse@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.4.0.tgz#5aaeaaef85c7a417f76271a4f5b0cc3315ddca85"
|
||||
integrity sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg==
|
||||
@@ -3108,15 +3075,6 @@ shelljs@0.8.5:
|
||||
interpret "^1.0.0"
|
||||
rechoir "^0.6.2"
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
@@ -3177,6 +3135,11 @@ sprintf-js@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
stream-wormhole@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/stream-wormhole/-/stream-wormhole-1.1.0.tgz#300aff46ced553cfec642a05251885417693c33d"
|
||||
integrity sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==
|
||||
|
||||
string-similarity@^4.0.1:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b"
|
||||
@@ -3220,30 +3183,13 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
superagent@^7.1.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.1.tgz#2ab187d38c3078c31c3771c0b751f10163a27136"
|
||||
integrity sha512-CQ2weSS6M+doIwwYFoMatklhRbx6sVNdB99OEJ5czcP3cng76Ljqus694knFWgOj3RkrtxZqIgpe6vhe0J7QWQ==
|
||||
strtok3@^7.0.0-alpha.7:
|
||||
version "7.0.0-alpha.8"
|
||||
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.0.0-alpha.8.tgz#23a7870974e0494b58b14af6dd1c2c67cf13314d"
|
||||
integrity sha512-u+k19v+rTxBjGYxncRQjGvZYwYvEd0uP3D+uHKe/s4WB1eXS5ZwpZsTlBu5xSS4zEd89mTXECXg6WW3FSeV8cA==
|
||||
dependencies:
|
||||
component-emitter "^1.3.0"
|
||||
cookiejar "^2.1.3"
|
||||
debug "^4.3.3"
|
||||
fast-safe-stringify "^2.1.1"
|
||||
form-data "^4.0.0"
|
||||
formidable "^2.0.1"
|
||||
methods "^1.1.2"
|
||||
mime "^2.5.0"
|
||||
qs "^6.10.1"
|
||||
readable-stream "^3.6.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
supertest@^6.1.6:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.2.2.tgz#04a5998fd3efaff187cb69f07a169755d655b001"
|
||||
integrity sha512-wCw9WhAtKJsBvh07RaS+/By91NNE0Wh0DN19/hWPlBOU8tAfOtbZoVSV4xXeoKoxgPx0rx2y+y+8660XtE7jzg==
|
||||
dependencies:
|
||||
methods "^1.1.2"
|
||||
superagent "^7.1.0"
|
||||
"@tokenizer/token" "^0.3.0"
|
||||
peek-readable "^5.0.0-alpha.5"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
@@ -3329,6 +3275,11 @@ terser@^5.7.2:
|
||||
source-map "~0.7.2"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-decoding@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/text-decoding/-/text-decoding-1.0.0.tgz#38a5692d23b5c2b12942d6e245599cb58b1bc52f"
|
||||
integrity sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
@@ -3358,6 +3309,11 @@ tiny-lru@^7.0.0:
|
||||
resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-7.0.6.tgz#b0c3cdede1e5882aa2d1ae21cb2ceccf2a331f24"
|
||||
integrity sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==
|
||||
|
||||
tiny-lru@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-8.0.1.tgz#c1d77d806e68035aaa2253e253d212291240ece2"
|
||||
integrity sha512-eBIAYA0BzSjxBedCaO0CSjertD+u+IvNuFkyD7ESf+qjqHKBr5wFqvEYl91+ZQd7jjq2pO6/fBVwFgb6bxvorw==
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
@@ -3372,6 +3328,14 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
token-types@^5.0.0-alpha.2:
|
||||
version "5.0.0-alpha.2"
|
||||
resolved "https://registry.yarnpkg.com/token-types/-/token-types-5.0.0-alpha.2.tgz#e43d63b2a8223a593d1c782a5149bec18f1abf97"
|
||||
integrity sha512-EsG9UxAW4M6VATrEEjhPFTKEUi1OiJqTUMIZOGBN49fGxYjZB36k0p7to3HZSmWRoHm1QfZgrg3e02fpqAt5fQ==
|
||||
dependencies:
|
||||
"@tokenizer/token" "^0.3.0"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
@@ -3487,11 +3451,16 @@ typeorm@^0.2.43:
|
||||
yargs "^17.0.1"
|
||||
zen-observable-ts "^1.0.0"
|
||||
|
||||
typescript@4.5.5, typescript@^4.4.4:
|
||||
typescript@4.5.5:
|
||||
version "4.5.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
|
||||
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
|
||||
|
||||
typescript@^4.7.0-dev.20220221:
|
||||
version "4.7.0-dev.20220221"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.0-dev.20220221.tgz#c1b20e391f2d324d9b4f7f32d467949dd9f3954f"
|
||||
integrity sha512-KG/K5SboPsuI/1HIJvqCdegnrVTp13uHrSuIVtLexe5FC3d1uasepzRP7tuglmoaPxE3qaoya3eeGa1/V9N4Dg==
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||
|
||||
Reference in New Issue
Block a user