mirror of
https://github.com/immich-app/immich.git
synced 2025-12-20 09:15:35 +03:00
feat(server, web): include pictures of shared albums on map (#7439)
* feat(server, web): include pictures of shared albums on map * run prettier * re-create api clients * implement suggestions from code review * shared from partner -> shared from partners * rename to 'include shared partner assets' * chore: fix tsc error in server and prettier in web * fix: include assets shared via owner albums --------- Co-authored-by: Zack Pollard <zackpollard@ymail.com> Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
committed by
GitHub
parent
d121903b38
commit
48927f5fb9
@@ -317,6 +317,9 @@ export class MapMarkerDto {
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
withPartners?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
withSharedAlbums?: boolean;
|
||||
}
|
||||
|
||||
export class MemoryLaneDto {
|
||||
|
||||
@@ -183,7 +183,7 @@ export interface IAssetRepository {
|
||||
softDeleteAll(ids: string[]): Promise<void>;
|
||||
restoreAll(ids: string[]): Promise<void>;
|
||||
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
|
||||
getMapMarkers(ownerIds: string[], options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
|
||||
getMapMarkers(ownerIds: string[], albumIds: string[], options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
|
||||
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
|
||||
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
|
||||
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
|
||||
|
||||
@@ -490,9 +490,24 @@ export class AssetRepository implements IAssetRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async getMapMarkers(ownerIds: string[], options: MapMarkerSearchOptions = {}): Promise<MapMarker[]> {
|
||||
async getMapMarkers(
|
||||
ownerIds: string[],
|
||||
albumIds: string[],
|
||||
options: MapMarkerSearchOptions = {},
|
||||
): Promise<MapMarker[]> {
|
||||
const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options;
|
||||
|
||||
const where = {
|
||||
isVisible: true,
|
||||
isArchived,
|
||||
exifInfo: {
|
||||
latitude: Not(IsNull()),
|
||||
longitude: Not(IsNull()),
|
||||
},
|
||||
isFavorite,
|
||||
fileCreatedAt: OptionalBetween(fileCreatedAfter, fileCreatedBefore),
|
||||
};
|
||||
|
||||
const assets = await this.repository.find({
|
||||
select: {
|
||||
id: true,
|
||||
@@ -504,17 +519,10 @@ export class AssetRepository implements IAssetRepository {
|
||||
longitude: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
ownerId: In([...ownerIds]),
|
||||
isVisible: true,
|
||||
isArchived,
|
||||
exifInfo: {
|
||||
latitude: Not(IsNull()),
|
||||
longitude: Not(IsNull()),
|
||||
},
|
||||
isFavorite,
|
||||
fileCreatedAt: OptionalBetween(fileCreatedAfter, fileCreatedBefore),
|
||||
},
|
||||
where: [
|
||||
{ ...where, ownerId: In([...ownerIds]) },
|
||||
{ ...where, albums: { id: In([...albumIds]) } },
|
||||
],
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AssetJobName, AssetStatsResponseDto, UploadFieldName } from 'src/dtos/asset.dto';
|
||||
import { AssetEntity, AssetType } from 'src/entities/asset.entity';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface';
|
||||
import { AssetStats, IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||
@@ -18,6 +19,7 @@ import { faceStub } from 'test/fixtures/face.stub';
|
||||
import { partnerStub } from 'test/fixtures/partner.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
||||
import { newAssetStackRepositoryMock } from 'test/repositories/asset-stack.repository.mock';
|
||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
|
||||
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
|
||||
@@ -160,6 +162,7 @@ describe(AssetService.name, () => {
|
||||
let configMock: Mocked<ISystemConfigRepository>;
|
||||
let partnerMock: Mocked<IPartnerRepository>;
|
||||
let assetStackMock: Mocked<IAssetStackRepository>;
|
||||
let albumMock: Mocked<IAlbumRepository>;
|
||||
let loggerMock: Mocked<ILoggerRepository>;
|
||||
|
||||
it('should work', () => {
|
||||
@@ -182,6 +185,7 @@ describe(AssetService.name, () => {
|
||||
configMock = newSystemConfigRepositoryMock();
|
||||
partnerMock = newPartnerRepositoryMock();
|
||||
assetStackMock = newAssetStackRepositoryMock();
|
||||
albumMock = newAlbumRepositoryMock();
|
||||
loggerMock = newLoggerRepositoryMock();
|
||||
|
||||
sut = new AssetService(
|
||||
@@ -194,6 +198,7 @@ describe(AssetService.name, () => {
|
||||
eventMock,
|
||||
partnerMock,
|
||||
assetStackMock,
|
||||
albumMock,
|
||||
loggerMock,
|
||||
);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { UpdateStackParentDto } from 'src/dtos/stack.dto';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { LibraryType } from 'src/entities/library.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||
@@ -78,6 +79,7 @@ export class AssetService {
|
||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
||||
@Inject(IAssetStackRepository) private assetStackRepository: IAssetStackRepository,
|
||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||
) {
|
||||
this.logger.setContext(AssetService.name);
|
||||
@@ -167,6 +169,7 @@ export class AssetService {
|
||||
|
||||
async getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||
const userIds: string[] = [auth.user.id];
|
||||
// TODO convert to SQL join
|
||||
if (options.withPartners) {
|
||||
const partners = await this.partnerRepository.getAll(auth.user.id);
|
||||
const partnersIds = partners
|
||||
@@ -174,7 +177,18 @@ export class AssetService {
|
||||
.map((partner) => partner.sharedById);
|
||||
userIds.push(...partnersIds);
|
||||
}
|
||||
return this.assetRepository.getMapMarkers(userIds, options);
|
||||
|
||||
// TODO convert to SQL join
|
||||
const albumIds: string[] = [];
|
||||
if (options.withSharedAlbums) {
|
||||
const [ownedAlbums, sharedAlbums] = await Promise.all([
|
||||
this.albumRepository.getOwned(auth.user.id),
|
||||
this.albumRepository.getShared(auth.user.id),
|
||||
]);
|
||||
albumIds.push(...ownedAlbums.map((album) => album.id), ...sharedAlbums.map((album) => album.id));
|
||||
}
|
||||
|
||||
return this.assetRepository.getMapMarkers(userIds, albumIds, options);
|
||||
}
|
||||
|
||||
async getMemoryLane(auth: AuthDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
||||
|
||||
Reference in New Issue
Block a user