You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-08-10 23:22:22 +02:00
118 - Implement shared album feature (#124)
* New features - Share album. Users can now create albums to share with existing people on the network. - Owner can delete the album. - Owner can invite the additional users to the album. - Shared users and the owner can add additional assets to the album. * In the asset viewer, the user can swipe up to see detailed information and swip down to dismiss. * Several UI enhancements.
This commit is contained in:
@@ -82,7 +82,7 @@ export class AssetController {
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
@Query(ValidationPipe) query: ServeFileDto,
|
||||
) {
|
||||
return this.assetService.downloadFile(authUser, query, res);
|
||||
return this.assetService.downloadFile(query, res);
|
||||
}
|
||||
|
||||
@Get('/file')
|
||||
@@ -95,6 +95,11 @@ export class AssetController {
|
||||
return this.assetService.serveFile(authUser, query, res, headers);
|
||||
}
|
||||
|
||||
@Get('/thumbnail/:assetId')
|
||||
async getAssetThumbnail(@Param('assetId') assetId: string): Promise<StreamableFile> {
|
||||
return await this.assetService.getAssetThumbnail(assetId);
|
||||
}
|
||||
|
||||
@Get('/allObjects')
|
||||
async getCuratedObject(@GetAuthUser() authUser: AuthUserDto) {
|
||||
return this.assetService.getCuratedObject(authUser);
|
||||
|
@@ -76,10 +76,10 @@ export class AssetService {
|
||||
}
|
||||
}
|
||||
|
||||
public async findOne(authUser: AuthUserDto, deviceId: string, assetId: string): Promise<AssetEntity> {
|
||||
public async findOne(deviceId: string, assetId: string): Promise<AssetEntity> {
|
||||
const rows = await this.assetRepository.query(
|
||||
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."userId" = $2 AND a."deviceId" = $3',
|
||||
[assetId, authUser.id, deviceId],
|
||||
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
|
||||
[assetId, deviceId],
|
||||
);
|
||||
|
||||
if (rows.lengh == 0) {
|
||||
@@ -92,16 +92,15 @@ export class AssetService {
|
||||
public async getAssetById(authUser: AuthUserDto, assetId: string) {
|
||||
return await this.assetRepository.findOne({
|
||||
where: {
|
||||
userId: authUser.id,
|
||||
id: assetId,
|
||||
},
|
||||
relations: ['exifInfo'],
|
||||
});
|
||||
}
|
||||
|
||||
public async downloadFile(authUser: AuthUserDto, query: ServeFileDto, res: Res) {
|
||||
public async downloadFile(query: ServeFileDto, res: Res) {
|
||||
let file = null;
|
||||
const asset = await this.findOne(authUser, query.did, query.aid);
|
||||
const asset = await this.findOne(query.did, query.aid);
|
||||
|
||||
if (query.isThumb === 'false' || !query.isThumb) {
|
||||
file = createReadStream(asset.originalPath);
|
||||
@@ -112,10 +111,15 @@ export class AssetService {
|
||||
return new StreamableFile(file);
|
||||
}
|
||||
|
||||
public async getAssetThumbnail(assetId: string) {
|
||||
const asset = await this.assetRepository.findOne({ id: assetId });
|
||||
|
||||
return new StreamableFile(createReadStream(asset.resizePath));
|
||||
}
|
||||
|
||||
public async serveFile(authUser: AuthUserDto, query: ServeFileDto, res: Res, headers: any) {
|
||||
let file = null;
|
||||
const asset = await this.findOne(authUser, query.did, query.aid);
|
||||
|
||||
const asset = await this.findOne(query.did, query.aid);
|
||||
if (!asset) {
|
||||
throw new BadRequestException('Asset does not exist');
|
||||
}
|
||||
|
10
server/src/api-v1/sharing/dto/add-assets.dto.ts
Normal file
10
server/src/api-v1/sharing/dto/add-assets.dto.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
import { AssetEntity } from '../../asset/entities/asset.entity';
|
||||
|
||||
export class AddAssetsDto {
|
||||
@IsNotEmpty()
|
||||
albumId: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
assetIds: string[];
|
||||
}
|
9
server/src/api-v1/sharing/dto/add-users.dto.ts
Normal file
9
server/src/api-v1/sharing/dto/add-users.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class AddUsersDto {
|
||||
@IsNotEmpty()
|
||||
albumId: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
sharedUserIds: string[];
|
||||
}
|
13
server/src/api-v1/sharing/dto/create-shared-album.dto.ts
Normal file
13
server/src/api-v1/sharing/dto/create-shared-album.dto.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { AssetEntity } from '../../asset/entities/asset.entity';
|
||||
|
||||
export class CreateSharedAlbumDto {
|
||||
@IsNotEmpty()
|
||||
albumName: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
sharedWithUserIds: string[];
|
||||
|
||||
@IsOptional()
|
||||
assetIds: string[];
|
||||
}
|
9
server/src/api-v1/sharing/dto/remove-assets.dto.ts
Normal file
9
server/src/api-v1/sharing/dto/remove-assets.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class RemoveAssetsDto {
|
||||
@IsNotEmpty()
|
||||
albumId: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
assetIds: string[];
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { AssetEntity } from '../../asset/entities/asset.entity';
|
||||
import { SharedAlbumEntity } from './shared-album.entity';
|
||||
|
||||
@Entity('asset_shared_album')
|
||||
@Unique('PK_unique_asset_in_album', ['albumId', 'assetId'])
|
||||
export class AssetSharedAlbumEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
albumId: string;
|
||||
|
||||
@Column()
|
||||
assetId: string;
|
||||
|
||||
@ManyToOne(() => SharedAlbumEntity, (sharedAlbum) => sharedAlbum.sharedAssets, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'albumId' })
|
||||
albumInfo: SharedAlbumEntity;
|
||||
|
||||
@ManyToOne(() => AssetEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'assetId' })
|
||||
assetInfo: AssetEntity;
|
||||
}
|
27
server/src/api-v1/sharing/entities/shared-album.entity.ts
Normal file
27
server/src/api-v1/sharing/entities/shared-album.entity.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { AssetSharedAlbumEntity } from './asset-shared-album.entity';
|
||||
import { UserSharedAlbumEntity } from './user-shared-album.entity';
|
||||
|
||||
@Entity('shared_albums')
|
||||
export class SharedAlbumEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
ownerId: string;
|
||||
|
||||
@Column({ default: 'Untitled Album' })
|
||||
albumName: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: string;
|
||||
|
||||
@Column({ comment: 'Asset ID to be used as thumbnail', nullable: true })
|
||||
albumThumbnailAssetId: string;
|
||||
|
||||
@OneToMany(() => UserSharedAlbumEntity, (userSharedAlbums) => userSharedAlbums.albumInfo)
|
||||
sharedUsers: UserSharedAlbumEntity[];
|
||||
|
||||
@OneToMany(() => AssetSharedAlbumEntity, (assetSharedAlbumEntity) => assetSharedAlbumEntity.albumInfo)
|
||||
sharedAssets: AssetSharedAlbumEntity[];
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { UserEntity } from '../../user/entities/user.entity';
|
||||
import { SharedAlbumEntity } from './shared-album.entity';
|
||||
|
||||
@Entity('user_shared_album')
|
||||
@Unique('PK_unique_user_in_album', ['albumId', 'sharedUserId'])
|
||||
export class UserSharedAlbumEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
albumId: string;
|
||||
|
||||
@Column()
|
||||
sharedUserId: string;
|
||||
|
||||
@ManyToOne(() => SharedAlbumEntity, (sharedAlbum) => sharedAlbum.sharedUsers, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'albumId' })
|
||||
albumInfo: SharedAlbumEntity;
|
||||
|
||||
@ManyToOne(() => UserEntity)
|
||||
@JoinColumn({ name: 'sharedUserId' })
|
||||
userInfo: UserEntity;
|
||||
}
|
55
server/src/api-v1/sharing/sharing.controller.ts
Normal file
55
server/src/api-v1/sharing/sharing.controller.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, ValidationPipe, Query } from '@nestjs/common';
|
||||
import { SharingService } from './sharing.service';
|
||||
import { CreateSharedAlbumDto } from './dto/create-shared-album.dto';
|
||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { AddAssetsDto } from './dto/add-assets.dto';
|
||||
import { AddUsersDto } from './dto/add-users.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('shared')
|
||||
export class SharingController {
|
||||
constructor(private readonly sharingService: SharingService) {}
|
||||
|
||||
@Post('/createAlbum')
|
||||
async create(@GetAuthUser() authUser, @Body(ValidationPipe) createSharedAlbumDto: CreateSharedAlbumDto) {
|
||||
return await this.sharingService.create(authUser, createSharedAlbumDto);
|
||||
}
|
||||
|
||||
@Post('/addUsers')
|
||||
async addUsers(@Body(ValidationPipe) addUsersDto: AddUsersDto) {
|
||||
return await this.sharingService.addUsersToAlbum(addUsersDto);
|
||||
}
|
||||
|
||||
@Post('/addAssets')
|
||||
async addAssets(@Body(ValidationPipe) addAssetsDto: AddAssetsDto) {
|
||||
return await this.sharingService.addAssetsToAlbum(addAssetsDto);
|
||||
}
|
||||
|
||||
@Get('/allSharedAlbums')
|
||||
async getAllSharedAlbums(@GetAuthUser() authUser) {
|
||||
return await this.sharingService.getAllSharedAlbums(authUser);
|
||||
}
|
||||
|
||||
@Get('/:albumId')
|
||||
async getAlbumInfo(@GetAuthUser() authUser, @Param('albumId') albumId: string) {
|
||||
return await this.sharingService.getAlbumInfo(authUser, albumId);
|
||||
}
|
||||
|
||||
@Delete('/removeAssets')
|
||||
async removeAssetFromAlbum(@GetAuthUser() authUser, @Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto) {
|
||||
console.log('removeAssets');
|
||||
return await this.sharingService.removeAssetsFromAlbum(authUser, removeAssetsDto);
|
||||
}
|
||||
|
||||
@Delete('/:albumId')
|
||||
async deleteAlbum(@GetAuthUser() authUser, @Param('albumId') albumId: string) {
|
||||
return await this.sharingService.deleteAlbum(authUser, albumId);
|
||||
}
|
||||
|
||||
@Delete('/leaveAlbum/:albumId')
|
||||
async leaveAlbum(@GetAuthUser() authUser, @Param('albumId') albumId: string) {
|
||||
return await this.sharingService.leaveAlbum(authUser, albumId);
|
||||
}
|
||||
}
|
24
server/src/api-v1/sharing/sharing.module.ts
Normal file
24
server/src/api-v1/sharing/sharing.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SharingService } from './sharing.service';
|
||||
import { SharingController } from './sharing.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AssetEntity } from '../asset/entities/asset.entity';
|
||||
import { UserEntity } from '../user/entities/user.entity';
|
||||
import { SharedAlbumEntity } from './entities/shared-album.entity';
|
||||
import { AssetSharedAlbumEntity } from './entities/asset-shared-album.entity';
|
||||
import { UserSharedAlbumEntity } from './entities/user-shared-album.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
AssetEntity,
|
||||
UserEntity,
|
||||
SharedAlbumEntity,
|
||||
AssetSharedAlbumEntity,
|
||||
UserSharedAlbumEntity,
|
||||
]),
|
||||
],
|
||||
controllers: [SharingController],
|
||||
providers: [SharingService],
|
||||
})
|
||||
export class SharingModule {}
|
187
server/src/api-v1/sharing/sharing.service.ts
Normal file
187
server/src/api-v1/sharing/sharing.service.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { BadRequestException, Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { getConnection, Repository } from 'typeorm';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { AssetEntity } from '../asset/entities/asset.entity';
|
||||
import { UserEntity } from '../user/entities/user.entity';
|
||||
import { AddAssetsDto } from './dto/add-assets.dto';
|
||||
import { CreateSharedAlbumDto } from './dto/create-shared-album.dto';
|
||||
import { AssetSharedAlbumEntity } from './entities/asset-shared-album.entity';
|
||||
import { SharedAlbumEntity } from './entities/shared-album.entity';
|
||||
import { UserSharedAlbumEntity } from './entities/user-shared-album.entity';
|
||||
import _ from 'lodash';
|
||||
import { AddUsersDto } from './dto/add-users.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
|
||||
@Injectable()
|
||||
export class SharingService {
|
||||
constructor(
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
|
||||
@InjectRepository(UserEntity)
|
||||
private userRepository: Repository<UserEntity>,
|
||||
|
||||
@InjectRepository(SharedAlbumEntity)
|
||||
private sharedAlbumRepository: Repository<SharedAlbumEntity>,
|
||||
|
||||
@InjectRepository(AssetSharedAlbumEntity)
|
||||
private assetSharedAlbumRepository: Repository<AssetSharedAlbumEntity>,
|
||||
|
||||
@InjectRepository(UserSharedAlbumEntity)
|
||||
private userSharedAlbumRepository: Repository<UserSharedAlbumEntity>,
|
||||
) {}
|
||||
|
||||
async create(authUser: AuthUserDto, createSharedAlbumDto: CreateSharedAlbumDto) {
|
||||
return await getConnection().transaction(async (transactionalEntityManager) => {
|
||||
// Create album entity
|
||||
const newSharedAlbum = new SharedAlbumEntity();
|
||||
newSharedAlbum.ownerId = authUser.id;
|
||||
newSharedAlbum.albumName = createSharedAlbumDto.albumName;
|
||||
|
||||
const sharedAlbum = await transactionalEntityManager.save(newSharedAlbum);
|
||||
|
||||
// Add shared users
|
||||
for (const sharedUserId of createSharedAlbumDto.sharedWithUserIds) {
|
||||
const newSharedUser = new UserSharedAlbumEntity();
|
||||
newSharedUser.albumId = sharedAlbum.id;
|
||||
newSharedUser.sharedUserId = sharedUserId;
|
||||
|
||||
await transactionalEntityManager.save(newSharedUser);
|
||||
}
|
||||
|
||||
// Add shared assets
|
||||
const newRecords: AssetSharedAlbumEntity[] = [];
|
||||
|
||||
for (const assetId of createSharedAlbumDto.assetIds) {
|
||||
const newAssetSharedAlbum = new AssetSharedAlbumEntity();
|
||||
newAssetSharedAlbum.assetId = assetId;
|
||||
newAssetSharedAlbum.albumId = sharedAlbum.id;
|
||||
|
||||
newRecords.push(newAssetSharedAlbum);
|
||||
}
|
||||
|
||||
if (!sharedAlbum.albumThumbnailAssetId && newRecords.length > 0) {
|
||||
sharedAlbum.albumThumbnailAssetId = newRecords[0].assetId;
|
||||
await transactionalEntityManager.save(sharedAlbum);
|
||||
}
|
||||
|
||||
await transactionalEntityManager.save([...newRecords]);
|
||||
|
||||
return sharedAlbum;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all shared album, including owned and shared one.
|
||||
* @param authUser AuthUserDto
|
||||
* @returns All Shared Album And Its Members
|
||||
*/
|
||||
async getAllSharedAlbums(authUser: AuthUserDto) {
|
||||
const ownedAlbums = await this.sharedAlbumRepository.find({
|
||||
where: { ownerId: authUser.id },
|
||||
relations: ['sharedUsers', 'sharedUsers.userInfo'],
|
||||
});
|
||||
|
||||
const isSharedWithAlbums = await this.userSharedAlbumRepository.find({
|
||||
where: {
|
||||
sharedUserId: authUser.id,
|
||||
},
|
||||
relations: ['albumInfo', 'albumInfo.sharedUsers', 'albumInfo.sharedUsers.userInfo'],
|
||||
select: ['albumInfo'],
|
||||
});
|
||||
|
||||
return [...ownedAlbums, ...isSharedWithAlbums.map((o) => o.albumInfo)].sort(
|
||||
(a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf(),
|
||||
);
|
||||
}
|
||||
|
||||
async getAlbumInfo(authUser: AuthUserDto, albumId: string) {
|
||||
const albumOwner = await this.sharedAlbumRepository.findOne({ where: { ownerId: authUser.id } });
|
||||
const personShared = await this.userSharedAlbumRepository.findOne({
|
||||
where: { albumId: albumId, sharedUserId: authUser.id },
|
||||
});
|
||||
|
||||
if (!(albumOwner || personShared)) {
|
||||
throw new UnauthorizedException('Unauthorized Album Access');
|
||||
}
|
||||
|
||||
const albumInfo = await this.sharedAlbumRepository.findOne({
|
||||
where: { id: albumId },
|
||||
relations: ['sharedUsers', 'sharedUsers.userInfo', 'sharedAssets', 'sharedAssets.assetInfo'],
|
||||
});
|
||||
|
||||
if (!albumInfo) {
|
||||
throw new NotFoundException('Album Not Found');
|
||||
}
|
||||
const sortedSharedAsset = albumInfo.sharedAssets.sort(
|
||||
(a, b) => new Date(a.assetInfo.createdAt).valueOf() - new Date(b.assetInfo.createdAt).valueOf(),
|
||||
);
|
||||
|
||||
albumInfo.sharedAssets = sortedSharedAsset;
|
||||
|
||||
return albumInfo;
|
||||
}
|
||||
|
||||
async addUsersToAlbum(addUsersDto: AddUsersDto) {
|
||||
const newRecords: UserSharedAlbumEntity[] = [];
|
||||
|
||||
for (const sharedUserId of addUsersDto.sharedUserIds) {
|
||||
const newEntity = new UserSharedAlbumEntity();
|
||||
newEntity.albumId = addUsersDto.albumId;
|
||||
newEntity.sharedUserId = sharedUserId;
|
||||
|
||||
newRecords.push(newEntity);
|
||||
}
|
||||
|
||||
return await this.userSharedAlbumRepository.save([...newRecords]);
|
||||
}
|
||||
|
||||
async deleteAlbum(authUser: AuthUserDto, albumId: string) {
|
||||
return await this.sharedAlbumRepository.delete({ id: albumId, ownerId: authUser.id });
|
||||
}
|
||||
|
||||
async leaveAlbum(authUser: AuthUserDto, albumId: string) {
|
||||
return await this.userSharedAlbumRepository.delete({ albumId: albumId, sharedUserId: authUser.id });
|
||||
}
|
||||
|
||||
async removeUsersFromAlbum() {}
|
||||
|
||||
async removeAssetsFromAlbum(authUser: AuthUserDto, removeAssetsDto: RemoveAssetsDto) {
|
||||
let deleteAssetCount = 0;
|
||||
const album = await this.sharedAlbumRepository.findOne({ id: removeAssetsDto.albumId });
|
||||
|
||||
if (album.ownerId != authUser.id) {
|
||||
throw new BadRequestException("You don't have permission to remove assets in this album");
|
||||
}
|
||||
|
||||
for (const assetId of removeAssetsDto.assetIds) {
|
||||
const res = await this.assetSharedAlbumRepository.delete({ albumId: removeAssetsDto.albumId, assetId: assetId });
|
||||
if (res.affected == 1) deleteAssetCount++;
|
||||
}
|
||||
|
||||
return deleteAssetCount == removeAssetsDto.assetIds.length;
|
||||
}
|
||||
|
||||
async addAssetsToAlbum(addAssetsDto: AddAssetsDto) {
|
||||
const newRecords: AssetSharedAlbumEntity[] = [];
|
||||
|
||||
for (const assetId of addAssetsDto.assetIds) {
|
||||
const newAssetSharedAlbum = new AssetSharedAlbumEntity();
|
||||
newAssetSharedAlbum.assetId = assetId;
|
||||
newAssetSharedAlbum.albumId = addAssetsDto.albumId;
|
||||
|
||||
newRecords.push(newAssetSharedAlbum);
|
||||
}
|
||||
|
||||
// Add album thumbnail if not exist.
|
||||
const album = await this.sharedAlbumRepository.findOne({ id: addAssetsDto.albumId });
|
||||
|
||||
if (!album.albumThumbnailAssetId && newRecords.length > 0) {
|
||||
album.albumThumbnailAssetId = newRecords[0].assetId;
|
||||
await this.sharedAlbumRepository.save(album);
|
||||
}
|
||||
|
||||
return await this.assetSharedAlbumRepository.save([...newRecords]);
|
||||
}
|
||||
}
|
@@ -1,9 +1,15 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('user')
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Get()
|
||||
async getAllUsers(@GetAuthUser() authUser: AuthUserDto) {
|
||||
return await this.userService.getAllUsers(authUser);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Not, Repository } from 'typeorm';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { UserEntity } from './entities/user.entity';
|
||||
@@ -11,4 +12,10 @@ export class UserService {
|
||||
@InjectRepository(UserEntity)
|
||||
private userRepository: Repository<UserEntity>,
|
||||
) {}
|
||||
|
||||
async getAllUsers(authUser: AuthUserDto) {
|
||||
return await this.userRepository.find({
|
||||
where: { id: Not(authUser.id) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import { ImageOptimizeModule } from './modules/image-optimize/image-optimize.mod
|
||||
import { ServerInfoModule } from './api-v1/server-info/server-info.module';
|
||||
import { BackgroundTaskModule } from './modules/background-task/background-task.module';
|
||||
import { CommunicationModule } from './api-v1/communication/communication.module';
|
||||
import { SharingModule } from './api-v1/sharing/sharing.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -40,6 +41,8 @@ import { CommunicationModule } from './api-v1/communication/communication.module
|
||||
BackgroundTaskModule,
|
||||
|
||||
CommunicationModule,
|
||||
|
||||
SharingModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
|
@@ -1,11 +1,5 @@
|
||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
// import dotenv from 'dotenv';
|
||||
|
||||
// const result = dotenv.config();
|
||||
|
||||
// if (result.error) {
|
||||
// console.log(result.error);
|
||||
// }
|
||||
export const databaseConfig: TypeOrmModuleOptions = {
|
||||
type: 'postgres',
|
||||
host: 'immich_postgres',
|
||||
|
@@ -47,7 +47,6 @@ export const multerOption: MulterOptions = {
|
||||
},
|
||||
|
||||
filename: (req: Request, file: Express.Multer.File, cb: any) => {
|
||||
// console.log(req, file);
|
||||
const fileNameUUID = randomUUID();
|
||||
if (file.fieldname == 'assetData') {
|
||||
cb(null, `${fileNameUUID}${req.body['fileExtension'].toLowerCase()}`);
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
export const serverVersion = {
|
||||
major: 1,
|
||||
minor: 6,
|
||||
minor: 7,
|
||||
patch: 0,
|
||||
build: 10,
|
||||
build: 11,
|
||||
};
|
||||
|
@@ -4,13 +4,13 @@ export class AddRegionCityToExIf1646709533213 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN city varchar;
|
||||
ADD COLUMN if not exists city varchar;
|
||||
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN state varchar;
|
||||
ADD COLUMN if not exists state varchar;
|
||||
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN country varchar;
|
||||
ADD COLUMN if not exists country varchar;
|
||||
`);
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,10 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddObjectColumnToSmartInfo1648317474768
|
||||
implements MigrationInterface
|
||||
{
|
||||
export class AddObjectColumnToSmartInfo1648317474768 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE smart_info
|
||||
ADD COLUMN objects text[];
|
||||
ADD COLUMN if not exists objects text[];
|
||||
|
||||
`);
|
||||
}
|
||||
|
@@ -0,0 +1,70 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateSharedAlbumAndRelatedTables1649643216111 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Create shared_albums
|
||||
await queryRunner.query(`
|
||||
create table if not exists shared_albums
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null
|
||||
constraint "PK_7f71c7b5bc7c87b8f94c9a93a00"
|
||||
primary key,
|
||||
"ownerId" varchar not null,
|
||||
"albumName" varchar default 'Untitled Album'::character varying not null,
|
||||
"createdAt" timestamp with time zone default now() not null,
|
||||
"albumThumbnailAssetId" varchar
|
||||
);
|
||||
|
||||
comment on column shared_albums."albumThumbnailAssetId" is 'Asset ID to be used as thumbnail';
|
||||
`);
|
||||
|
||||
// Create user_shared_album
|
||||
await queryRunner.query(`
|
||||
create table if not exists user_shared_album
|
||||
(
|
||||
id serial
|
||||
constraint "PK_b6562316a98845a7b3e9a25cdd0"
|
||||
primary key,
|
||||
"albumId" uuid not null
|
||||
constraint "FK_7b3bf0f5f8da59af30519c25f18"
|
||||
references shared_albums
|
||||
on delete cascade,
|
||||
"sharedUserId" uuid not null
|
||||
constraint "FK_543c31211653e63e080ba882eb5"
|
||||
references users,
|
||||
constraint "PK_unique_user_in_album"
|
||||
unique ("albumId", "sharedUserId")
|
||||
);
|
||||
`);
|
||||
|
||||
// Create asset_shared_album
|
||||
await queryRunner.query(
|
||||
`
|
||||
create table if not exists asset_shared_album
|
||||
(
|
||||
id serial
|
||||
constraint "PK_a34e076afbc601d81938e2c2277"
|
||||
primary key,
|
||||
"albumId" uuid not null
|
||||
constraint "FK_a8b79a84996cef6ba6a3662825d"
|
||||
references shared_albums
|
||||
on delete cascade,
|
||||
"assetId" uuid not null
|
||||
constraint "FK_64f2e7d68d1d1d8417acc844a4a"
|
||||
references assets
|
||||
on delete cascade,
|
||||
constraint "UQ_a1e2734a1ce361e7a26f6b28288"
|
||||
unique ("albumId", "assetId")
|
||||
);
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
drop table asset_shared_album;
|
||||
drop table user_shared_album;
|
||||
drop table shared_albums;
|
||||
`);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user