make sure typeorm stays in backend

This commit is contained in:
rubikscraft
2022-03-01 22:05:59 +01:00
parent 05c0ad0e28
commit 0e0060ffb5
16 changed files with 108 additions and 67 deletions

View File

@@ -5,8 +5,8 @@ import { ImageModule } from './routes/image/imageroute.module';
import { ServeStaticModule } from '@nestjs/serve-static'; import { ServeStaticModule } from '@nestjs/serve-static';
import Config from './env'; import Config from './env';
import { DemoManagerModule } from './managers/demo/demomanager.module'; import { DemoManagerModule } from './managers/demo/demomanager.module';
import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { EImageBackend } from './backenddto/image.entity';
import { EImage } from 'picsur-shared/dist/entities/image.entity'; import { EUserBackend } from './backenddto/user.entity';
@Module({ @Module({
imports: [ imports: [
@@ -19,7 +19,7 @@ import { EImage } from 'picsur-shared/dist/entities/image.entity';
database: Config.database.database, database: Config.database.database,
synchronize: true, synchronize: true,
entities: [EUser, EImage], entities: [EUserBackend, EImageBackend],
}), }),
ServeStaticModule.forRoot({ ServeStaticModule.forRoot({
rootPath: Config.static.frontendRoot, rootPath: Config.static.frontendRoot,

View File

@@ -0,0 +1,23 @@
import {
SupportedMime,
SupportedMimes,
} from 'picsur-shared/dist/dto/mimes.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class EImageBackend extends EImage {
@PrimaryGeneratedColumn()
override id?: number;
@Index()
@Column({ unique: true })
override hash: string;
// Binary data
@Column({ type: 'bytea', nullable: false, select: false })
override data?: Buffer;
@Column({ enum: SupportedMimes })
override mime: SupportedMime;
}

View File

@@ -0,0 +1,20 @@
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
// Different data for public and private
@Entity()
export class EUserBackend extends EUser {
@PrimaryGeneratedColumn()
override id?: number;
@Index()
@Column({ unique: true })
override username: string;
@Column({ default: false })
override isAdmin: boolean;
@Column({ select: false })
override password?: string;
}

View File

@@ -1,11 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { EImageBackend } from '../../backenddto/image.entity';
import { ImageDBService } from './imagedb.service'; import { ImageDBService } from './imagedb.service';
import { MimesService } from './mimes.service'; import { MimesService } from './mimes.service';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([EImage])], imports: [TypeOrmModule.forFeature([EImageBackend])],
providers: [ImageDBService, MimesService], providers: [ImageDBService, MimesService],
exports: [ImageDBService, MimesService], exports: [ImageDBService, MimesService],
}) })

View File

@@ -10,25 +10,25 @@ import {
} from 'picsur-shared/dist/types'; } from 'picsur-shared/dist/types';
import { SupportedMime } from 'picsur-shared/dist/dto/mimes.dto'; import { SupportedMime } from 'picsur-shared/dist/dto/mimes.dto';
import { GetCols } from '../collectionutils'; import { GetCols } from '../collectionutils';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { plainToClass } from 'class-transformer'; import { plainToClass } from 'class-transformer';
import { EImageBackend } from '../../backenddto/image.entity';
@Injectable() @Injectable()
export class ImageDBService { export class ImageDBService {
constructor( constructor(
@InjectRepository(EImage) @InjectRepository(EImageBackend)
private imageRepository: Repository<EImage>, private imageRepository: Repository<EImageBackend>,
) {} ) {}
public async create( public async create(
image: Buffer, image: Buffer,
type: SupportedMime, type: SupportedMime,
): AsyncFailable<EImage> { ): AsyncFailable<EImageBackend> {
const hash = this.hash(image); const hash = this.hash(image);
const find = await this.findOne(hash); const find = await this.findOne(hash);
if (HasSuccess(find)) return find; if (HasSuccess(find)) return find;
let imageEntity = new EImage(); let imageEntity = new EImageBackend();
imageEntity.data = image; imageEntity.data = image;
imageEntity.mime = type; imageEntity.mime = type;
imageEntity.hash = hash; imageEntity.hash = hash;
@@ -40,13 +40,13 @@ export class ImageDBService {
} }
// Strips unwanted data // Strips unwanted data
return plainToClass(EImage, imageEntity); return plainToClass(EImageBackend, imageEntity);
} }
public async findOne<B extends true | undefined = undefined>( public async findOne<B extends true | undefined = undefined>(
hash: string, hash: string,
getPrivate?: B, getPrivate?: B,
): AsyncFailable<B extends undefined ? EImage : Required<EImage>> { ): AsyncFailable<B extends undefined ? EImageBackend : Required<EImageBackend>> {
try { try {
const found = await this.imageRepository.findOne({ const found = await this.imageRepository.findOne({
where: { hash }, where: { hash },
@@ -54,7 +54,7 @@ export class ImageDBService {
}); });
if (!found) return Fail('Image not found'); if (!found) return Fail('Image not found');
return found as B extends undefined ? EImage : Required<EImage>; return found as B extends undefined ? EImageBackend : Required<EImageBackend>;
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
@@ -63,7 +63,7 @@ export class ImageDBService {
public async findMany( public async findMany(
startId: number, startId: number,
limit: number, limit: number,
): AsyncFailable<EImage[]> { ): AsyncFailable<EImageBackend[]> {
try { try {
const found = await this.imageRepository.find({ const found = await this.imageRepository.find({
where: { id: { gte: startId } }, where: { id: { gte: startId } },

View File

@@ -1,10 +1,10 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { EUserBackend } from '../../backenddto/user.entity';
import { UsersService } from './userdb.service'; import { UsersService } from './userdb.service';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([EUser])], imports: [TypeOrmModule.forFeature([EUserBackend])],
providers: [UsersService], providers: [UsersService],
exports: [UsersService], exports: [UsersService],
}) })

View File

@@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer'; import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator'; import { validate } from 'class-validator';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { import {
AsyncFailable, AsyncFailable,
Fail, Fail,
@@ -10,6 +9,7 @@ import {
HasSuccess, HasSuccess,
} from 'picsur-shared/dist/types'; } from 'picsur-shared/dist/types';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { EUserBackend } from '../../backenddto/user.entity';
import { GetCols } from '../collectionutils'; import { GetCols } from '../collectionutils';
@Injectable() @Injectable()
@@ -17,17 +17,17 @@ export class UsersService {
private readonly logger = new Logger('UsersService'); private readonly logger = new Logger('UsersService');
constructor( constructor(
@InjectRepository(EUser) @InjectRepository(EUserBackend)
private usersRepository: Repository<EUser>, private usersRepository: Repository<EUserBackend>,
) {} ) {}
public async create( public async create(
username: string, username: string,
hashedPassword: string, hashedPassword: string,
): AsyncFailable<EUser> { ): AsyncFailable<EUserBackend> {
if (await this.exists(username)) return Fail('User already exists'); if (await this.exists(username)) return Fail('User already exists');
let user = new EUser(); let user = new EUserBackend();
user.username = username; user.username = username;
user.password = hashedPassword; user.password = hashedPassword;
@@ -37,11 +37,11 @@ export class UsersService {
return Fail(e?.message); return Fail(e?.message);
} }
return plainToClass(EUser, user); // Strips unwanted data return plainToClass(EUserBackend, user); // Strips unwanted data
} }
// Returns user object without id // Returns user object without id
public async delete(user: string | EUser): AsyncFailable<EUser> { public async delete(user: string | EUserBackend): AsyncFailable<EUserBackend> {
const userToModify = await this.resolve(user); const userToModify = await this.resolve(user);
if (HasFailed(userToModify)) return userToModify; if (HasFailed(userToModify)) return userToModify;
@@ -55,7 +55,7 @@ export class UsersService {
public async findOne<B extends true | undefined = undefined>( public async findOne<B extends true | undefined = undefined>(
username: string, username: string,
getPrivate?: B, getPrivate?: B,
): AsyncFailable<B extends undefined ? EUser : Required<EUser>> { ): AsyncFailable<B extends undefined ? EUserBackend : Required<EUserBackend>> {
try { try {
const found = await this.usersRepository.findOne({ const found = await this.usersRepository.findOne({
where: { username }, where: { username },
@@ -63,13 +63,13 @@ export class UsersService {
}); });
if (!found) return Fail('User not found'); if (!found) return Fail('User not found');
return found as B extends undefined ? EUser : Required<EUser>; return found as B extends undefined ? EUserBackend : Required<EUserBackend>;
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
} }
public async findAll(): AsyncFailable<EUser[]> { public async findAll(): AsyncFailable<EUserBackend[]> {
try { try {
return await this.usersRepository.find(); return await this.usersRepository.find();
} catch (e: any) { } catch (e: any) {
@@ -82,7 +82,7 @@ export class UsersService {
} }
public async modifyAdmin( public async modifyAdmin(
user: string | EUser, user: string | EUserBackend,
admin: boolean, admin: boolean,
): AsyncFailable<true> { ): AsyncFailable<true> {
const userToModify = await this.resolve(user); const userToModify = await this.resolve(user);
@@ -94,11 +94,11 @@ export class UsersService {
return true; return true;
} }
private async resolve(user: string | EUser): AsyncFailable<EUser> { private async resolve(user: string | EUserBackend): AsyncFailable<EUserBackend> {
if (typeof user === 'string') { if (typeof user === 'string') {
return await this.findOne(user); return await this.findOne(user);
} else { } else {
user = plainToClass(EUser, user); user = plainToClass(EUserBackend, user);
const errors = await validate(user, { forbidUnknownValues: true }); const errors = await validate(user, { forbidUnknownValues: true });
if (errors.length > 0) { if (errors.length > 0) {
this.logger.warn(errors); this.logger.warn(errors);

View File

@@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { isHash } from 'class-validator';
import { fileTypeFromBuffer, FileTypeResult } from 'file-type'; import { fileTypeFromBuffer, FileTypeResult } from 'file-type';
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto'; import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity'; import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types'; import { EImageBackend } from '../../backenddto/image.entity';
import { ImageDBService } from '../../collections/imagedb/imagedb.service'; import { ImageDBService } from '../../collections/imagedb/imagedb.service';
import { MimesService } from '../../collections/imagedb/mimes.service'; import { MimesService } from '../../collections/imagedb/mimes.service';
@@ -14,16 +13,16 @@ export class ImageManagerService {
private readonly mimesService: MimesService, private readonly mimesService: MimesService,
) {} ) {}
public async retrieveInfo(hash: string): AsyncFailable<EImage> { public async retrieveInfo(hash: string): AsyncFailable<EImageBackend> {
return await this.imagesService.findOne(hash); return await this.imagesService.findOne(hash);
} }
// Image data buffer is not included by default, this also returns that buffer // Image data buffer is not included by default, this also returns that buffer
public async retrieveComplete(hash: string): AsyncFailable<Required<EImage>> { public async retrieveComplete(hash: string): AsyncFailable<Required<EImageBackend>> {
return await this.imagesService.findOne(hash, true); return await this.imagesService.findOne(hash, true);
} }
public async upload(image: Buffer): AsyncFailable<EImage> { public async upload(image: Buffer): AsyncFailable<EImageBackend> {
const fullMime = await this.getFullMimeFromBuffer(image); const fullMime = await this.getFullMimeFromBuffer(image);
if (HasFailed(fullMime)) return fullMime; if (HasFailed(fullMime)) return fullMime;

View File

@@ -6,7 +6,7 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { plainToClass } from 'class-transformer'; import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator'; import { validate } from 'class-validator';
import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { EUserBackend } from '../../../backenddto/user.entity';
@Injectable() @Injectable()
export class AdminGuard implements CanActivate { export class AdminGuard implements CanActivate {
@@ -15,7 +15,7 @@ export class AdminGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
const user = plainToClass(EUser, request.user); const user = plainToClass(EUserBackend, request.user);
const errors = await validate(user, {forbidUnknownValues: true}); const errors = await validate(user, {forbidUnknownValues: true});
if (errors.length > 0) { if (errors.length > 0) {
this.logger.warn(errors); this.logger.warn(errors);

View File

@@ -4,8 +4,8 @@ import * as bcrypt from 'bcrypt';
import { instanceToPlain, plainToClass } from 'class-transformer'; import { instanceToPlain, plainToClass } from 'class-transformer';
import { validate } from 'class-validator'; import { validate } from 'class-validator';
import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto'; import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable, HasFailed, Fail } from 'picsur-shared/dist/types'; import { AsyncFailable, HasFailed, Fail } from 'picsur-shared/dist/types';
import { EUserBackend } from '../../../backenddto/user.entity';
import { UsersService } from '../../../collections/userdb/userdb.service'; import { UsersService } from '../../../collections/userdb/userdb.service';
@Injectable() @Injectable()
@@ -17,20 +17,20 @@ export class AuthService {
private jwtService: JwtService, private jwtService: JwtService,
) {} ) {}
async createUser(username: string, password: string): AsyncFailable<EUser> { async createUser(username: string, password: string): AsyncFailable<EUserBackend> {
const hashedPassword = await bcrypt.hash(password, 12); const hashedPassword = await bcrypt.hash(password, 12);
return this.usersService.create(username, hashedPassword); return this.usersService.create(username, hashedPassword);
} }
async deleteUser(user: string | EUser): AsyncFailable<EUser> { async deleteUser(user: string | EUserBackend): AsyncFailable<EUserBackend> {
return this.usersService.delete(user); return this.usersService.delete(user);
} }
async listUsers(): AsyncFailable<EUser[]> { async listUsers(): AsyncFailable<EUserBackend[]> {
return this.usersService.findAll(); return this.usersService.findAll();
} }
async authenticate(username: string, password: string): AsyncFailable<EUser> { async authenticate(username: string, password: string): AsyncFailable<EUserBackend> {
const user = await this.usersService.findOne(username, true); const user = await this.usersService.findOne(username, true);
if (HasFailed(user)) return user; if (HasFailed(user)) return user;
@@ -40,7 +40,7 @@ export class AuthService {
return await this.usersService.findOne(username); return await this.usersService.findOne(username);
} }
async createToken(user: EUser): Promise<string> { async createToken(user: EUserBackend): Promise<string> {
const jwtData: JwtDataDto = plainToClass(JwtDataDto, { const jwtData: JwtDataDto = plainToClass(JwtDataDto, {
user, user,
}); });
@@ -54,11 +54,11 @@ export class AuthService {
return this.jwtService.signAsync(instanceToPlain(jwtData)); return this.jwtService.signAsync(instanceToPlain(jwtData));
} }
async makeAdmin(user: string | EUser): AsyncFailable<true> { async makeAdmin(user: string | EUserBackend): AsyncFailable<true> {
return this.usersService.modifyAdmin(user, true); return this.usersService.modifyAdmin(user, true);
} }
async revokeAdmin(user: string | EUser): AsyncFailable<true> { async revokeAdmin(user: string | EUserBackend): AsyncFailable<true> {
return this.usersService.modifyAdmin(user, false); return this.usersService.modifyAdmin(user, false);
} }
} }

View File

@@ -1,6 +1,6 @@
import { FastifyRequest } from 'fastify'; import { FastifyRequest } from 'fastify';
import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { EUserBackend } from '../../../backenddto/user.entity';
export default interface AuthFasityRequest extends FastifyRequest { export default interface AuthFasityRequest extends FastifyRequest {
user: EUser; user: EUserBackend;
} }

View File

@@ -5,7 +5,7 @@ import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer'; import { plainToClass } from 'class-transformer';
import Config from '../../../env'; import Config from '../../../env';
import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto'; import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { EUserBackend } from '../../../backenddto/user.entity';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
@@ -19,7 +19,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
}); });
} }
async validate(payload: any): Promise<EUser> { async validate(payload: any): Promise<EUserBackend> {
const jwt = plainToClass(JwtDataDto, payload); const jwt = plainToClass(JwtDataDto, payload);
const errors = await validate(jwt, { const errors = await validate(jwt, {

View File

@@ -3,7 +3,7 @@ import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types'; import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { EUserBackend } from '../../../backenddto/user.entity';
@Injectable() @Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') { export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
@@ -11,7 +11,7 @@ export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
super(); super();
} }
async validate(username: string, password: string): AsyncFailable<EUser> { async validate(username: string, password: string): AsyncFailable<EUserBackend> {
const user = await this.authService.authenticate(username, password); const user = await this.authService.authenticate(username, password);
if (HasFailed(user)) { if (HasFailed(user)) {
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@@ -11,8 +11,7 @@
"dependencies": { "dependencies": {
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.13.2", "class-validator": "^0.13.2",
"tsc-watch": "^4.6.0", "tsc-watch": "^4.6.0"
"typeorm": "^0.2.44"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^17.0.21", "@types/node": "^17.0.21",

View File

@@ -5,27 +5,27 @@ import {
IsHash, IsHash,
IsOptional, IsOptional,
} from 'class-validator'; } from 'class-validator';
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; //import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
import { SupportedMime, SupportedMimes } from '../dto/mimes.dto'; import { SupportedMime, SupportedMimes } from '../dto/mimes.dto';
@Entity() //@Entity()
export class EImage { export class EImage {
@PrimaryGeneratedColumn() // @PrimaryGeneratedColumn()
@IsOptional() @IsOptional()
id?: number; id?: number;
@Index() // @Index()
@Column({ unique: true }) // @Column({ unique: true })
@IsHash('sha256') @IsHash('sha256')
hash: string; hash: string;
// Binary data // Binary data
@Column({ type: 'bytea', nullable: false, select: false }) // @Column({ type: 'bytea', nullable: false, select: false })
@IsOptional() @IsOptional()
@Exclude() @Exclude()
data?: Buffer; data?: Buffer;
@Column({ enum: SupportedMimes }) // @Column({ enum: SupportedMimes })
@IsEnum(SupportedMimes) @IsEnum(SupportedMimes)
@IsDefined() @IsDefined()
mime: SupportedMime; mime: SupportedMime;

View File

@@ -5,26 +5,26 @@ import {
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
} from 'class-validator'; } from 'class-validator';
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; // import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
// Different data for public and private // Different data for public and private
@Entity() // @Entity()
export class EUser { export class EUser {
@PrimaryGeneratedColumn() // @PrimaryGeneratedColumn()
@IsOptional() @IsOptional()
id?: number; id?: number;
@Index() // @Index()
@Column({ unique: true }) // @Column({ unique: true })
@IsNotEmpty() @IsNotEmpty()
username: string; username: string;
@Column({ default: false }) // @Column({ default: false })
@IsDefined() @IsDefined()
isAdmin: boolean; isAdmin: boolean;
@Column({ select: false }) // @Column({ select: false })
@IsOptional() @IsOptional()
@Exclude() @Exclude()
password?: string; password?: string;