mirror of
https://github.com/KevinMidboe/immich.git
synced 2026-06-16 09:23:05 +00:00
Feature - Add upload functionality on Web (#231)
* Added file selector * Extract metadata to upload files to the web * Added request for uploading * Generate jpeg/Webp thumbnail for asset uploaded without thumbnail data * Added generating thumbnail for video and WebSocket broadcast after thumbnail is generated * Added video length extraction * Added Uploading Panel * Added upload progress store and styling the uploaded asset * Added condition to only show upload panel when there is upload in progress * Remove asset from the upload list after successfully uploading * Added WebSocket to listen to upload event on the web * Added mechanism to check for existing assets before uploading on the web * Added test workflow * Update readme
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
Delete,
|
||||
Logger,
|
||||
Patch,
|
||||
HttpCode,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { AssetService } from './asset.service';
|
||||
@@ -76,6 +77,10 @@ export class AssetController {
|
||||
{ asset: assetWithThumbnail, fileName: file.originalname, fileSize: file.size, hasThumbnail: true },
|
||||
{ jobId: savedAsset.id },
|
||||
);
|
||||
|
||||
this.wsCommunicateionGateway.server
|
||||
.to(savedAsset.userId)
|
||||
.emit('on_upload_success', JSON.stringify(assetWithThumbnail));
|
||||
} else {
|
||||
await this.assetUploadedQueue.add(
|
||||
'asset-uploaded',
|
||||
@@ -83,8 +88,6 @@ export class AssetController {
|
||||
{ jobId: savedAsset.id },
|
||||
);
|
||||
}
|
||||
|
||||
this.wsCommunicateionGateway.server.to(savedAsset.userId).emit('on_upload_success', JSON.stringify(savedAsset));
|
||||
} catch (e) {
|
||||
Logger.error(`Error receiving upload file ${e}`);
|
||||
}
|
||||
@@ -171,4 +174,20 @@ export class AssetController {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check duplicated asset before uploading - for Web upload used
|
||||
*/
|
||||
@Post('/check')
|
||||
@HttpCode(200)
|
||||
async checkDuplicateAsset(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) { deviceAssetId }: { deviceAssetId: string },
|
||||
) {
|
||||
const res = await this.assetService.checkDuplicatedAsset(authUser, deviceAssetId);
|
||||
|
||||
return {
|
||||
isExist: res,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@ import { CommunicationModule } from '../communication/communication.module';
|
||||
],
|
||||
controllers: [AssetController],
|
||||
providers: [AssetService, BackgroundTaskService],
|
||||
exports: [],
|
||||
exports: [AssetService],
|
||||
})
|
||||
export class AssetModule {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BadRequestException, Injectable, InternalServerErrorException, Logger, StreamableFile } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||
@@ -72,6 +72,7 @@ export class AssetService {
|
||||
return await this.assetRepository.find({
|
||||
where: {
|
||||
userId: authUser.id,
|
||||
resizePath: Not(IsNull()),
|
||||
},
|
||||
relations: ['exifInfo'],
|
||||
order: {
|
||||
@@ -381,4 +382,15 @@ export class AssetService {
|
||||
[authUser.id],
|
||||
);
|
||||
}
|
||||
|
||||
async checkDuplicatedAsset(authUser: AuthUserDto, deviceAssetId: string) {
|
||||
const res = await this.assetRepository.findOne({
|
||||
where: {
|
||||
deviceAssetId,
|
||||
userId: authUser.id,
|
||||
},
|
||||
});
|
||||
|
||||
return res ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { UserEntity } from '@app/database/entities/user.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import { query } from 'express';
|
||||
|
||||
@WebSocketGateway()
|
||||
@WebSocketGateway({ cors: true })
|
||||
export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
constructor(
|
||||
private immichJwtService: ImmichJwtService,
|
||||
@@ -21,27 +22,33 @@ export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisco
|
||||
handleDisconnect(client: Socket) {
|
||||
client.leave(client.nsp.name);
|
||||
|
||||
Logger.log(`Client ${client.id} disconnected`);
|
||||
Logger.log(`Client ${client.id} disconnected from Websocket`, 'WebsocketConnectionEvent');
|
||||
}
|
||||
|
||||
async handleConnection(client: Socket, ...args: any[]) {
|
||||
Logger.log(`New websocket connection: ${client.id}`, 'NewWebSocketConnection');
|
||||
const accessToken = client.handshake.headers.authorization.split(' ')[1];
|
||||
const res = await this.immichJwtService.validateToken(accessToken);
|
||||
try {
|
||||
Logger.log(`New websocket connection: ${client.id}`, 'WebsocketConnectionEvent');
|
||||
|
||||
if (!res.status) {
|
||||
client.emit('error', 'unauthorized');
|
||||
client.disconnect();
|
||||
return;
|
||||
const accessToken = client.handshake.headers.authorization.split(' ')[1];
|
||||
|
||||
const res = await this.immichJwtService.validateToken(accessToken);
|
||||
|
||||
if (!res.status) {
|
||||
client.emit('error', 'unauthorized');
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await this.userRepository.findOne({ where: { id: res.userId } });
|
||||
if (!user) {
|
||||
client.emit('error', 'unauthorized');
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
client.join(user.id);
|
||||
} catch (e) {
|
||||
// Logger.error(`Error establish websocket conneciton ${e}`, 'HandleWebscoketConnection');
|
||||
}
|
||||
|
||||
const user = await this.userRepository.findOne({ where: { id: res.userId } });
|
||||
if (!user) {
|
||||
client.emit('error', 'unauthorized');
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
client.join(user.id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user