mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02:00
Refactor web to use OpenAPI SDK (#326)
* Refactor main index page * Refactor admin page * Refactor Auth endpoint * Refactor directory to prep for monorepo * Fixed refactoring path * Resolved file path in vite * Refactor photo index page * Refactor thumbnail * Fixed test * Refactor Video Viewer component * Refactor download file * Refactor navigation bar * Refactor upload file check * Simplify Upload Asset signature * PR feedback
This commit is contained in:
parent
7f236c5b18
commit
9a6dfacf9b
@ -8,7 +8,6 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
Param,
|
Param,
|
||||||
ValidationPipe,
|
ValidationPipe,
|
||||||
StreamableFile,
|
|
||||||
Query,
|
Query,
|
||||||
Response,
|
Response,
|
||||||
Headers,
|
Headers,
|
||||||
@ -16,13 +15,13 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
UploadedFile,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||||
import { AssetService } from './asset.service';
|
import { AssetService } from './asset.service';
|
||||||
import { FileFieldsInterceptor } from '@nestjs/platform-express';
|
import { FileFieldsInterceptor, FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { assetUploadOption } from '../../config/asset-upload.config';
|
import { assetUploadOption } from '../../config/asset-upload.config';
|
||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
|
||||||
import { ServeFileDto } from './dto/serve-file.dto';
|
import { ServeFileDto } from './dto/serve-file.dto';
|
||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
import { Response as Res } from 'express';
|
import { Response as Res } from 'express';
|
||||||
@ -36,10 +35,14 @@ import { IAssetUploadedJob } from '@app/job/index';
|
|||||||
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
||||||
import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant';
|
import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant';
|
||||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||||
import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||||
import { AssetResponseDto } from './response-dto/asset-response.dto';
|
import { AssetResponseDto } from './response-dto/asset-response.dto';
|
||||||
|
import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
||||||
|
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
||||||
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
|
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ -56,46 +59,43 @@ export class AssetController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('upload')
|
@Post('upload')
|
||||||
@UseInterceptors(
|
@UseInterceptors(FileInterceptor('assetData', assetUploadOption))
|
||||||
FileFieldsInterceptor(
|
@ApiConsumes('multipart/form-data')
|
||||||
[
|
@ApiBody({
|
||||||
{ name: 'assetData', maxCount: 1 },
|
description: 'Asset Upload Information',
|
||||||
{ name: 'thumbnailData', maxCount: 1 },
|
type: AssetFileUploadDto,
|
||||||
],
|
})
|
||||||
assetUploadOption,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@UploadedFiles() uploadFiles: { assetData: Express.Multer.File[] },
|
@UploadedFile() file: Express.Multer.File,
|
||||||
@Body(ValidationPipe) assetInfo: CreateAssetDto,
|
@Body(ValidationPipe) assetInfo: CreateAssetDto,
|
||||||
): Promise<'ok' | undefined> {
|
): Promise<AssetFileUploadResponseDto> {
|
||||||
for (const file of uploadFiles.assetData) {
|
try {
|
||||||
try {
|
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
|
||||||
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
|
|
||||||
|
|
||||||
if (savedAsset) {
|
if (!savedAsset) {
|
||||||
await this.assetUploadedQueue.add(
|
throw new BadRequestException('Asset not created');
|
||||||
assetUploadedProcessorName,
|
|
||||||
{ asset: savedAsset, fileName: file.originalname, fileSize: file.size },
|
|
||||||
{ jobId: savedAsset.id },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Logger.error(`Error uploading file ${e}`);
|
|
||||||
throw new BadRequestException(`Error uploading file`, `${e}`);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return 'ok';
|
await this.assetUploadedQueue.add(
|
||||||
|
assetUploadedProcessorName,
|
||||||
|
{ asset: savedAsset, fileName: file.originalname, fileSize: file.size },
|
||||||
|
{ jobId: savedAsset.id },
|
||||||
|
);
|
||||||
|
|
||||||
|
return new AssetFileUploadResponseDto(savedAsset.id);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error(`Error uploading file ${e}`);
|
||||||
|
throw new BadRequestException(`Error uploading file`, `${e}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/download')
|
@Get('/download')
|
||||||
async downloadFile(
|
async downloadFile(
|
||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Query(ValidationPipe) query: ServeFileDto,
|
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
||||||
): Promise<StreamableFile> {
|
): Promise<any> {
|
||||||
return this.assetService.downloadFile(query, res);
|
return this.assetService.downloadFile(query, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,14 +104,14 @@ export class AssetController {
|
|||||||
@Headers() headers: Record<string, string>,
|
@Headers() headers: Record<string, string>,
|
||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Query(ValidationPipe) query: ServeFileDto,
|
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
||||||
): Promise<StreamableFile | undefined> {
|
): Promise<any> {
|
||||||
return this.assetService.serveFile(authUser, query, res, headers);
|
return this.assetService.serveFile(authUser, query, res, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/thumbnail/:assetId')
|
@Get('/thumbnail/:assetId')
|
||||||
async getAssetThumbnail(@Param('assetId') assetId: string) {
|
async getAssetThumbnail(@Param('assetId') assetId: string): Promise<any> {
|
||||||
return await this.assetService.getAssetThumbnail(assetId);
|
return this.assetService.getAssetThumbnail(assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/allObjects')
|
@Get('/allObjects')
|
||||||
@ -195,11 +195,9 @@ export class AssetController {
|
|||||||
async checkDuplicateAsset(
|
async checkDuplicateAsset(
|
||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Body(ValidationPipe) checkDuplicateAssetDto: CheckDuplicateAssetDto,
|
@Body(ValidationPipe) checkDuplicateAssetDto: CheckDuplicateAssetDto,
|
||||||
) {
|
): Promise<CheckDuplicateAssetResponseDto> {
|
||||||
const res = await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto);
|
const res = await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto);
|
||||||
|
|
||||||
return {
|
return new CheckDuplicateAssetResponseDto(res);
|
||||||
isExist: res,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { IsNull, Not, Repository } from 'typeorm';
|
import { IsNull, Not, Repository } from 'typeorm';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
|
||||||
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
||||||
import { ServeFileDto } from './dto/serve-file.dto';
|
import { ServeFileDto } from './dto/serve-file.dto';
|
||||||
@ -21,6 +20,8 @@ import fs from 'fs/promises';
|
|||||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
|
import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
|
||||||
|
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
||||||
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
|
|
||||||
const fileInfo = promisify(stat);
|
const fileInfo = promisify(stat);
|
||||||
|
|
||||||
@ -132,8 +133,10 @@ export class AssetService {
|
|||||||
let fileReadStream = null;
|
let fileReadStream = null;
|
||||||
const asset = await this.findAssetOfDevice(query.did, query.aid);
|
const asset = await this.findAssetOfDevice(query.did, query.aid);
|
||||||
|
|
||||||
if (query.isThumb === 'false' || !query.isThumb) {
|
// Download Video
|
||||||
|
if (asset.type === AssetType.VIDEO) {
|
||||||
const { size } = await fileInfo(asset.originalPath);
|
const { size } = await fileInfo(asset.originalPath);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': asset.mimeType,
|
'Content-Type': asset.mimeType,
|
||||||
'Content-Length': size,
|
'Content-Length': size,
|
||||||
@ -142,22 +145,43 @@ export class AssetService {
|
|||||||
await fs.access(asset.originalPath, constants.R_OK | constants.W_OK);
|
await fs.access(asset.originalPath, constants.R_OK | constants.W_OK);
|
||||||
fileReadStream = createReadStream(asset.originalPath);
|
fileReadStream = createReadStream(asset.originalPath);
|
||||||
} else {
|
} else {
|
||||||
if (!asset.resizePath) {
|
// Download Image
|
||||||
throw new NotFoundException('resizePath not set');
|
if (!query.isThumb) {
|
||||||
}
|
/**
|
||||||
const { size } = await fileInfo(asset.resizePath);
|
* Download Image Original File
|
||||||
res.set({
|
*/
|
||||||
'Content-Type': 'image/jpeg',
|
const { size } = await fileInfo(asset.originalPath);
|
||||||
'Content-Length': size,
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
res.set({
|
||||||
fileReadStream = createReadStream(asset.resizePath);
|
'Content-Type': asset.mimeType,
|
||||||
|
'Content-Length': size,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.access(asset.originalPath, constants.R_OK | constants.W_OK);
|
||||||
|
fileReadStream = createReadStream(asset.originalPath);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Download Image Resize File
|
||||||
|
*/
|
||||||
|
if (!asset.resizePath) {
|
||||||
|
throw new NotFoundException('resizePath not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { size } = await fileInfo(asset.resizePath);
|
||||||
|
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'image/jpeg',
|
||||||
|
'Content-Length': size,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
||||||
|
fileReadStream = createReadStream(asset.resizePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new StreamableFile(fileReadStream);
|
return new StreamableFile(fileReadStream);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.error(`Error download asset`, 'downloadFile');
|
Logger.error(`Error download asset ${e}`, 'downloadFile');
|
||||||
throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile');
|
throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,7 +201,7 @@ export class AssetService {
|
|||||||
fileReadStream = createReadStream(asset.webpPath);
|
fileReadStream = createReadStream(asset.webpPath);
|
||||||
} else {
|
} else {
|
||||||
if (!asset.resizePath) {
|
if (!asset.resizePath) {
|
||||||
return new NotFoundException('resizePath not set');
|
throw new NotFoundException('resizePath not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
|
||||||
@ -203,7 +227,7 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle Sending Images
|
// Handle Sending Images
|
||||||
if (asset.type == AssetType.IMAGE || query.isThumb == 'true') {
|
if (asset.type == AssetType.IMAGE) {
|
||||||
try {
|
try {
|
||||||
/**
|
/**
|
||||||
* Serve file viewer on the web
|
* Serve file viewer on the web
|
||||||
@ -225,7 +249,7 @@ export class AssetService {
|
|||||||
/**
|
/**
|
||||||
* Serve thumbnail image for both web and mobile app
|
* Serve thumbnail image for both web and mobile app
|
||||||
*/
|
*/
|
||||||
if (query.isThumb === 'false' || !query.isThumb) {
|
if (!query.isThumb) {
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': asset.mimeType,
|
'Content-Type': asset.mimeType,
|
||||||
});
|
});
|
||||||
@ -262,7 +286,7 @@ export class AssetService {
|
|||||||
`Cannot read thumbnail file for asset ${asset.id} - contact your administrator`,
|
`Cannot read thumbnail file for asset ${asset.id} - contact your administrator`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (asset.type == AssetType.VIDEO) {
|
} else {
|
||||||
try {
|
try {
|
||||||
// Handle Video
|
// Handle Video
|
||||||
let videoPath = asset.originalPath;
|
let videoPath = asset.originalPath;
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { AssetType } from '@app/database/entities/asset.entity';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
|
||||||
|
import { CreateAssetDto } from './create-asset.dto';
|
||||||
|
|
||||||
|
export class AssetFileUploadDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ApiProperty({ type: 'string', format: 'binary' })
|
||||||
|
assetData!: any;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsBooleanString, IsNotEmpty, IsOptional } from 'class-validator';
|
import { Transform, Type } from 'class-transformer';
|
||||||
|
import { IsBoolean, IsBooleanString, IsNotEmpty, IsOptional } from 'class-validator';
|
||||||
|
|
||||||
export class ServeFileDto {
|
export class ServeFileDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@ -11,10 +12,28 @@ export class ServeFileDto {
|
|||||||
did!: string;
|
did!: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBooleanString()
|
@IsBoolean()
|
||||||
isThumb?: string;
|
@Transform(({ value }) => {
|
||||||
|
if (value == 'true') {
|
||||||
|
return true;
|
||||||
|
} else if (value == 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
@ApiProperty({ type: Boolean, title: 'Is serve thumbnail (resize) file' })
|
||||||
|
isThumb?: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBooleanString()
|
@IsBoolean()
|
||||||
isWeb?: string;
|
@Transform(({ value }) => {
|
||||||
|
if (value == 'true') {
|
||||||
|
return true;
|
||||||
|
} else if (value == 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
@ApiProperty({ type: Boolean, title: 'Is request made from web' })
|
||||||
|
isWeb?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
export class AssetFileUploadResponseDto {
|
||||||
|
constructor(id: string) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
id: string;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
export class CheckDuplicateAssetResponseDto {
|
||||||
|
constructor(isExist: boolean) {
|
||||||
|
this.isExist = isExist;
|
||||||
|
}
|
||||||
|
isExist: boolean;
|
||||||
|
}
|
@ -12,6 +12,7 @@ import {
|
|||||||
UploadedFile,
|
UploadedFile,
|
||||||
Response,
|
Response,
|
||||||
StreamableFile,
|
StreamableFile,
|
||||||
|
ParseBoolPipe,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||||
@ -24,7 +25,6 @@ import { profileImageUploadOption } from '../../config/profile-image-upload.conf
|
|||||||
import { Response as Res } from 'express';
|
import { Response as Res } from 'express';
|
||||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
import { UserResponseDto } from './response-dto/user-response.dto';
|
import { UserResponseDto } from './response-dto/user-response.dto';
|
||||||
import { UserEntity } from '@app/database/entities/user.entity';
|
|
||||||
import { UserCountResponseDto } from './response-dto/user-count-response.dto';
|
import { UserCountResponseDto } from './response-dto/user-count-response.dto';
|
||||||
import { CreateProfileImageDto } from './dto/create-profile-image.dto';
|
import { CreateProfileImageDto } from './dto/create-profile-image.dto';
|
||||||
import { CreateProfileImageResponseDto } from './response-dto/create-profile-image-response.dto';
|
import { CreateProfileImageResponseDto } from './response-dto/create-profile-image-response.dto';
|
||||||
@ -37,7 +37,10 @@ export class UserController {
|
|||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Get()
|
@Get()
|
||||||
async getAllUsers(@GetAuthUser() authUser: AuthUserDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
|
async getAllUsers(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
|
@Query('isAll', ParseBoolPipe) isAll: boolean,
|
||||||
|
): Promise<UserResponseDto[]> {
|
||||||
return await this.userService.getAllUsers(authUser, isAll);
|
return await this.userService.getAllUsers(authUser, isAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,8 +60,8 @@ export class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('/count')
|
@Get('/count')
|
||||||
async getUserCount(@Query('isAdmin') isAdmin: boolean): Promise<UserCountResponseDto> {
|
async getUserCount(): Promise<UserCountResponseDto> {
|
||||||
return await this.userService.getUserCount(isAdmin);
|
return await this.userService.getUserCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ -84,10 +87,7 @@ export class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('/profile-image/:userId')
|
@Get('/profile-image/:userId')
|
||||||
async getProfileImage(
|
async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise<any> {
|
||||||
@Param('userId') userId: string,
|
|
||||||
@Response({ passthrough: true }) res: Res,
|
|
||||||
): Promise<StreamableFile | undefined> {
|
|
||||||
return this.userService.getUserProfileImage(userId, res);
|
return this.userService.getUserProfileImage(userId, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ export class UserService {
|
|||||||
async getAllUsers(authUser: AuthUserDto, isAll: boolean): Promise<UserResponseDto[]> {
|
async getAllUsers(authUser: AuthUserDto, isAll: boolean): Promise<UserResponseDto[]> {
|
||||||
if (isAll) {
|
if (isAll) {
|
||||||
const allUsers = await this.userRepository.find();
|
const allUsers = await this.userRepository.find();
|
||||||
|
|
||||||
return allUsers.map(mapUser);
|
return allUsers.map(mapUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,14 +53,8 @@ export class UserService {
|
|||||||
return mapUser(user);
|
return mapUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserCount(isAdmin: boolean): Promise<UserCountResponseDto> {
|
async getUserCount(): Promise<UserCountResponseDto> {
|
||||||
let users;
|
const users = await this.userRepository.find();
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
users = await this.userRepository.find({ where: { isAdmin: true } });
|
|
||||||
} else {
|
|
||||||
users = await this.userRepository.find();
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapUserCountResponse(users.length);
|
return mapUserCountResponse(users.length);
|
||||||
}
|
}
|
||||||
@ -157,8 +150,7 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!user.profileImagePath) {
|
if (!user.profileImagePath) {
|
||||||
res.status(404).send('User does not have a profile image');
|
throw new NotFoundException('User does not have a profile image');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
@ -167,7 +159,7 @@ export class UserService {
|
|||||||
const fileStream = createReadStream(user.profileImagePath);
|
const fileStream = createReadStream(user.profileImagePath);
|
||||||
return new StreamableFile(fileStream);
|
return new StreamableFile(fileStream);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(404).send('User does not have a profile image');
|
throw new NotFoundException('User does not have a profile image');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import { AppController } from './app.controller';
|
|||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
|
import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
|
||||||
import { DatabaseModule } from '@app/database';
|
import { DatabaseModule } from '@app/database';
|
||||||
|
import { AppLoggerMiddleware } from './middlewares/app-logger.middleware';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -86,7 +86,7 @@ describe('User', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fetches the user collection excluding the auth user', async () => {
|
it('fetches the user collection excluding the auth user', async () => {
|
||||||
const { status, body } = await request(app.getHttpServer()).get('/user');
|
const { status, body } = await request(app.getHttpServer()).get('/user?isAll=false');
|
||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
expect(body).toHaveLength(2);
|
expect(body).toHaveLength(2);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
|
File diff suppressed because one or more lines are too long
2
server/package-lock.json
generated
2
server/package-lock.json
generated
@ -53,7 +53,7 @@
|
|||||||
"@nestjs/cli": "^8.2.8",
|
"@nestjs/cli": "^8.2.8",
|
||||||
"@nestjs/schematics": "^8.0.11",
|
"@nestjs/schematics": "^8.0.11",
|
||||||
"@nestjs/testing": "^8.4.7",
|
"@nestjs/testing": "^8.4.7",
|
||||||
"@openapitools/openapi-generator-cli": "^2.5.1",
|
"@openapitools/openapi-generator-cli": "2.5.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.7",
|
"@types/bull": "^3.15.7",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./apps/immich/test/jest-e2e.json",
|
"test:e2e": "jest --config ./apps/immich/test/jest-e2e.json",
|
||||||
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
|
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
|
||||||
"api:generate-typescript": "rm -rf ../web/src/lib/open-api && npx openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/lib/open-api"
|
"api:generate-typescript": "rm -rf ../web/src/api/open-api && npx openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/api/open-api"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-sdk": "^0.13.3",
|
"@mapbox/mapbox-sdk": "^0.13.3",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { serverEndpoint } from '$lib/constants';
|
||||||
import {
|
import {
|
||||||
AlbumApi,
|
AlbumApi,
|
||||||
AssetApi,
|
AssetApi,
|
||||||
@ -6,7 +7,7 @@ import {
|
|||||||
DeviceInfoApi,
|
DeviceInfoApi,
|
||||||
ServerInfoApi,
|
ServerInfoApi,
|
||||||
UserApi,
|
UserApi,
|
||||||
} from '../open-api';
|
} from './open-api';
|
||||||
|
|
||||||
class ImmichApi {
|
class ImmichApi {
|
||||||
public userApi: UserApi;
|
public userApi: UserApi;
|
||||||
@ -15,7 +16,7 @@ class ImmichApi {
|
|||||||
public authenticationApi: AuthenticationApi;
|
public authenticationApi: AuthenticationApi;
|
||||||
public deviceInfoApi: DeviceInfoApi;
|
public deviceInfoApi: DeviceInfoApi;
|
||||||
public serverInfoApi: ServerInfoApi;
|
public serverInfoApi: ServerInfoApi;
|
||||||
private config = new Configuration();
|
private config = new Configuration({ basePath: serverEndpoint });
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.userApi = new UserApi(this.config);
|
this.userApi = new UserApi(this.config);
|
||||||
@ -31,4 +32,4 @@ class ImmichApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const immichApi = new ImmichApi();
|
export const api = new ImmichApi();
|
2
web/src/api/index.ts
Normal file
2
web/src/api/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './open-api';
|
||||||
|
export * from './api';
|
@ -139,6 +139,19 @@ export interface AlbumResponseDto {
|
|||||||
*/
|
*/
|
||||||
'assets': Array<AssetResponseDto>;
|
'assets': Array<AssetResponseDto>;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface AssetFileUploadResponseDto
|
||||||
|
*/
|
||||||
|
export interface AssetFileUploadResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof AssetFileUploadResponseDto
|
||||||
|
*/
|
||||||
|
'id': string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -271,6 +284,19 @@ export interface CheckDuplicateAssetDto {
|
|||||||
*/
|
*/
|
||||||
'deviceId': string;
|
'deviceId': string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface CheckDuplicateAssetResponseDto
|
||||||
|
*/
|
||||||
|
export interface CheckDuplicateAssetResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof CheckDuplicateAssetResponseDto
|
||||||
|
*/
|
||||||
|
'isExist': boolean;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -296,71 +322,6 @@ export interface CreateAlbumDto {
|
|||||||
*/
|
*/
|
||||||
'assetIds'?: Array<string>;
|
'assetIds'?: Array<string>;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface CreateAssetDto
|
|
||||||
*/
|
|
||||||
export interface CreateAssetDto {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'deviceAssetId': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'deviceId': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'assetType': CreateAssetDtoAssetTypeEnum;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'createdAt': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'modifiedAt': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'isFavorite': boolean;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'fileExtension': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof CreateAssetDto
|
|
||||||
*/
|
|
||||||
'duration'?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreateAssetDtoAssetTypeEnum = {
|
|
||||||
Image: 'IMAGE',
|
|
||||||
Video: 'VIDEO',
|
|
||||||
Audio: 'AUDIO',
|
|
||||||
Other: 'OTHER'
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type CreateAssetDtoAssetTypeEnum = typeof CreateAssetDtoAssetTypeEnum[keyof typeof CreateAssetDtoAssetTypeEnum];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -1879,12 +1840,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
downloadFile: async (aid: string, did: string, isThumb?: string, isWeb?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
downloadFile: async (aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'aid' is not null or undefined
|
// verify required parameter 'aid' is not null or undefined
|
||||||
assertParamExists('downloadFile', 'aid', aid)
|
assertParamExists('downloadFile', 'aid', aid)
|
||||||
// verify required parameter 'did' is not null or undefined
|
// verify required parameter 'did' is not null or undefined
|
||||||
@ -2221,12 +2182,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
serveFile: async (aid: string, did: string, isThumb?: string, isWeb?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
serveFile: async (aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'aid' is not null or undefined
|
// verify required parameter 'aid' is not null or undefined
|
||||||
assertParamExists('serveFile', 'aid', aid)
|
assertParamExists('serveFile', 'aid', aid)
|
||||||
// verify required parameter 'did' is not null or undefined
|
// verify required parameter 'did' is not null or undefined
|
||||||
@ -2276,13 +2237,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {CreateAssetDto} createAssetDto
|
* @param {any} assetData
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
uploadFile: async (createAssetDto: CreateAssetDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
uploadFile: async (assetData: any, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'createAssetDto' is not null or undefined
|
// verify required parameter 'assetData' is not null or undefined
|
||||||
assertParamExists('uploadFile', 'createAssetDto', createAssetDto)
|
assertParamExists('uploadFile', 'assetData', assetData)
|
||||||
const localVarPath = `/asset/upload`;
|
const localVarPath = `/asset/upload`;
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
@ -2294,19 +2255,24 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||||
const localVarHeaderParameter = {} as any;
|
const localVarHeaderParameter = {} as any;
|
||||||
const localVarQueryParameter = {} as any;
|
const localVarQueryParameter = {} as any;
|
||||||
|
const localVarFormParams = new ((configuration && configuration.formDataCtor) || FormData)();
|
||||||
|
|
||||||
// authentication bearer required
|
// authentication bearer required
|
||||||
// http bearer authentication required
|
// http bearer authentication required
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
if (assetData !== undefined) {
|
||||||
|
localVarFormParams.append('assetData', assetData as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
localVarHeaderParameter['Content-Type'] = 'multipart/form-data';
|
||||||
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
localVarRequestOptions.data = serializeDataIfNeeded(createAssetDto, localVarRequestOptions, configuration)
|
localVarRequestOptions.data = localVarFormParams;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: toPathString(localVarUrlObj),
|
url: toPathString(localVarUrlObj),
|
||||||
@ -2330,7 +2296,7 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
async checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<CheckDuplicateAssetResponseDto>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.checkDuplicateAsset(checkDuplicateAssetDto, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.checkDuplicateAsset(checkDuplicateAssetDto, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
@ -2348,12 +2314,12 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
async downloadFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(aid, did, isThumb, isWeb, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(aid, did, isThumb, isWeb, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
@ -2440,23 +2406,23 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
async serveFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.serveFile(aid, did, isThumb, isWeb, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.serveFile(aid, did, isThumb, isWeb, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {CreateAssetDto} createAssetDto
|
* @param {any} assetData
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async uploadFile(createAssetDto: CreateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {
|
async uploadFile(assetData: any, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetFileUploadResponseDto>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(createAssetDto, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetData, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -2476,7 +2442,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: any): AxiosPromise<void> {
|
checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: any): AxiosPromise<CheckDuplicateAssetResponseDto> {
|
||||||
return localVarFp.checkDuplicateAsset(checkDuplicateAssetDto, options).then((request) => request(axios, basePath));
|
return localVarFp.checkDuplicateAsset(checkDuplicateAssetDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -2492,12 +2458,12 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: any): AxiosPromise<void> {
|
downloadFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: any): AxiosPromise<object> {
|
||||||
return localVarFp.downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath));
|
return localVarFp.downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -2575,22 +2541,22 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: any): AxiosPromise<void> {
|
serveFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: any): AxiosPromise<object> {
|
||||||
return localVarFp.serveFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath));
|
return localVarFp.serveFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {CreateAssetDto} createAssetDto
|
* @param {any} assetData
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
uploadFile(createAssetDto: CreateAssetDto, options?: any): AxiosPromise<string> {
|
uploadFile(assetData: any, options?: any): AxiosPromise<AssetFileUploadResponseDto> {
|
||||||
return localVarFp.uploadFile(createAssetDto, options).then((request) => request(axios, basePath));
|
return localVarFp.uploadFile(assetData, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -2629,13 +2595,13 @@ export class AssetApi extends BaseAPI {
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof AssetApi
|
* @memberof AssetApi
|
||||||
*/
|
*/
|
||||||
public downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig) {
|
public downloadFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig) {
|
||||||
return AssetApiFp(this.configuration).downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2730,25 +2696,25 @@ export class AssetApi extends BaseAPI {
|
|||||||
*
|
*
|
||||||
* @param {string} aid
|
* @param {string} aid
|
||||||
* @param {string} did
|
* @param {string} did
|
||||||
* @param {string} [isThumb]
|
* @param {boolean} [isThumb]
|
||||||
* @param {string} [isWeb]
|
* @param {boolean} [isWeb]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof AssetApi
|
* @memberof AssetApi
|
||||||
*/
|
*/
|
||||||
public serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig) {
|
public serveFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig) {
|
||||||
return AssetApiFp(this.configuration).serveFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).serveFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {CreateAssetDto} createAssetDto
|
* @param {any} assetData
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof AssetApi
|
* @memberof AssetApi
|
||||||
*/
|
*/
|
||||||
public uploadFile(createAssetDto: CreateAssetDto, options?: AxiosRequestConfig) {
|
public uploadFile(assetData: any, options?: AxiosRequestConfig) {
|
||||||
return AssetApiFp(this.configuration).uploadFile(createAssetDto, options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).uploadFile(assetData, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3560,13 +3526,10 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} isAdmin
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getUserCount: async (isAdmin: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
getUserCount: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'isAdmin' is not null or undefined
|
|
||||||
assertParamExists('getUserCount', 'isAdmin', isAdmin)
|
|
||||||
const localVarPath = `/user/count`;
|
const localVarPath = `/user/count`;
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
@ -3579,10 +3542,6 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
|
|||||||
const localVarHeaderParameter = {} as any;
|
const localVarHeaderParameter = {} as any;
|
||||||
const localVarQueryParameter = {} as any;
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
if (isAdmin !== undefined) {
|
|
||||||
localVarQueryParameter['isAdmin'] = isAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
@ -3688,18 +3647,17 @@ export const UserApiFp = function(configuration?: Configuration) {
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async getProfileImage(userId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
async getProfileImage(userId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getProfileImage(userId, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getProfileImage(userId, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} isAdmin
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async getUserCount(isAdmin: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserCountResponseDto>> {
|
async getUserCount(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserCountResponseDto>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getUserCount(isAdmin, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getUserCount(options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -3763,17 +3721,16 @@ export const UserApiFactory = function (configuration?: Configuration, basePath?
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getProfileImage(userId: string, options?: any): AxiosPromise<void> {
|
getProfileImage(userId: string, options?: any): AxiosPromise<object> {
|
||||||
return localVarFp.getProfileImage(userId, options).then((request) => request(axios, basePath));
|
return localVarFp.getProfileImage(userId, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} isAdmin
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getUserCount(isAdmin: boolean, options?: any): AxiosPromise<UserCountResponseDto> {
|
getUserCount(options?: any): AxiosPromise<UserCountResponseDto> {
|
||||||
return localVarFp.getUserCount(isAdmin, options).then((request) => request(axios, basePath));
|
return localVarFp.getUserCount(options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -3850,13 +3807,12 @@ export class UserApi extends BaseAPI {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} isAdmin
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof UserApi
|
* @memberof UserApi
|
||||||
*/
|
*/
|
||||||
public getUserCount(isAdmin: boolean, options?: AxiosRequestConfig) {
|
public getUserCount(options?: AxiosRequestConfig) {
|
||||||
return UserApiFp(this.configuration).getUserCount(isAdmin, options).then((request) => request(this.axios, this.basePath));
|
return UserApiFp(this.configuration).getUserCount(options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -5,29 +5,30 @@
|
|||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.17.0
|
* The version of the OpenAPI document: 1.17.0
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
* https://openapi-generator.tech
|
* https://openapi-generator.tech
|
||||||
* Do not edit the class manually.
|
* Do not edit the class manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Configuration } from './configuration';
|
|
||||||
|
import { Configuration } from "./configuration";
|
||||||
// Some imports not used depending on template conditions
|
// Some imports not used depending on template conditions
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
|
import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
export const BASE_PATH = '/api'.replace(/\/+$/, '');
|
export const BASE_PATH = "/api".replace(/\/+$/, "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const COLLECTION_FORMATS = {
|
export const COLLECTION_FORMATS = {
|
||||||
csv: ',',
|
csv: ",",
|
||||||
ssv: ' ',
|
ssv: " ",
|
||||||
tsv: '\t',
|
tsv: "\t",
|
||||||
pipes: '|',
|
pipes: "|",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,8 +37,8 @@ export const COLLECTION_FORMATS = {
|
|||||||
* @interface RequestArgs
|
* @interface RequestArgs
|
||||||
*/
|
*/
|
||||||
export interface RequestArgs {
|
export interface RequestArgs {
|
||||||
url: string;
|
url: string;
|
||||||
options: AxiosRequestConfig;
|
options: AxiosRequestConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,19 +47,15 @@ export interface RequestArgs {
|
|||||||
* @class BaseAPI
|
* @class BaseAPI
|
||||||
*/
|
*/
|
||||||
export class BaseAPI {
|
export class BaseAPI {
|
||||||
protected configuration: Configuration | undefined;
|
protected configuration: Configuration | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
|
||||||
configuration?: Configuration,
|
if (configuration) {
|
||||||
protected basePath: string = BASE_PATH,
|
this.configuration = configuration;
|
||||||
protected axios: AxiosInstance = globalAxios,
|
this.basePath = configuration.basePath || this.basePath;
|
||||||
) {
|
}
|
||||||
if (configuration) {
|
}
|
||||||
this.configuration = configuration;
|
};
|
||||||
this.basePath = configuration.basePath || this.basePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -67,8 +64,8 @@ export class BaseAPI {
|
|||||||
* @extends {Error}
|
* @extends {Error}
|
||||||
*/
|
*/
|
||||||
export class RequiredError extends Error {
|
export class RequiredError extends Error {
|
||||||
name: 'RequiredError' = 'RequiredError';
|
name: "RequiredError" = "RequiredError";
|
||||||
constructor(public field: string, msg?: string) {
|
constructor(public field: string, msg?: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import type { ExternalFetch, GetSession, Handle } from '@sveltejs/kit';
|
import type { GetSession, Handle } from '@sveltejs/kit';
|
||||||
import * as cookie from 'cookie';
|
import * as cookie from 'cookie';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
import { api } from '@api';
|
||||||
import { session } from '$app/stores';
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
|
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
|
||||||
@ -13,14 +13,10 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
try {
|
try {
|
||||||
const { email, isAdmin, firstName, lastName, id, accessToken } = JSON.parse(cookies.session);
|
const { email, isAdmin, firstName, lastName, id, accessToken } = JSON.parse(cookies.session);
|
||||||
|
|
||||||
const res = await fetch(`${serverEndpoint}/auth/validateToken`, {
|
api.setAccessToken(accessToken);
|
||||||
method: 'POST',
|
const { status } = await api.authenticationApi.validateAccessToken();
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 201) {
|
if (status === 201) {
|
||||||
event.locals.user = {
|
event.locals.user = {
|
||||||
id,
|
id,
|
||||||
accessToken,
|
accessToken,
|
||||||
@ -35,7 +31,12 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error parsing session', error);
|
if (error instanceof AxiosError) {
|
||||||
|
console.log('Error validating token');
|
||||||
|
return await resolve(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Error parsing session');
|
||||||
return await resolve(event);
|
return await resolve(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { UserResponseDto } from '@api';
|
||||||
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
|
import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
|
||||||
export let usersOnServer: Array<any>;
|
export let allUsers: Array<UserResponseDto>;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
</script>
|
</script>
|
||||||
@ -18,7 +20,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border">
|
<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border">
|
||||||
{#each usersOnServer as user, i}
|
{#each allUsers as user, i}
|
||||||
<tr
|
<tr
|
||||||
class={`text-center flex place-items-center w-full border-b h-[80px] ${
|
class={`text-center flex place-items-center w-full border-b h-[80px] ${
|
||||||
i % 2 == 0 ? 'bg-gray-100' : 'bg-immich-bg'
|
i % 2 == 0 ? 'bg-gray-100' : 'bg-immich-bg'
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||||
import { fly, slide } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import AsserViewerNavBar from './asser-viewer-nav-bar.svelte';
|
import AsserViewerNavBar from './asser-viewer-nav-bar.svelte';
|
||||||
import { flattenAssetGroupByDate } from '$lib/stores/assets';
|
import { flattenAssetGroupByDate } from '$lib/stores/assets';
|
||||||
import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
|
import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
|
||||||
import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
|
import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
|
||||||
import { AssetType, type ImmichAsset, type ImmichExif } from '../../models/immich-asset';
|
import { AssetType } from '../../models/immich-asset';
|
||||||
import PhotoViewer from './photo-viewer.svelte';
|
import PhotoViewer from './photo-viewer.svelte';
|
||||||
import DetailPanel from './detail-panel.svelte';
|
import DetailPanel from './detail-panel.svelte';
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import { serverEndpoint } from '../../constants';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { downloadAssets } from '$lib/stores/download';
|
import { downloadAssets } from '$lib/stores/download';
|
||||||
import VideoViewer from './video-viewer.svelte';
|
import VideoViewer from './video-viewer.svelte';
|
||||||
|
import { api, AssetResponseDto } from '@api';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let selectedAsset: ImmichAsset;
|
export let selectedAsset: AssetResponseDto;
|
||||||
|
|
||||||
export let selectedIndex: number;
|
export let selectedIndex: number;
|
||||||
|
|
||||||
@ -99,8 +98,6 @@
|
|||||||
|
|
||||||
const downloadFile = async () => {
|
const downloadFile = async () => {
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
const url = `${serverEndpoint}/asset/download?aid=${selectedAsset.deviceAssetId}&did=${selectedAsset.deviceId}&isThumb=false`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const imageName = selectedAsset.exifInfo?.imageName ? selectedAsset.exifInfo?.imageName : selectedAsset.id;
|
const imageName = selectedAsset.exifInfo?.imageName ? selectedAsset.exifInfo?.imageName : selectedAsset.id;
|
||||||
const imageExtension = selectedAsset.originalPath.split('.')[1];
|
const imageExtension = selectedAsset.originalPath.split('.')[1];
|
||||||
@ -112,24 +109,31 @@
|
|||||||
}
|
}
|
||||||
$downloadAssets[imageFileName] = 0;
|
$downloadAssets[imageFileName] = 0;
|
||||||
|
|
||||||
const res = await axios.get(url, {
|
const { data, status } = await api.assetApi.downloadFile(
|
||||||
responseType: 'blob',
|
selectedAsset.deviceAssetId,
|
||||||
headers: {
|
selectedAsset.deviceId,
|
||||||
Authorization: 'Bearer ' + $session.user.accessToken,
|
false,
|
||||||
},
|
false,
|
||||||
onDownloadProgress: (progressEvent) => {
|
{
|
||||||
if (progressEvent.lengthComputable) {
|
responseType: 'blob',
|
||||||
const total = progressEvent.total;
|
onDownloadProgress: (progressEvent) => {
|
||||||
const current = progressEvent.loaded;
|
if (progressEvent.lengthComputable) {
|
||||||
let percentCompleted = Math.floor((current / total) * 100);
|
const total = progressEvent.total;
|
||||||
|
const current = progressEvent.loaded;
|
||||||
|
let percentCompleted = Math.floor((current / total) * 100);
|
||||||
|
|
||||||
$downloadAssets[imageFileName] = percentCompleted;
|
$downloadAssets[imageFileName] = percentCompleted;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (!(data instanceof Blob)) {
|
||||||
const fileUrl = URL.createObjectURL(new Blob([res.data]));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 200) {
|
||||||
|
const fileUrl = URL.createObjectURL(data);
|
||||||
const anchor = document.createElement('a');
|
const anchor = document.createElement('a');
|
||||||
anchor.href = fileUrl;
|
anchor.href = fileUrl;
|
||||||
anchor.download = imageFileName;
|
anchor.download = imageFileName;
|
||||||
|
@ -5,24 +5,23 @@
|
|||||||
import CameraIris from 'svelte-material-icons/CameraIris.svelte';
|
import CameraIris from 'svelte-material-icons/CameraIris.svelte';
|
||||||
import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
|
import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import type { ImmichAsset } from '../../models/immich-asset';
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { round } from 'lodash';
|
import { AssetResponseDto } from '@api';
|
||||||
|
|
||||||
// Map Property
|
// Map Property
|
||||||
let map: any;
|
let map: any;
|
||||||
let leaflet: any;
|
let leaflet: any;
|
||||||
let marker: any;
|
let marker: any;
|
||||||
|
|
||||||
export let asset: ImmichAsset;
|
export let asset: AssetResponseDto;
|
||||||
$: if (asset.exifInfo) {
|
$: if (asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null) {
|
||||||
drawMap(asset.exifInfo.latitude, asset.exifInfo.longitude);
|
drawMap(asset.exifInfo.latitude, asset.exifInfo.longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
if (asset.exifInfo) {
|
if (asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null) {
|
||||||
await drawMap(asset.exifInfo.latitude, asset.exifInfo.longitude);
|
await drawMap(asset.exifInfo.latitude, asset.exifInfo.longitude);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AssetType, type ImmichAsset } from '../../models/immich-asset';
|
import { AssetType } from '../../models/immich-asset';
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||||
import { fade, fly, slide } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
import { serverEndpoint } from '../../constants';
|
|
||||||
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
||||||
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
||||||
import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
|
import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
|
||||||
import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
|
import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
|
||||||
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
||||||
|
import { api, AssetResponseDto } from '@api';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let asset: ImmichAsset;
|
export let asset: AssetResponseDto;
|
||||||
export let groupIndex: number;
|
export let groupIndex: number;
|
||||||
|
|
||||||
let imageData: string;
|
let imageData: string;
|
||||||
@ -29,33 +29,28 @@
|
|||||||
|
|
||||||
const loadImageData = async () => {
|
const loadImageData = async () => {
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
const res = await fetch(serverEndpoint + '/asset/thumbnail/' + asset.id, {
|
const { data } = await api.assetApi.getAssetThumbnail(asset.id, { responseType: 'blob' });
|
||||||
method: 'GET',
|
if (data instanceof Blob) {
|
||||||
headers: {
|
imageData = URL.createObjectURL(data);
|
||||||
Authorization: 'bearer ' + $session.user.accessToken,
|
return imageData;
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
imageData = URL.createObjectURL(await res.blob());
|
|
||||||
|
|
||||||
return imageData;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadVideoData = async () => {
|
const loadVideoData = async () => {
|
||||||
isThumbnailVideoPlaying = false;
|
isThumbnailVideoPlaying = false;
|
||||||
const videoUrl = `/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isWeb=true`;
|
|
||||||
|
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(serverEndpoint + videoUrl, {
|
const { data } = await api.assetApi.serveFile(asset.deviceAssetId, asset.deviceId, false, true, {
|
||||||
method: 'GET',
|
responseType: 'blob',
|
||||||
headers: {
|
|
||||||
Authorization: 'bearer ' + $session.user.accessToken,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
videoData = URL.createObjectURL(await res.blob());
|
if (!(data instanceof Blob)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoData = URL.createObjectURL(data);
|
||||||
|
|
||||||
videoPlayerNode.src = videoData;
|
videoPlayerNode.src = videoData;
|
||||||
// videoPlayerNode.src = videoData + '#t=0,5';
|
// videoPlayerNode.src = videoData + '#t=0,5';
|
||||||
|
@ -1,43 +1,39 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
import type { ImmichAsset, ImmichExif } from '$lib/models/immich-asset';
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
||||||
|
import { api, AssetResponseDto } from '@api';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let deviceId: string;
|
export let deviceId: string;
|
||||||
|
|
||||||
let assetInfo: ImmichAsset;
|
let assetInfo: AssetResponseDto;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
const res = await fetch(serverEndpoint + '/asset/assetById/' + assetId, {
|
const { data } = await api.assetApi.getAssetById(assetId);
|
||||||
headers: {
|
assetInfo = data;
|
||||||
Authorization: 'bearer ' + $session.user.accessToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
assetInfo = await res.json();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadAssetData = async () => {
|
const loadAssetData = async () => {
|
||||||
const assetUrl = `/asset/file?aid=${assetInfo.deviceAssetId}&did=${deviceId}&isWeb=true`;
|
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
const res = await fetch(serverEndpoint + assetUrl, {
|
try {
|
||||||
method: 'GET',
|
const { data } = await api.assetApi.serveFile(assetInfo.deviceAssetId, deviceId, false, true, {
|
||||||
headers: {
|
responseType: 'blob',
|
||||||
Authorization: 'bearer ' + $session.user.accessToken,
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const assetData = URL.createObjectURL(await res.blob());
|
if (!(data instanceof Blob)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return assetData;
|
const assetData = URL.createObjectURL(data);
|
||||||
|
return assetData;
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
import type { ImmichAsset, ImmichExif } from '$lib/models/immich-asset';
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
||||||
|
import { api, AssetResponseDto } from '@api';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
|
|
||||||
let asset: ImmichAsset;
|
let asset: AssetResponseDto;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
@ -18,12 +17,9 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
const res = await fetch(serverEndpoint + '/asset/assetById/' + assetId, {
|
const { data: assetInfo } = await api.assetApi.getAssetById(assetId);
|
||||||
headers: {
|
|
||||||
Authorization: 'bearer ' + $session.user.accessToken,
|
asset = assetInfo;
|
||||||
},
|
|
||||||
});
|
|
||||||
asset = await res.json();
|
|
||||||
|
|
||||||
await loadVideoData();
|
await loadVideoData();
|
||||||
}
|
}
|
||||||
@ -31,17 +27,18 @@
|
|||||||
|
|
||||||
const loadVideoData = async () => {
|
const loadVideoData = async () => {
|
||||||
isVideoLoading = true;
|
isVideoLoading = true;
|
||||||
const videoUrl = `/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isWeb=true`;
|
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(serverEndpoint + videoUrl, {
|
const { data } = await api.assetApi.serveFile(asset.deviceAssetId, asset.deviceId, false, true, {
|
||||||
method: 'GET',
|
responseType: 'blob',
|
||||||
headers: {
|
|
||||||
Authorization: 'bearer ' + $session.user.accessToken,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const videoData = URL.createObjectURL(await res.blob());
|
if (!(data instanceof Blob)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoData = URL.createObjectURL(data);
|
||||||
videoPlayerNode.src = videoData;
|
videoPlayerNode.src = videoData;
|
||||||
|
|
||||||
videoPlayerNode.load();
|
videoPlayerNode.load();
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { session } from '$app/stores';
|
import { sendUpdateForm } from '$lib/auth-api';
|
||||||
|
|
||||||
import { sendRegistrationForm, sendUpdateForm } from '$lib/auth-api';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import type { ImmichUser } from '../../models/immich-user';
|
import type { ImmichUser } from '../../models/immich-user';
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { session } from '$app/stores';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import type { ImmichUser } from '$lib/models/immich-user';
|
import type { ImmichUser } from '$lib/models/immich-user';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import { fade, fly, slide } from 'svelte/transition';
|
import { fade, fly, slide } from 'svelte/transition';
|
||||||
import { postRequest } from '../../api';
|
|
||||||
import { serverEndpoint } from '../../constants';
|
import { serverEndpoint } from '../../constants';
|
||||||
import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
|
import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
|
||||||
import { clickOutside } from './click-outside';
|
import { clickOutside } from './click-outside';
|
||||||
|
import { api } from '@api';
|
||||||
|
|
||||||
export let user: ImmichUser;
|
export let user: ImmichUser;
|
||||||
|
|
||||||
@ -16,12 +17,22 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let shouldShowAccountInfoPanel = false;
|
let shouldShowAccountInfoPanel = false;
|
||||||
onMount(async () => {
|
|
||||||
const res = await fetch(`${serverEndpoint}/user/profile-image/${user.id}`, { method: 'GET' });
|
|
||||||
|
|
||||||
if (res.status == 200) shouldShowProfileImage = true;
|
onMount(() => {
|
||||||
|
getUserProfileImage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getUserProfileImage = async () => {
|
||||||
|
if ($session.user) {
|
||||||
|
try {
|
||||||
|
await api.userApi.getProfileImage(user.id);
|
||||||
|
shouldShowProfileImage = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.log('User does not have a profile image');
|
||||||
|
shouldShowProfileImage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
const getFirstLetter = (text?: string) => {
|
const getFirstLetter = (text?: string) => {
|
||||||
return text?.charAt(0).toUpperCase();
|
return text?.charAt(0).toUpperCase();
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getRequest } from '$lib/api';
|
import { getRequest } from '$lib/utils/api-helper';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
import { serverEndpoint } from '$lib/constants';
|
||||||
import Cloud from 'svelte-material-icons/Cloud.svelte';
|
import Cloud from 'svelte-material-icons/Cloud.svelte';
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { writable, derived } from 'svelte/store';
|
import { writable, derived } from 'svelte/store';
|
||||||
import { getRequest } from '$lib/api';
|
|
||||||
import type { ImmichAsset } from '$lib/models/immich-asset';
|
|
||||||
import lodash from 'lodash-es';
|
import lodash from 'lodash-es';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
export const assets = writable<ImmichAsset[]>([]);
|
import { api, AssetResponseDto } from '@api';
|
||||||
|
export const assets = writable<AssetResponseDto[]>([]);
|
||||||
|
|
||||||
export const assetsGroupByDate = derived(assets, ($assets) => {
|
export const assetsGroupByDate = derived(assets, ($assets) => {
|
||||||
try {
|
try {
|
||||||
@ -23,6 +22,6 @@ export const flattenAssetGroupByDate = derived(assetsGroupByDate, ($assetsGroupB
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getAssetsInfo = async (accessToken: string) => {
|
export const getAssetsInfo = async (accessToken: string) => {
|
||||||
const res = await getRequest('asset', accessToken);
|
const { data } = await api.assetApi.getAllAssets();
|
||||||
assets.set(res);
|
assets.set(data);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { serverEndpoint } from './constants';
|
import { serverEndpoint } from '../constants';
|
||||||
|
|
||||||
type ISend = {
|
type ISend = {
|
||||||
method: string;
|
method: string;
|
@ -1,7 +1,9 @@
|
|||||||
|
/* @vite-ignore */
|
||||||
import * as exifr from 'exifr';
|
import * as exifr from 'exifr';
|
||||||
import { serverEndpoint } from '../constants';
|
import { serverEndpoint } from '../constants';
|
||||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||||
import type { UploadAsset } from '../models/upload-asset';
|
import type { UploadAsset } from '../models/upload-asset';
|
||||||
|
import { api } from '@api';
|
||||||
|
|
||||||
export async function fileUploader(asset: File, accessToken: string) {
|
export async function fileUploader(asset: File, accessToken: string) {
|
||||||
const assetType = asset.type.split('/')[0].toUpperCase();
|
const assetType = asset.type.split('/')[0].toUpperCase();
|
||||||
@ -51,19 +53,14 @@ export async function fileUploader(asset: File, accessToken: string) {
|
|||||||
formData.append('assetData', asset);
|
formData.append('assetData', asset);
|
||||||
|
|
||||||
// Check if asset upload on server before performing upload
|
// Check if asset upload on server before performing upload
|
||||||
const res = await fetch(serverEndpoint + '/asset/check', {
|
|
||||||
method: 'POST',
|
const { data, status } = await api.assetApi.checkDuplicateAsset({
|
||||||
body: JSON.stringify({ deviceAssetId, deviceId: 'WEB' }),
|
deviceAssetId: String(deviceAssetId),
|
||||||
headers: {
|
deviceId: 'WEB',
|
||||||
Authorization: 'Bearer ' + accessToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (status === 200) {
|
||||||
const { isExist } = await res.json();
|
if (data.isExist) {
|
||||||
|
|
||||||
if (isExist) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
import { checkAppVersion } from '$lib/utils/check-app-version';
|
import { checkAppVersion } from '$lib/utils/check-app-version';
|
||||||
import { browser } from '$app/env';
|
|
||||||
|
|
||||||
export const load: Load = async ({ url }) => {
|
export const load: Load = async ({ url, session }) => {
|
||||||
if (browser) {
|
if (session.user) {
|
||||||
const { shouldShowAnnouncement, localVersion, remoteVersion } = await checkAppVersion();
|
api.setAccessToken(session.user.accessToken);
|
||||||
|
|
||||||
return { props: { url, shouldShowAnnouncement, localVersion, remoteVersion } };
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
props: { url },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: { url },
|
||||||
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -24,11 +21,21 @@
|
|||||||
import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
|
import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
|
||||||
import AnnouncementBox from '$lib/components/shared/announcement-box.svelte';
|
import AnnouncementBox from '$lib/components/shared/announcement-box.svelte';
|
||||||
import UploadPanel from '$lib/components/shared/upload-panel.svelte';
|
import UploadPanel from '$lib/components/shared/upload-panel.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { api } from '@api';
|
||||||
|
|
||||||
export let url: string;
|
export let url: string;
|
||||||
export let shouldShowAnnouncement: boolean;
|
let shouldShowAnnouncement: boolean;
|
||||||
export let localVersion: string;
|
let localVersion: string;
|
||||||
export let remoteVersion: string;
|
let remoteVersion: string;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const res = await checkAppVersion();
|
||||||
|
|
||||||
|
shouldShowAnnouncement = res.shouldShowAnnouncement;
|
||||||
|
localVersion = res.localVersion ?? 'unknown';
|
||||||
|
remoteVersion = res.remoteVersion ?? 'unknown';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
@ -1,44 +1,34 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
import { api } from '@api';
|
||||||
|
|
||||||
export const post: RequestHandler = async ({ request, locals }) => {
|
export const post: RequestHandler = async ({ request }) => {
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
|
|
||||||
const email = form.get('email')
|
const email = form.get('email');
|
||||||
const password = form.get('password')
|
const password = form.get('password');
|
||||||
const firstName = form.get('firstName')
|
const firstName = form.get('firstName');
|
||||||
const lastName = form.get('lastName')
|
const lastName = form.get('lastName');
|
||||||
|
|
||||||
const payload = {
|
const { status } = await api.userApi.createUser({
|
||||||
email,
|
email: String(email),
|
||||||
password,
|
password: String(password),
|
||||||
firstName,
|
firstName: String(firstName),
|
||||||
lastName,
|
lastName: String(lastName),
|
||||||
}
|
});
|
||||||
|
|
||||||
const res = await fetch(`${serverEndpoint}/user`, {
|
if (status === 201) {
|
||||||
method: 'POST',
|
return {
|
||||||
headers: {
|
status: 201,
|
||||||
'Content-Type': 'application/json',
|
body: {
|
||||||
'Authorization': `Bearer ${locals.user?.accessToken}`
|
success: 'Succesfully create user account',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
};
|
||||||
})
|
} else {
|
||||||
|
return {
|
||||||
if (res.status === 201) {
|
status: 400,
|
||||||
return {
|
body: {
|
||||||
status: 201,
|
error: 'Error create user account',
|
||||||
body: {
|
},
|
||||||
success: 'Succesfully create user account'
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
error: await res.json()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
import { getRequest } from '$lib/api';
|
import { api, UserResponseDto } from '@api';
|
||||||
|
|
||||||
export const load: Load = async ({ session, fetch }) => {
|
export const load: Load = async ({ session }) => {
|
||||||
if (!session.user) {
|
if (!session.user) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@ -10,13 +10,13 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersOnServer = await getRequest('user', session.user.accessToken);
|
const { data } = await api.userApi.getAllUsers(false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
props: {
|
props: {
|
||||||
user: session.user,
|
user: session.user,
|
||||||
usersOnServer,
|
allUsers: data,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { session } from '$app/stores';
|
|
||||||
|
|
||||||
import type { ImmichUser } from '$lib/models/immich-user';
|
import type { ImmichUser } from '$lib/models/immich-user';
|
||||||
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
||||||
@ -34,12 +33,12 @@
|
|||||||
import UserManagement from '$lib/components/admin/user-management.svelte';
|
import UserManagement from '$lib/components/admin/user-management.svelte';
|
||||||
import FullScreenModal from '$lib/components/shared/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared/full-screen-modal.svelte';
|
||||||
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
|
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
|
||||||
import StatusBox from '../../lib/components/shared/status-box.svelte';
|
import StatusBox from '$lib/components/shared/status-box.svelte';
|
||||||
|
|
||||||
let selectedAction: AdminSideBarSelection;
|
let selectedAction: AdminSideBarSelection;
|
||||||
|
|
||||||
export let user: ImmichUser;
|
export let user: ImmichUser;
|
||||||
export let usersOnServer: Array<ImmichUser>;
|
export let allUsers: UserResponseDto[];
|
||||||
|
|
||||||
let shouldShowCreateUserForm: boolean;
|
let shouldShowCreateUserForm: boolean;
|
||||||
|
|
||||||
@ -52,9 +51,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onUserCreated = async () => {
|
const onUserCreated = async () => {
|
||||||
if ($session.user) {
|
const { data } = await api.userApi.getAllUsers(false);
|
||||||
usersOnServer = await getRequest('user', $session.user.accessToken);
|
allUsers = data;
|
||||||
}
|
|
||||||
|
|
||||||
shouldShowCreateUserForm = false;
|
shouldShowCreateUserForm = false;
|
||||||
};
|
};
|
||||||
@ -97,7 +95,7 @@
|
|||||||
<section id="setting-content" class="relative pt-[85px] flex place-content-center">
|
<section id="setting-content" class="relative pt-[85px] flex place-content-center">
|
||||||
<section class="w-[800px] pt-4">
|
<section class="w-[800px] pt-4">
|
||||||
{#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
{#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
||||||
<UserManagement {usersOnServer} on:createUser={() => (shouldShowCreateUserForm = true)} />
|
<UserManagement {allUsers} on:createUser={() => (shouldShowCreateUserForm = true)} />
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
import type { ImmichUser } from '$lib/models/immich-user';
|
|
||||||
|
|
||||||
export const load: Load = async ({ session }) => {
|
export const load: Load = async ({ session }) => {
|
||||||
if (!session.user) {
|
if (!session.user) {
|
||||||
@ -13,14 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(serverEndpoint + '/user/me', {
|
const { data: userInfo } = await api.userApi.getMyUserInfo();
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + session.user.accessToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const userInfo: ImmichUser = await res.json();
|
|
||||||
|
|
||||||
if (userInfo.shouldChangePassword) {
|
if (userInfo.shouldChangePassword) {
|
||||||
return {
|
return {
|
||||||
@ -47,15 +39,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { session } from '$app/stores';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import ChangePasswordForm from '../../../lib/components/forms/change-password-form.svelte';
|
|
||||||
import { serverEndpoint } from '../../../lib/constants';
|
|
||||||
|
|
||||||
export let user: ImmichUser;
|
import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
|
||||||
|
import { api, UserResponseDto } from '@api';
|
||||||
|
|
||||||
|
export let user: UserResponseDto;
|
||||||
|
|
||||||
const onSuccessHandler = async () => {
|
const onSuccessHandler = async () => {
|
||||||
|
/** Svelte route fetch */
|
||||||
const res = await fetch('/auth/logout', { method: 'POST' });
|
const res = await fetch('/auth/logout', { method: 'POST' });
|
||||||
|
|
||||||
if (res.status == 200 && res.statusText == 'OK') {
|
if (res.status == 200 && res.statusText == 'OK') {
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
import { api } from '@api';
|
||||||
|
|
||||||
export const post: RequestHandler = async ({ request, locals }) => {
|
export const post: RequestHandler = async ({ request, locals }) => {
|
||||||
const form = await request.formData();
|
if (!locals.user) {
|
||||||
|
return {
|
||||||
|
status: 401,
|
||||||
|
body: {
|
||||||
|
error: 'Unauthorized',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = await request.formData();
|
||||||
const password = form.get('password');
|
const password = form.get('password');
|
||||||
|
|
||||||
const payload = {
|
const { status } = await api.userApi.updateUser({
|
||||||
id: locals.user?.id,
|
id: locals.user.id,
|
||||||
password,
|
password: String(password),
|
||||||
shouldChangePassword: false,
|
shouldChangePassword: false,
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(`${serverEndpoint}/user`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${locals.user?.accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (status === 200) {
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
@ -32,7 +31,7 @@ export const post: RequestHandler = async ({ request, locals }) => {
|
|||||||
return {
|
return {
|
||||||
status: 400,
|
status: 400,
|
||||||
body: {
|
body: {
|
||||||
error: await res.json(),
|
error: 'Error change password',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import { getRequest } from '../../../../lib/api';
|
|
||||||
|
|
||||||
export const get: RequestHandler = async ({ request, locals }) => {
|
|
||||||
const allUsers = await getRequest('user?isAll=true', locals.user!.accessToken);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: { allUsers },
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,52 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import { putRequest } from '$lib/api';
|
|
||||||
import * as cookie from 'cookie';
|
|
||||||
|
|
||||||
export const post: RequestHandler = async ({ request, locals }) => {
|
|
||||||
|
|
||||||
const { id, isAdmin } = await request.json()
|
|
||||||
|
|
||||||
const res = await putRequest('user', {
|
|
||||||
id,
|
|
||||||
isAdmin,
|
|
||||||
}, locals.user!.accessToken);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (res.statusCode) {
|
|
||||||
return {
|
|
||||||
status: res.statusCode,
|
|
||||||
body: JSON.stringify(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.id == locals.user!.id) {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: { userInfo: res },
|
|
||||||
headers: {
|
|
||||||
'Set-Cookie': cookie.serialize('session', JSON.stringify(
|
|
||||||
{
|
|
||||||
id: res.id,
|
|
||||||
accessToken: locals.user!.accessToken,
|
|
||||||
firstName: res.firstName,
|
|
||||||
lastName: res.lastName,
|
|
||||||
isAdmin: res.isAdmin,
|
|
||||||
email: res.email,
|
|
||||||
}), {
|
|
||||||
path: '/',
|
|
||||||
httpOnly: true,
|
|
||||||
sameSite: 'strict',
|
|
||||||
maxAge: 60 * 60 * 24 * 30,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: { userInfo: { ...locals.user! } },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,17 +1,6 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
|
||||||
import * as cookie from 'cookie';
|
import * as cookie from 'cookie';
|
||||||
import { getRequest, putRequest } from '$lib/api';
|
import { api } from '@api';
|
||||||
|
|
||||||
type AuthUser = {
|
|
||||||
accessToken: string;
|
|
||||||
userId: string;
|
|
||||||
userEmail: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
isAdmin: boolean;
|
|
||||||
shouldChangePassword: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const post: RequestHandler = async ({ request }) => {
|
export const post: RequestHandler = async ({ request }) => {
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
@ -19,22 +8,11 @@ export const post: RequestHandler = async ({ request }) => {
|
|||||||
const email = form.get('email');
|
const email = form.get('email');
|
||||||
const password = form.get('password');
|
const password = form.get('password');
|
||||||
|
|
||||||
const payload = {
|
try {
|
||||||
email,
|
const { data: authUser } = await api.authenticationApi.login({
|
||||||
password,
|
email: String(email),
|
||||||
};
|
password: String(password),
|
||||||
|
});
|
||||||
const res = await fetch(`${serverEndpoint}/auth/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 201) {
|
|
||||||
// Login success
|
|
||||||
const authUser = (await res.json()) as AuthUser;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -70,7 +48,7 @@ export const post: RequestHandler = async ({ request }) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
status: 400,
|
status: 400,
|
||||||
body: {
|
body: {
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import { putRequest } from '../../../lib/api';
|
|
||||||
import * as cookie from 'cookie';
|
|
||||||
|
|
||||||
export const post: RequestHandler = async ({ request, locals }) => {
|
|
||||||
const form = await request.formData();
|
|
||||||
|
|
||||||
const firstName = form.get('firstName');
|
|
||||||
const lastName = form.get('lastName');
|
|
||||||
|
|
||||||
if (locals.user) {
|
|
||||||
const updatedUser = await putRequest(
|
|
||||||
'user',
|
|
||||||
{
|
|
||||||
id: locals.user.id,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
},
|
|
||||||
locals.user.accessToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
user: {
|
|
||||||
id: updatedUser.id,
|
|
||||||
accessToken: locals.user.accessToken,
|
|
||||||
firstName: updatedUser.firstName,
|
|
||||||
lastName: updatedUser.lastName,
|
|
||||||
isAdmin: updatedUser.isAdmin,
|
|
||||||
email: updatedUser.email,
|
|
||||||
},
|
|
||||||
success: 'Update user success',
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
'Set-Cookie': cookie.serialize(
|
|
||||||
'session',
|
|
||||||
JSON.stringify({
|
|
||||||
id: updatedUser.id,
|
|
||||||
accessToken: locals.user.accessToken,
|
|
||||||
firstName: updatedUser.firstName,
|
|
||||||
lastName: updatedUser.lastName,
|
|
||||||
isAdmin: updatedUser.isAdmin,
|
|
||||||
email: updatedUser.email,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
httpOnly: true,
|
|
||||||
sameSite: 'strict',
|
|
||||||
maxAge: 60 * 60 * 24 * 30,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
error: 'Cannot get access token from cookies',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,6 +1,6 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const post: RequestHandler = async ({ request }) => {
|
export const post: RequestHandler = async () => {
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
'Set-Cookie': 'session=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT',
|
'Set-Cookie': 'session=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT',
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
|
||||||
|
|
||||||
export const load: Load = async ({ session, fetch }) => {
|
export const load: Load = async ({ session }) => {
|
||||||
const res = await fetch(`${serverEndpoint}/user/count`);
|
const { data } = await api.userApi.getUserCount();
|
||||||
const { userCount } = await res.json();
|
|
||||||
|
|
||||||
if (userCount != 0) {
|
if (data.userCount != 0) {
|
||||||
// Admin has been registered, redirect to login
|
// Admin has been registered, redirect to login
|
||||||
|
|
||||||
if (!session.user) {
|
if (!session.user) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@ -17,7 +14,7 @@
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/dashboard',
|
redirect: '/photos',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,6 +25,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
|
import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
|
||||||
|
import { api } from '@api';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -1,43 +1,34 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { serverEndpoint } from '$lib/constants';
|
import { api } from '@api';
|
||||||
|
|
||||||
export const post: RequestHandler = async ({ request }) => {
|
export const post: RequestHandler = async ({ request }) => {
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
|
|
||||||
const email = form.get('email')
|
const email = form.get('email');
|
||||||
const password = form.get('password')
|
const password = form.get('password');
|
||||||
const firstName = form.get('firstName')
|
const firstName = form.get('firstName');
|
||||||
const lastName = form.get('lastName')
|
const lastName = form.get('lastName');
|
||||||
|
|
||||||
const payload = {
|
const { status } = await api.authenticationApi.adminSignUp({
|
||||||
email,
|
email: String(email),
|
||||||
password,
|
password: String(password),
|
||||||
firstName,
|
firstName: String(firstName),
|
||||||
lastName,
|
lastName: String(lastName),
|
||||||
}
|
});
|
||||||
|
|
||||||
const res = await fetch(`${serverEndpoint}/auth/admin-sign-up`, {
|
if (status === 201) {
|
||||||
method: 'POST',
|
return {
|
||||||
headers: {
|
status: 201,
|
||||||
'Content-Type': 'application/json'
|
body: {
|
||||||
},
|
success: 'Succesfully create admin account',
|
||||||
body: JSON.stringify(payload),
|
},
|
||||||
})
|
};
|
||||||
|
} else {
|
||||||
if (res.status === 201) {
|
return {
|
||||||
return {
|
status: 400,
|
||||||
status: 201,
|
body: {
|
||||||
body: {
|
error: 'Error create admin account',
|
||||||
success: 'Succesfully create admin account'
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
error: await res.json()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,39 +1,28 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
import { api } from '@api';
|
||||||
|
|
||||||
export const load: Load = async ({ session, fetch }) => {
|
export const load: Load = async ({ session }) => {
|
||||||
const res = await fetch(`${serverEndpoint}/user/count`);
|
const { data } = await api.userApi.getUserCount();
|
||||||
const { userCount } = await res.json();
|
|
||||||
|
|
||||||
if (!session.user) {
|
if (session.user) {
|
||||||
// Check if admin exist to wherether navigating to login or registration
|
|
||||||
if (userCount != 0) {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
props: {
|
|
||||||
isAdminUserExist: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
props: {
|
|
||||||
isAdminUserExist: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/photos',
|
redirect: '/photos',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
props: {
|
||||||
|
isAdminUserExist: data.userCount == 0 ? false : true,
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { serverEndpoint } from '$lib/constants';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export let isAdminUserExist: boolean;
|
export let isAdminUserExist: boolean;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await getAssetsInfo(session.user.accessToken);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
props: {
|
props: {
|
||||||
@ -30,17 +32,16 @@
|
|||||||
|
|
||||||
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
||||||
import { AppSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
import { AppSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
|
import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
|
||||||
import ImmichThumbnail from '$lib/components/asset-viewer/immich-thumbnail.svelte';
|
import ImmichThumbnail from '$lib/components/asset-viewer/immich-thumbnail.svelte';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import type { ImmichAsset } from '$lib/models/immich-asset';
|
|
||||||
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
||||||
import StatusBox from '$lib/components/shared/status-box.svelte';
|
import StatusBox from '$lib/components/shared/status-box.svelte';
|
||||||
import { fileUploader } from '$lib/utils/file-uploader';
|
import { fileUploader } from '$lib/utils/file-uploader';
|
||||||
import { openWebsocketConnection, closeWebsocketConnection } from '$lib/stores/websocket';
|
import { AssetResponseDto } from '@api';
|
||||||
|
|
||||||
export let user: ImmichUser;
|
export let user: ImmichUser;
|
||||||
|
|
||||||
@ -54,7 +55,7 @@
|
|||||||
|
|
||||||
let isShowAsset = false;
|
let isShowAsset = false;
|
||||||
let currentViewAssetIndex = 0;
|
let currentViewAssetIndex = 0;
|
||||||
let currentSelectedAsset: ImmichAsset;
|
let currentSelectedAsset: AssetResponseDto;
|
||||||
|
|
||||||
const onButtonClicked = (buttonType: CustomEvent) => {
|
const onButtonClicked = (buttonType: CustomEvent) => {
|
||||||
selectedAction = buttonType.detail['actionType'] as AppSideBarSelection;
|
selectedAction = buttonType.detail['actionType'] as AppSideBarSelection;
|
||||||
@ -62,16 +63,6 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
selectedAction = AppSideBarSelection.PHOTOS;
|
selectedAction = AppSideBarSelection.PHOTOS;
|
||||||
|
|
||||||
if ($session.user) {
|
|
||||||
await getAssetsInfo($session.user.accessToken);
|
|
||||||
|
|
||||||
openWebsocketConnection($session.user.accessToken);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
closeWebsocketConnection();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const thumbnailMouseEventHandler = (event: CustomEvent) => {
|
const thumbnailMouseEventHandler = (event: CustomEvent) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import preprocess from 'svelte-preprocess';
|
import preprocess from 'svelte-preprocess';
|
||||||
import adapter from '@sveltejs/adapter-node';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
import path from 'path';
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
preprocess: preprocess(),
|
preprocess: preprocess(),
|
||||||
@ -14,6 +14,7 @@ const config = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js',
|
'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js',
|
||||||
|
'@api': path.resolve('./src/api'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -17,6 +17,11 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"importsNotUsedAsValues": "preserve",
|
"importsNotUsedAsValues": "preserve",
|
||||||
"preserveValueImports": false
|
"preserveValueImports": false,
|
||||||
|
"paths": {
|
||||||
|
"$lib": ["src/lib"],
|
||||||
|
"$lib/*": ["src/lib/*"],
|
||||||
|
"@api": ["src/api"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user