create shared library

This commit is contained in:
rubikscraft 2022-02-24 22:56:27 +01:00
parent 5f700a3374
commit 825b2856bb
No known key found for this signature in database
GPG Key ID: 1463EBE9200A5CD4
40 changed files with 2501 additions and 4066 deletions

11
.vscode/tasks.json vendored
View File

@ -8,6 +8,15 @@
"isBackground": true,
"group": "build"
},
{
"type": "shell",
"label": "Start shared",
"command": "yarn start",
"options": {
"cwd": "./shared"
},
"group": "build"
},
{
"type": "shell",
"label": "Start backend",
@ -15,6 +24,7 @@
"options": {
"cwd": "./backend"
},
"dependsOn": ["Start shared"],
"group": "build"
},
{
@ -24,6 +34,7 @@
"options": {
"cwd": "./frontend"
},
"dependsOn": ["Start shared"],
"group": "build"
},
{

View File

@ -7,6 +7,7 @@
"repository": "https://github.com/rubikscraft/Imagur",
"author": "Rubikscraft <contact@rubikscraft.nl>",
"type": "module",
"main": "dist/main.js",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
@ -41,7 +42,8 @@
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.4",
"typeorm": "^0.2.43"
"typeorm": "^0.2.43",
"imagur-shared": "*"
},
"devDependencies": {
"@nestjs/cli": "^8.2.1",

View File

@ -7,8 +7,6 @@ import { ServeStaticModule } from '@nestjs/serve-static';
import Config from './env';
import { ImageEntity } from './collections/imagedb/image.entity';
const backendRoutes = ['i', 'api'];
@Module({
imports: [
TypeOrmModule.forRoot({

View File

@ -3,8 +3,8 @@ 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/types/failable';
import { SupportedMime } from './mimes.service';
import { AsyncFailable, Fail, HasFailed, HasSuccess } from 'imagur-shared/dist/types';
@Injectable()
export class ImageDBService {
@ -27,8 +27,8 @@ export class ImageDBService {
imageEntity.hash = hash;
try {
await this.imageRepository.save(imageEntity);
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
return imageEntity;
@ -39,8 +39,8 @@ export class ImageDBService {
const found = await this.imageRepository.findOne({ where: { hash } });
if (found === undefined) return Fail('Image not found');
return found;
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
}
@ -55,8 +55,8 @@ export class ImageDBService {
});
if (found === undefined) return Fail('Images not found');
return found;
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
}
@ -67,8 +67,8 @@ export class ImageDBService {
try {
await this.imageRepository.delete(image);
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
return true;
}

View File

@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { Fail, Failable } from 'src/types/failable';
import { Fail, Failable } from 'imagur-shared/dist/types';
const tuple = <T extends string[]>(...args: T): T => args;

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AsyncFailable, Fail, HasFailed, HasSuccess } from 'src/types/failable';
import { AsyncFailable, Fail, HasFailed, HasSuccess } from 'imagur-shared/dist/types';
import { Repository } from 'typeorm';
import { UserEntity } from './user.entity';
@ -23,8 +23,8 @@ export class UsersService {
try {
await this.usersRepository.save(user);
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
return user;
@ -37,8 +37,8 @@ export class UsersService {
try {
await this.usersRepository.remove(userToModify);
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
return userToModify;
@ -49,16 +49,16 @@ export class UsersService {
const found = await this.usersRepository.findOne({ where: { username } });
if (!found) return Fail('User not found');
return found;
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
}
public async findAll(): AsyncFailable<UserEntity[]> {
try {
return await this.usersRepository.find();
} catch (e) {
return Fail(e.message);
} catch (e: any) {
return Fail(e?.message);
}
}

View File

@ -8,8 +8,8 @@ import {
import { validate } from 'class-validator';
import { FastifyRequest } from 'fastify';
import { Multipart, MultipartFields, MultipartFile } from 'fastify-multipart';
import Config from 'src/env';
import { Newable } from 'src/types/newable';
import { Newable } from 'imagur-shared/dist/types';
import Config from '../env';
import { MultiPartFieldDto, MultiPartFileDto } from './multipart.dto';
const logger = new Logger('MultiPart');
@ -56,7 +56,7 @@ export const MultiPart = createParamDecorator(
if (!req.isMultipart()) throw new BadRequestException('Invalid file');
let fields: MultipartFields;
let fields: MultipartFields | null = null;
try {
fields = (
await req.file({
@ -74,9 +74,9 @@ export const MultiPart = createParamDecorator(
}
if ((fields[key] as any).value) {
dtoClass[key] = new MultiPartFieldDto(fields[key] as MultipartFile);
(dtoClass as any)[key] = new MultiPartFieldDto(fields[key] as MultipartFile);
} else {
dtoClass[key] = new MultiPartFileDto(
(dtoClass as any)[key] = new MultiPartFileDto(
fields[key] as MultipartFile,
new BadRequestException('Invalid file'),
);

View File

@ -1,7 +1,6 @@
import { MultipartFile } from 'fastify-multipart';
import { BusboyFileStream } from '@fastify/busboy';
import { IsDefined, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { AsyncFailable, Fail } from 'src/types/failable';
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
import { HttpException } from '@nestjs/common';
export class MultiPartFileDto {

View File

@ -1,5 +1,5 @@
import { Module } from '@nestjs/common';
import { ImageDBModule } from 'src/collections/imagedb/imagedb.module';
import { ImageDBModule } from '../../collections/imagedb/imagedb.module';
import { ImageManagerService } from './imagemanager.service';
@Module({

View File

@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common';
import { fileTypeFromBuffer, FileTypeResult } from 'file-type';
import { ImageEntity } from 'src/collections/imagedb/image.entity';
import { ImageDBService } from 'src/collections/imagedb/imagedb.service';
import { FullMime, MimesService } from 'src/collections/imagedb/mimes.service';
import { AsyncFailable, Fail, HasFailed } from 'src/types/failable';
import { AsyncFailable, Fail, HasFailed } from 'imagur-shared/dist/types';
import { ImageEntity } from '../../collections/imagedb/image.entity';
import { ImageDBService } from '../../collections/imagedb/imagedb.service';
import { MimesService, FullMime } from '../../collections/imagedb/mimes.service';
@Injectable()
export class ImageManagerService {
@ -38,7 +38,7 @@ export class ImageManagerService {
}
private async getFullMimeFromBuffer(image: Buffer): AsyncFailable<FullMime> {
const mime: FileTypeResult = await fileTypeFromBuffer(image);
const mime: FileTypeResult | undefined = await fileTypeFromBuffer(image);
const fullMime = await this.mimesService.getFullMime(
mime?.mime ?? 'extra/discard',
);

View File

@ -6,7 +6,7 @@ import {
} from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { User } from 'src/collections/userdb/user.dto';
import { User } from '../../../collections/userdb/user.dto';
@Injectable()
export class AdminGuard implements CanActivate {

View File

@ -18,7 +18,8 @@ import {
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt.guard';
import { AdminGuard } from './admin.guard';
import { HasFailed } from 'src/types/failable';
import { HasFailed } from 'imagur-shared/dist/types';
import AuthFasityRequest from './authrequest';
@Controller('api/auth')
export class AuthController {
@ -26,7 +27,7 @@ export class AuthController {
@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Request() req) {
async login(@Request() req: AuthFasityRequest) {
const response: LoginResponseDto = {
access_token: await this.authService.createToken(req.user),
};
@ -36,7 +37,10 @@ export class AuthController {
@UseGuards(JwtAuthGuard, AdminGuard)
@Post('create')
async register(@Request() req, @Body() register: RegisterRequestDto) {
async register(
@Request() req: AuthFasityRequest,
@Body() register: RegisterRequestDto,
) {
const user = await this.authService.createUser(
register.username,
register.password,
@ -53,7 +57,10 @@ export class AuthController {
@UseGuards(JwtAuthGuard, AdminGuard)
@Post('delete')
async delete(@Request() req, @Body() deleteData: DeleteRequestDto) {
async delete(
@Request() req: AuthFasityRequest,
@Body() deleteData: DeleteRequestDto,
) {
const user = await this.authService.deleteUser(deleteData.username);
if (HasFailed(user)) throw new NotFoundException('User does not exist');
@ -62,7 +69,7 @@ export class AuthController {
@UseGuards(JwtAuthGuard, AdminGuard)
@Get('list')
async listUsers(@Request() req) {
async listUsers(@Request() req: AuthFasityRequest) {
const users = this.authService.listUsers();
if (HasFailed(users))
@ -73,9 +80,7 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@Get('me')
async me(@Request() req) {
async me(@Request() req: AuthFasityRequest) {
return req.user;
}
}
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoiYWRtaW4iLCJpc0FkbWluIjp0cnVlfSwiaWF0IjoxNjQ1NDUxMzg1LCJleHAiOjE2NDU1Mzc3ODV9.Uf6JmygblXgBS4ztTcfZl6m579YjH2R0uD0QyNCfQNs

View File

@ -6,7 +6,7 @@ import {
IsString,
ValidateNested,
} from 'class-validator';
import { User } from 'src/collections/userdb/user.dto';
import { User } from '../../../collections/userdb/user.dto';
export class LoginResponseDto {
@IsString()

View File

@ -1,12 +1,12 @@
import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { UsersModule } from 'src/collections/userdb/userdb.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { AuthController } from './auth.controller';
import Config from 'src/env';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../../../collections/userdb/userdb.module';
import Config from '../../../env';
@Module({
imports: [

View File

@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { AsyncFailable, Fail, HasFailed } from 'src/types/failable';
import { User } from 'src/collections/userdb/user.dto';
import { UserEntity } from 'src/collections/userdb/user.entity';
import { UsersService } from 'src/collections/userdb/userdb.service';
import { AsyncFailable, HasFailed, Fail } from 'imagur-shared/dist/types';
import { User } from '../../../collections/userdb/user.dto';
import { UserEntity } from '../../../collections/userdb/user.entity';
import { UsersService } from '../../../collections/userdb/userdb.service';
import { JwtDataDto } from './auth.dto';
@Injectable()
@ -47,7 +47,7 @@ export class AuthService {
return user;
}
async createToken(user: UserEntity): Promise<string> {
async createToken(user: User): Promise<string> {
const jwtData: JwtDataDto = {
user: {
username: user.username,

View File

@ -0,0 +1,6 @@
import { FastifyRequest } from 'fastify';
import { User } from '../../../collections/userdb/user.dto';
export default interface AuthFasityRequest extends FastifyRequest {
user: User;
}

View File

@ -1,11 +1,11 @@
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
import Config from 'src/env';
import { validate } from 'class-validator';
import { JwtDataDto } from './auth.dto';
import { plainToClass } from 'class-transformer';
import { User } from 'src/collections/userdb/user.dto';
import { User } from '../../../collections/userdb/user.dto';
import Config from '../../../env';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {

View File

@ -2,8 +2,8 @@ import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AsyncFailable, HasFailed } from 'src/types/failable';
import { UserEntity } from 'src/collections/userdb/user.entity';
import { User } from '../../../collections/userdb/user.dto';
import { AsyncFailable, HasFailed } from 'imagur-shared/dist/types';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
@ -11,14 +11,16 @@ export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
super();
}
async validate(
username: string,
password: string,
): AsyncFailable<UserEntity> {
const user = await this.authService.authenticate(username, password);
if (HasFailed(user)) {
async validate(username: string, password: string): AsyncFailable<User> {
const userEntity = await this.authService.authenticate(username, password);
if (HasFailed(userEntity)) {
throw new UnauthorizedException();
}
const user: User = {
username: userEntity.username,
isAdmin: userEntity.isAdmin,
};
return user;
}
}

View File

@ -9,18 +9,10 @@ import {
Req,
Res,
} from '@nestjs/common';
import {
IsBoolean,
IsDefined,
Validate,
ValidateNested,
} from 'class-validator';
import { FastifyReply, FastifyRequest } from 'fastify';
import { User } from 'src/collections/userdb/user.dto';
import { MultiPart, MultiPartDto } from 'src/decorators/multipart.decorator';
import { MultiPartFileDto } from 'src/decorators/multipart.dto';
import { ImageManagerService } from 'src/managers/imagemanager/imagemanager.service';
import { HasFailed } from 'src/types/failable';
import { HasFailed } from 'imagur-shared/dist/types';
import { MultiPart } from '../../decorators/multipart.decorator';
import { ImageManagerService } from '../../managers/imagemanager/imagemanager.service';
import { ImageUploadDto } from './imageroute.dto';
@Controller('i')
export class ImageController {

View File

@ -1,6 +1,6 @@
import { IsDefined, ValidateNested } from 'class-validator';
import { MultiPartDto } from 'src/decorators/multipart.decorator';
import { MultiPartFileDto } from 'src/decorators/multipart.dto';
import { MultiPartDto } from '../../decorators/multipart.decorator';
import { MultiPartFileDto } from '../../decorators/multipart.dto';
export class ImageUploadDto extends MultiPartDto {
@IsDefined()

View File

@ -1,6 +1,5 @@
import { Module } from '@nestjs/common';
import { ImageManagerModule } from 'src/managers/imagemanager/imagemanager.module';
import { ImageManagerModule } from '../../managers/imagemanager/imagemanager.module';
import { ImageController } from './imageroute.controller';
@Module({

View File

@ -1,18 +1,12 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"module": "es2020",
"moduleResolution": "node",
"esModuleInterop": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2020",
"sourceMap": true,
"module": "es2020",
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true
"declaration": true,
"sourceMap": true,
"emitDecoratorMetadata": true
}
}

File diff suppressed because it is too large Load Diff

1
frontend/.env Normal file
View File

@ -0,0 +1 @@
BUILD_PATH='./dist'

View File

@ -1,8 +1,10 @@
const path = require('path');
module.exports = {
devServer: {
writeToDisk: true,
port: 3300,
liveReload: true,
open: false
},
open: false,
}
};

View File

@ -7,6 +7,7 @@
"repository": "https://github.com/rubikscraft/Imagur",
"author": "Rubikscraft <contact@rubikscraft.nl>",
"type": "commonjs",
"main": "dist/index.js",
"dependencies": {
"@emotion/react": "^11.8.1",
"@emotion/styled": "^11.8.1",
@ -16,7 +17,8 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-dropzone": "^12.0.4",
"react-router-dom": "^6.2.1"
"react-router-dom": "^6.2.1",
"imagur-shared": "*"
},
"devDependencies": {
"@babel/core": "^7.17.5",

View File

@ -1,4 +1,4 @@
import { AsyncFailable, Fail } from '../types/failable';
import { AsyncFailable, Fail } from 'imagur-shared/dist/types'
export function GetImageURL(image: string): string {
const baseURL = window.location.protocol + '//' + window.location.host;

View File

@ -3,7 +3,7 @@ import CircularProgress from '@mui/material/CircularProgress';
import { useLocation, useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
import { UploadImage } from '../../api/images';
import { HasFailed } from '../../types/failable';
import { HasFailed } from 'imagur-shared/dist/types';
export interface ProcessingViewMetadata {
imageFile: File;

View File

@ -1,23 +0,0 @@
export class Failure {
constructor(private readonly reason?: string) {}
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);
}

View File

@ -1,20 +1,18 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"jsx": "react-jsx",
"noEmit": true,
"jsx": "react-jsx"
"skipLibCheck": true
},
"include": ["src"]
"include": [
"src"
]
}

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"private": true,
"workspaces": [
"shared",
"backend",
"frontend"
]
}

2
shared/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
dist

22
shared/package.json Normal file
View File

@ -0,0 +1,22 @@
{
"private": false,
"name": "imagur-shared",
"version": "1.0.0",
"description": "Shared libraries for Imagur",
"license": "GPL-3.0",
"repository": "https://github.com/rubikscraft/Imagur",
"author": "Rubikscraft <contact@rubikscraft.nl>",
"type": "commonjs",
"main": "./dist/index.js",
"dependencies": {
"tsc-watch": "^4.6.0"
},
"devDependencies": {
"@types/node": "^17.0.20",
"typescript": "^4.5.5"
},
"scripts": {
"start": "tsc-watch",
"build": "tsc"
}
}

4
shared/src/index.ts Normal file
View File

@ -0,0 +1,4 @@
// Nothing
const a = 'hello';
export default a;

View File

@ -0,0 +1,2 @@
export * from './failable';
export * from './newable';

13
shared/tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"target": "es2015",
"module": "esnext",
"outDir": "./dist",
"declaration": true,
"emitDecoratorMetadata": true,
"sourceMap": true
},
"include": ["src"]
}

23
tsconfig.base.json Normal file
View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"allowJs": true,
"strict": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"strictPropertyInitialization": false,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"removeComments": true,
"isolatedModules": true,
"incremental": true
}
}

File diff suppressed because it is too large Load Diff