mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
Transfer repository from Gitlab
This commit is contained in:
85
server/src/api-v1/asset/asset.controller.ts
Normal file
85
server/src/api-v1/asset/asset.controller.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
UseInterceptors,
|
||||
UploadedFiles,
|
||||
Body,
|
||||
UseGuards,
|
||||
Get,
|
||||
Param,
|
||||
ValidationPipe,
|
||||
StreamableFile,
|
||||
Response,
|
||||
Query,
|
||||
Logger,
|
||||
UploadedFile,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { AssetService } from './asset.service';
|
||||
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
||||
import { multerOption } from '../../config/multer-option.config';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||
import { createReadStream } from 'fs';
|
||||
import { ServeFileDto } from './dto/serve-file.dto';
|
||||
import { ImageOptimizeService } from '../../modules/image-optimize/image-optimize.service';
|
||||
import { AssetType } from './entities/asset.entity';
|
||||
import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('asset')
|
||||
export class AssetController {
|
||||
constructor(
|
||||
private readonly assetService: AssetService,
|
||||
private readonly imageOptimizeService: ImageOptimizeService,
|
||||
) {}
|
||||
|
||||
@Post('upload')
|
||||
@UseInterceptors(FilesInterceptor('files', 30, multerOption))
|
||||
async uploadFile(
|
||||
@GetAuthUser() authUser,
|
||||
@UploadedFiles() files: Express.Multer.File[],
|
||||
@Body(ValidationPipe) assetInfo: CreateAssetDto,
|
||||
) {
|
||||
files.forEach(async (file) => {
|
||||
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
|
||||
|
||||
if (savedAsset && savedAsset.type == AssetType.IMAGE) {
|
||||
await this.imageOptimizeService.resizeImage(savedAsset);
|
||||
}
|
||||
});
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
@Get('/file')
|
||||
async serveFile(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Response({ passthrough: true }) res,
|
||||
@Query(ValidationPipe) query: ServeFileDto,
|
||||
): Promise<StreamableFile> {
|
||||
let file = null;
|
||||
const asset = await this.assetService.findOne(authUser, query.did, query.aid);
|
||||
res.set({
|
||||
'Content-Type': asset.mimeType,
|
||||
});
|
||||
|
||||
if (query.isThumb === 'false' || !query.isThumb) {
|
||||
file = createReadStream(asset.originalPath);
|
||||
} else {
|
||||
file = createReadStream(asset.resizePath);
|
||||
}
|
||||
|
||||
return new StreamableFile(file);
|
||||
}
|
||||
|
||||
@Get('/all')
|
||||
async getAllAssets(@GetAuthUser() authUser: AuthUserDto, @Query(ValidationPipe) query: GetAllAssetQueryDto) {
|
||||
return await this.assetService.getAllAssets(authUser, query);
|
||||
}
|
||||
|
||||
@Get('/:deviceId')
|
||||
async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param('deviceId') deviceId: string) {
|
||||
return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
|
||||
}
|
||||
}
|
||||
35
server/src/api-v1/asset/asset.module.ts
Normal file
35
server/src/api-v1/asset/asset.module.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AssetService } from './asset.service';
|
||||
import { AssetController } from './asset.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AssetEntity } from './entities/asset.entity';
|
||||
import { ImageOptimizeModule } from '../../modules/image-optimize/image-optimize.module';
|
||||
import { ImageOptimizeService } from '../../modules/image-optimize/image-optimize.service';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
BullModule.registerQueue({
|
||||
name: 'image',
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
BullModule.registerQueue({
|
||||
name: 'machine-learning',
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
TypeOrmModule.forFeature([AssetEntity]),
|
||||
ImageOptimizeModule,
|
||||
],
|
||||
controllers: [AssetController],
|
||||
providers: [AssetService, ImageOptimizeService],
|
||||
exports: [],
|
||||
})
|
||||
export class AssetModule {}
|
||||
105
server/src/api-v1/asset/asset.service.ts
Normal file
105
server/src/api-v1/asset/asset.service.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||
import { UpdateAssetDto } from './dto/update-asset.dto';
|
||||
import { AssetEntity, AssetType } from './entities/asset.entity';
|
||||
import _ from 'lodash';
|
||||
import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
|
||||
import { GetAllAssetReponseDto } from './dto/get-all-asset-response.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AssetService {
|
||||
constructor(
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
) {}
|
||||
|
||||
public async createUserAsset(authUser: AuthUserDto, assetInfo: CreateAssetDto, path: string, mimeType: string) {
|
||||
const asset = new AssetEntity();
|
||||
asset.deviceAssetId = assetInfo.deviceAssetId;
|
||||
asset.userId = authUser.id;
|
||||
asset.deviceId = assetInfo.deviceId;
|
||||
asset.type = assetInfo.assetType || AssetType.OTHER;
|
||||
asset.originalPath = path;
|
||||
asset.createdAt = assetInfo.createdAt;
|
||||
asset.modifiedAt = assetInfo.modifiedAt;
|
||||
asset.isFavorite = assetInfo.isFavorite;
|
||||
asset.lat = assetInfo.lat;
|
||||
asset.lon = assetInfo.lon;
|
||||
asset.mimeType = mimeType;
|
||||
try {
|
||||
const res = await this.assetRepository.save(asset);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
Logger.error(`Error Create New Asset ${e}`, 'createUserAsset');
|
||||
}
|
||||
}
|
||||
|
||||
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
||||
const rows = await this.assetRepository.find({
|
||||
where: {
|
||||
userId: authUser.id,
|
||||
deviceId: deviceId,
|
||||
},
|
||||
select: ['deviceAssetId'],
|
||||
});
|
||||
|
||||
const res = [];
|
||||
rows.forEach((v) => res.push(v.deviceAssetId));
|
||||
return res;
|
||||
}
|
||||
|
||||
public async getAllAssets(authUser: AuthUserDto, query: GetAllAssetQueryDto): Promise<GetAllAssetReponseDto> {
|
||||
// Each page will take 100 images.
|
||||
|
||||
try {
|
||||
const assets = await this.assetRepository
|
||||
.createQueryBuilder('a')
|
||||
.where('a."userId" = :userId', { userId: authUser.id })
|
||||
.andWhere('a."createdAt" < :lastQueryCreatedAt', {
|
||||
lastQueryCreatedAt: query.nextPageKey || new Date().toISOString(),
|
||||
})
|
||||
.orderBy('a."createdAt"::date', 'DESC')
|
||||
.take(200)
|
||||
.getMany();
|
||||
|
||||
if (assets.length > 0) {
|
||||
const data = _.groupBy(assets, (a) => new Date(a.createdAt).toISOString().slice(0, 10));
|
||||
const formattedData = [];
|
||||
Object.keys(data).forEach((v) => formattedData.push({ date: v, assets: data[v] }));
|
||||
|
||||
const response = new GetAllAssetReponseDto();
|
||||
response.count = assets.length;
|
||||
response.data = formattedData;
|
||||
response.nextPageKey = assets[assets.length - 1].createdAt;
|
||||
|
||||
return response;
|
||||
} else {
|
||||
const response = new GetAllAssetReponseDto();
|
||||
response.count = 0;
|
||||
response.data = [];
|
||||
response.nextPageKey = 'null';
|
||||
|
||||
return response;
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(e, 'getAllAssets');
|
||||
}
|
||||
}
|
||||
|
||||
public async findOne(authUser: AuthUserDto, deviceId: string, assetId: string): Promise<AssetEntity> {
|
||||
const rows = await this.assetRepository.query(
|
||||
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."userId" = $2 AND a."deviceId" = $3',
|
||||
[assetId, authUser.id, deviceId],
|
||||
);
|
||||
|
||||
if (rows.lengh == 0) {
|
||||
throw new BadRequestException('Not Found');
|
||||
}
|
||||
|
||||
return rows[0] as AssetEntity;
|
||||
}
|
||||
}
|
||||
31
server/src/api-v1/asset/dto/create-asset.dto.ts
Normal file
31
server/src/api-v1/asset/dto/create-asset.dto.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { AssetType } from '../entities/asset.entity';
|
||||
|
||||
export class CreateAssetDto {
|
||||
@IsNotEmpty()
|
||||
deviceAssetId: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
deviceId: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
assetType: AssetType;
|
||||
|
||||
@IsNotEmpty()
|
||||
createdAt: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
modifiedAt: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
isFavorite: boolean;
|
||||
|
||||
@IsNotEmpty()
|
||||
fileExtension: string;
|
||||
|
||||
@IsOptional()
|
||||
lat: string;
|
||||
|
||||
@IsOptional()
|
||||
lon: string;
|
||||
}
|
||||
6
server/src/api-v1/asset/dto/get-all-asset-query.dto.ts
Normal file
6
server/src/api-v1/asset/dto/get-all-asset-query.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsNotEmpty, IsOptional } from 'class-validator';
|
||||
|
||||
export class GetAllAssetQueryDto {
|
||||
@IsOptional()
|
||||
nextPageKey: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { AssetEntity } from '../entities/asset.entity';
|
||||
|
||||
export class GetAllAssetReponseDto {
|
||||
data: Array<{ date: string; assets: Array<AssetEntity> }>;
|
||||
count: number;
|
||||
nextPageKey: string;
|
||||
}
|
||||
6
server/src/api-v1/asset/dto/get-asset.dto.ts
Normal file
6
server/src/api-v1/asset/dto/get-asset.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
class GetAssetDto {
|
||||
@IsNotEmpty()
|
||||
deviceId: string;
|
||||
}
|
||||
16
server/src/api-v1/asset/dto/serve-file.dto.ts
Normal file
16
server/src/api-v1/asset/dto/serve-file.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsBooleanString, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
|
||||
export class ServeFileDto {
|
||||
//assetId
|
||||
@IsNotEmpty()
|
||||
aid: string;
|
||||
|
||||
//deviceId
|
||||
@IsNotEmpty()
|
||||
did: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBooleanString()
|
||||
isThumb: string;
|
||||
}
|
||||
4
server/src/api-v1/asset/dto/update-asset.dto.ts
Normal file
4
server/src/api-v1/asset/dto/update-asset.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateAssetDto } from './create-asset.dto';
|
||||
|
||||
export class UpdateAssetDto extends PartialType(CreateAssetDto) {}
|
||||
54
server/src/api-v1/asset/entities/asset.entity.ts
Normal file
54
server/src/api-v1/asset/entities/asset.entity.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
|
||||
@Entity('assets')
|
||||
@Unique(['deviceAssetId', 'userId', 'deviceId'])
|
||||
export class AssetEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
deviceAssetId: string;
|
||||
|
||||
@Column()
|
||||
userId: string;
|
||||
|
||||
@Column()
|
||||
deviceId: string;
|
||||
|
||||
@Column()
|
||||
type: AssetType;
|
||||
|
||||
@Column()
|
||||
originalPath: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
resizePath: string;
|
||||
|
||||
@Column()
|
||||
createdAt: string;
|
||||
|
||||
@Column()
|
||||
modifiedAt: string;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isFavorite: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
lat: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
lon: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
export enum AssetType {
|
||||
IMAGE = 'IMAGE',
|
||||
VIDEO = 'VIDEO',
|
||||
AUDIO = 'AUDIO',
|
||||
OTHER = 'OTHER',
|
||||
}
|
||||
29
server/src/api-v1/auth/auth.controller.ts
Normal file
29
server/src/api-v1/auth/auth.controller.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Body, Controller, Post, UseGuards, ValidationPipe } from '@nestjs/common';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LoginCredentialDto } from './dto/login-credential.dto';
|
||||
import { SignUpDto } from './dto/sign-up.dto';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('/login')
|
||||
async login(@Body(ValidationPipe) loginCredential: LoginCredentialDto) {
|
||||
return await this.authService.login(loginCredential);
|
||||
}
|
||||
|
||||
@Post('/signUp')
|
||||
async signUp(@Body(ValidationPipe) signUpCrendential: SignUpDto) {
|
||||
return await this.authService.signUp(signUpCrendential);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('/validateToken')
|
||||
async validateToken(@GetAuthUser() authUser: AuthUserDto) {
|
||||
return {
|
||||
authStatus: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
16
server/src/api-v1/auth/auth.module.ts
Normal file
16
server/src/api-v1/auth/auth.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UserEntity } from '../user/entities/user.entity';
|
||||
import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
|
||||
import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { jwtConfig } from '../../config/jwt.config';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([UserEntity]), ImmichJwtModule, JwtModule.register(jwtConfig)],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, ImmichJwtService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
84
server/src/api-v1/auth/auth.service.ts
Normal file
84
server/src/api-v1/auth/auth.service.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { BadRequestException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { UserEntity } from '../user/entities/user.entity';
|
||||
import { LoginCredentialDto } from './dto/login-credential.dto';
|
||||
import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
|
||||
import { JwtPayloadDto } from './dto/jwt-payload.dto';
|
||||
import { SignUpDto } from './dto/sign-up.dto';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
@InjectRepository(UserEntity)
|
||||
private userRepository: Repository<UserEntity>,
|
||||
private immichJwtService: ImmichJwtService,
|
||||
) {}
|
||||
|
||||
private async validateUser(loginCredential: LoginCredentialDto): Promise<UserEntity> {
|
||||
const user = await this.userRepository.findOne(
|
||||
{ email: loginCredential.email },
|
||||
{ select: ['id', 'email', 'password', 'salt'] },
|
||||
);
|
||||
|
||||
const isAuthenticated = await this.validatePassword(user.password, loginCredential.password, user.salt);
|
||||
|
||||
if (user && isAuthenticated) {
|
||||
return user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async login(loginCredential: LoginCredentialDto) {
|
||||
const validatedUser = await this.validateUser(loginCredential);
|
||||
|
||||
if (!validatedUser) {
|
||||
throw new BadRequestException('Incorrect email or password');
|
||||
}
|
||||
|
||||
const payload = new JwtPayloadDto(validatedUser.id, validatedUser.email);
|
||||
|
||||
return {
|
||||
accessToken: await this.immichJwtService.generateToken(payload),
|
||||
userId: validatedUser.id,
|
||||
userEmail: validatedUser.email,
|
||||
};
|
||||
}
|
||||
|
||||
public async signUp(signUpCrendential: SignUpDto) {
|
||||
const registerUser = await this.userRepository.findOne({ email: signUpCrendential.email });
|
||||
|
||||
if (registerUser) {
|
||||
throw new BadRequestException('User exist');
|
||||
}
|
||||
|
||||
const newUser = new UserEntity();
|
||||
newUser.email = signUpCrendential.email;
|
||||
newUser.salt = await bcrypt.genSalt();
|
||||
newUser.password = await this.hashPassword(signUpCrendential.password, newUser.salt);
|
||||
|
||||
try {
|
||||
const savedUser = await this.userRepository.save(newUser);
|
||||
|
||||
return {
|
||||
id: savedUser.id,
|
||||
email: savedUser.email,
|
||||
createdAt: savedUser.createdAt,
|
||||
};
|
||||
} catch (e) {
|
||||
Logger.error('e', 'signUp');
|
||||
throw new InternalServerErrorException('Failed to register new user');
|
||||
}
|
||||
}
|
||||
|
||||
private async hashPassword(password: string, salt: string): Promise<string> {
|
||||
return bcrypt.hash(password, salt);
|
||||
}
|
||||
|
||||
private async validatePassword(hasedPassword: string, inputPassword: string, salt: string): Promise<boolean> {
|
||||
const hash = await bcrypt.hash(inputPassword, salt);
|
||||
return hash === hasedPassword;
|
||||
}
|
||||
}
|
||||
9
server/src/api-v1/auth/dto/jwt-payload.dto.ts
Normal file
9
server/src/api-v1/auth/dto/jwt-payload.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class JwtPayloadDto {
|
||||
constructor(userId: string, email: string) {
|
||||
this.userId = userId;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
userId: string;
|
||||
email: string;
|
||||
}
|
||||
9
server/src/api-v1/auth/dto/login-credential.dto.ts
Normal file
9
server/src/api-v1/auth/dto/login-credential.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class LoginCredentialDto {
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
9
server/src/api-v1/auth/dto/sign-up.dto.ts
Normal file
9
server/src/api-v1/auth/dto/sign-up.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class SignUpDto {
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
22
server/src/api-v1/device-info/device-info.controller.ts
Normal file
22
server/src/api-v1/device-info/device-info.controller.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, ValidationPipe } from '@nestjs/common';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { DeviceInfoService } from './device-info.service';
|
||||
import { CreateDeviceInfoDto } from './dto/create-device-info.dto';
|
||||
import { UpdateDeviceInfoDto } from './dto/update-device-info.dto';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('device-info')
|
||||
export class DeviceInfoController {
|
||||
constructor(private readonly deviceInfoService: DeviceInfoService) {}
|
||||
|
||||
@Post()
|
||||
async create(@Body(ValidationPipe) createDeviceInfoDto: CreateDeviceInfoDto, @GetAuthUser() authUser: AuthUserDto) {
|
||||
return await this.deviceInfoService.create(createDeviceInfoDto, authUser);
|
||||
}
|
||||
|
||||
@Patch()
|
||||
async update(@Body(ValidationPipe) updateDeviceInfoDto: UpdateDeviceInfoDto, @GetAuthUser() authUser: AuthUserDto) {
|
||||
return this.deviceInfoService.update(authUser.id, updateDeviceInfoDto);
|
||||
}
|
||||
}
|
||||
12
server/src/api-v1/device-info/device-info.module.ts
Normal file
12
server/src/api-v1/device-info/device-info.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DeviceInfoService } from './device-info.service';
|
||||
import { DeviceInfoController } from './device-info.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DeviceInfoEntity } from './entities/device-info.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([DeviceInfoEntity])],
|
||||
controllers: [DeviceInfoController],
|
||||
providers: [DeviceInfoService],
|
||||
})
|
||||
export class DeviceInfoModule {}
|
||||
63
server/src/api-v1/device-info/device-info.service.ts
Normal file
63
server/src/api-v1/device-info/device-info.service.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { BadRequestException, HttpCode, Injectable, Logger, Res } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { CreateDeviceInfoDto } from './dto/create-device-info.dto';
|
||||
import { UpdateDeviceInfoDto } from './dto/update-device-info.dto';
|
||||
import { DeviceInfoEntity } from './entities/device-info.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceInfoService {
|
||||
constructor(
|
||||
@InjectRepository(DeviceInfoEntity)
|
||||
private deviceRepository: Repository<DeviceInfoEntity>,
|
||||
) {}
|
||||
|
||||
async create(createDeviceInfoDto: CreateDeviceInfoDto, authUser: AuthUserDto) {
|
||||
const res = await this.deviceRepository.findOne({
|
||||
deviceId: createDeviceInfoDto.deviceId,
|
||||
userId: authUser.id,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
Logger.log('Device Info Exist', 'createDeviceInfo');
|
||||
return res;
|
||||
}
|
||||
|
||||
const deviceInfo = new DeviceInfoEntity();
|
||||
deviceInfo.deviceId = createDeviceInfoDto.deviceId;
|
||||
deviceInfo.deviceType = createDeviceInfoDto.deviceType;
|
||||
deviceInfo.userId = authUser.id;
|
||||
|
||||
try {
|
||||
return await this.deviceRepository.save(deviceInfo);
|
||||
} catch (e) {
|
||||
Logger.error('Error creating new device info', 'createDeviceInfo');
|
||||
}
|
||||
}
|
||||
|
||||
async update(userId: string, updateDeviceInfoDto: UpdateDeviceInfoDto) {
|
||||
const deviceInfo = await this.deviceRepository.findOne({
|
||||
where: { deviceId: updateDeviceInfoDto.deviceId, userId: userId },
|
||||
});
|
||||
|
||||
if (!deviceInfo) {
|
||||
throw new BadRequestException('Device Not Found');
|
||||
}
|
||||
|
||||
const res = await this.deviceRepository.update(
|
||||
{
|
||||
id: deviceInfo.id,
|
||||
},
|
||||
updateDeviceInfoDto,
|
||||
);
|
||||
|
||||
if (res.affected == 1) {
|
||||
return await this.deviceRepository.findOne({
|
||||
where: { deviceId: updateDeviceInfoDto.deviceId, userId: userId },
|
||||
});
|
||||
} else {
|
||||
throw new BadRequestException('Bad Request');
|
||||
}
|
||||
}
|
||||
}
|
||||
13
server/src/api-v1/device-info/dto/create-device-info.dto.ts
Normal file
13
server/src/api-v1/device-info/dto/create-device-info.dto.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { DeviceType } from '../entities/device-info.entity';
|
||||
|
||||
export class CreateDeviceInfoDto {
|
||||
@IsNotEmpty()
|
||||
deviceId: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
deviceType: DeviceType;
|
||||
|
||||
@IsOptional()
|
||||
isAutoBackup: boolean;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { IsOptional } from 'class-validator';
|
||||
import { DeviceType } from '../entities/device-info.entity';
|
||||
import { CreateDeviceInfoDto } from './create-device-info.dto';
|
||||
|
||||
export class UpdateDeviceInfoDto extends PartialType(CreateDeviceInfoDto) {}
|
||||
32
server/src/api-v1/device-info/entities/device-info.entity.ts
Normal file
32
server/src/api-v1/device-info/entities/device-info.entity.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
|
||||
@Entity('device_info')
|
||||
@Unique(['userId', 'deviceId'])
|
||||
export class DeviceInfoEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
userId: string;
|
||||
|
||||
@Column()
|
||||
deviceId: string;
|
||||
|
||||
@Column()
|
||||
deviceType: DeviceType;
|
||||
|
||||
@Column({ nullable: true })
|
||||
notificationToken: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: string;
|
||||
|
||||
@Column({ type: 'bool', default: false })
|
||||
isAutoBackup: boolean;
|
||||
}
|
||||
|
||||
export enum DeviceType {
|
||||
IOS = 'IOS',
|
||||
ANDROID = 'ANDROID',
|
||||
WEB = 'WEB',
|
||||
}
|
||||
9
server/src/api-v1/server-info/dto/server-info.dto.ts
Normal file
9
server/src/api-v1/server-info/dto/server-info.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class ServerInfoDto {
|
||||
diskSize: String;
|
||||
diskUse: String;
|
||||
diskAvailable: String;
|
||||
diskSizeRaw: number;
|
||||
diskUseRaw: number;
|
||||
diskAvailableRaw: number;
|
||||
diskUsagePercentage: number;
|
||||
}
|
||||
19
server/src/api-v1/server-info/server-info.controller.ts
Normal file
19
server/src/api-v1/server-info/server-info.controller.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { ServerInfoService } from './server-info.service';
|
||||
|
||||
@Controller('server-info')
|
||||
export class ServerInfoController {
|
||||
constructor(private readonly serverInfoService: ServerInfoService) {}
|
||||
|
||||
@Get()
|
||||
async getServerInfo() {
|
||||
return await this.serverInfoService.getServerInfo();
|
||||
}
|
||||
|
||||
@Get('/ping')
|
||||
async getServerPulse() {
|
||||
return {
|
||||
res: 'pong',
|
||||
};
|
||||
}
|
||||
}
|
||||
9
server/src/api-v1/server-info/server-info.module.ts
Normal file
9
server/src/api-v1/server-info/server-info.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ServerInfoService } from './server-info.service';
|
||||
import { ServerInfoController } from './server-info.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ServerInfoController],
|
||||
providers: [ServerInfoService]
|
||||
})
|
||||
export class ServerInfoModule {}
|
||||
54
server/src/api-v1/server-info/server-info.service.ts
Normal file
54
server/src/api-v1/server-info/server-info.service.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import systemInformation from 'systeminformation';
|
||||
import { ServerInfoDto } from './dto/server-info.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ServerInfoService {
|
||||
constructor() {}
|
||||
async getServerInfo() {
|
||||
const res = await systemInformation.fsSize();
|
||||
|
||||
const size = res[0].size;
|
||||
const used = res[0].used;
|
||||
const available = res[0].available;
|
||||
const percentageUsage = res[0].use;
|
||||
|
||||
const serverInfo = new ServerInfoDto();
|
||||
serverInfo.diskAvailable = this.getHumanReadableString(available);
|
||||
serverInfo.diskSize = this.getHumanReadableString(size);
|
||||
serverInfo.diskUse = this.getHumanReadableString(used);
|
||||
serverInfo.diskAvailableRaw = available;
|
||||
serverInfo.diskSizeRaw = size;
|
||||
serverInfo.diskUseRaw = used;
|
||||
serverInfo.diskUsagePercentage = percentageUsage;
|
||||
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
private getHumanReadableString(sizeInByte: number) {
|
||||
const pepibyte = 1.126 * Math.pow(10, 15);
|
||||
const tebibyte = 1.1 * Math.pow(10, 12);
|
||||
const gibibyte = 1.074 * Math.pow(10, 9);
|
||||
const mebibyte = 1.049 * Math.pow(10, 6);
|
||||
const kibibyte = 1024;
|
||||
// Pebibyte
|
||||
if (sizeInByte >= pepibyte) {
|
||||
// Pe
|
||||
return `${(sizeInByte / pepibyte).toFixed(1)}PB`;
|
||||
} else if (tebibyte <= sizeInByte && sizeInByte < pepibyte) {
|
||||
// Te
|
||||
return `${(sizeInByte / tebibyte).toFixed(1)}TB`;
|
||||
} else if (gibibyte <= sizeInByte && sizeInByte < tebibyte) {
|
||||
// Gi
|
||||
return `${(sizeInByte / gibibyte).toFixed(1)}GB`;
|
||||
} else if (mebibyte <= sizeInByte && sizeInByte < gibibyte) {
|
||||
// Mega
|
||||
return `${(sizeInByte / mebibyte).toFixed(1)}MB`;
|
||||
} else if (kibibyte <= sizeInByte && sizeInByte < mebibyte) {
|
||||
// Kibi
|
||||
return `${(sizeInByte / kibibyte).toFixed(1)}KB`;
|
||||
} else {
|
||||
return `${sizeInByte}B`;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
server/src/api-v1/user/dto/create-user.dto.ts
Normal file
1
server/src/api-v1/user/dto/create-user.dto.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class CreateUserDto {}
|
||||
4
server/src/api-v1/user/dto/update-user.dto.ts
Normal file
4
server/src/api-v1/user/dto/update-user.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateUserDto } from './create-user.dto';
|
||||
|
||||
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
||||
19
server/src/api-v1/user/entities/user.entity.ts
Normal file
19
server/src/api-v1/user/entities/user.entity.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('users')
|
||||
export class UserEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
email: string;
|
||||
|
||||
@Column({ select: false })
|
||||
password: string;
|
||||
|
||||
@Column({ select: false })
|
||||
salt: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: string;
|
||||
}
|
||||
34
server/src/api-v1/user/user.controller.ts
Normal file
34
server/src/api-v1/user/user.controller.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
|
||||
@Controller('user')
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createUserDto: CreateUserDto) {
|
||||
return this.userService.create(createUserDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.userService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.userService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
|
||||
return this.userService.update(+id, updateUserDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.userService.remove(+id);
|
||||
}
|
||||
}
|
||||
12
server/src/api-v1/user/user.module.ts
Normal file
12
server/src/api-v1/user/user.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { UserController } from './user.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UserEntity } from './entities/user.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([UserEntity])],
|
||||
controllers: [UserController],
|
||||
providers: [UserService],
|
||||
})
|
||||
export class UserModule {}
|
||||
41
server/src/api-v1/user/user.service.ts
Normal file
41
server/src/api-v1/user/user.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { UserEntity } from './entities/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
@InjectRepository(UserEntity)
|
||||
private userRepository: Repository<UserEntity>,
|
||||
) {}
|
||||
|
||||
create(createUserDto: CreateUserDto) {
|
||||
return 'This action adds a new user';
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
try {
|
||||
return 'welcome';
|
||||
// return await this.userRepository.find();
|
||||
// return await this.userRepository.query('select * from users');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
// return 'helloworld';
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} user`;
|
||||
}
|
||||
|
||||
update(id: number, updateUserDto: UpdateUserDto) {
|
||||
return `This action updates a #${id} user`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} user`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user