mirror of
https://github.com/immich-app/immich.git
synced 2026-05-27 10:02:31 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a89564270 |
@@ -276,8 +276,6 @@ class TimeBucketAssetResponseDto {
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'city',
|
||||
'country',
|
||||
'createdAt',
|
||||
'duration',
|
||||
'fileCreatedAt',
|
||||
|
||||
@@ -25324,8 +25324,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"city",
|
||||
"country",
|
||||
"createdAt",
|
||||
"duration",
|
||||
"fileCreatedAt",
|
||||
|
||||
@@ -2612,9 +2612,9 @@ export type TagUpdateDto = {
|
||||
};
|
||||
export type TimeBucketAssetResponseDto = {
|
||||
/** Array of city names extracted from EXIF GPS data */
|
||||
city: (string | null)[];
|
||||
city?: (string | null)[];
|
||||
/** Array of country names extracted from EXIF GPS data */
|
||||
country: (string | null)[];
|
||||
country?: (string | null)[];
|
||||
/** Array of UTC timestamps when each asset was originally uploaded to Immich */
|
||||
createdAt: string[];
|
||||
/** Array of video/gif durations in milliseconds (null for static images) */
|
||||
|
||||
@@ -107,8 +107,8 @@ const TimeBucketAssetResponseSchema = z
|
||||
livePhotoVideoId: z
|
||||
.array(z.string().nullable())
|
||||
.describe('Array of live photo video asset IDs (null for non-live photos)'),
|
||||
city: z.array(z.string().nullable()).describe('Array of city names extracted from EXIF GPS data'),
|
||||
country: z.array(z.string().nullable()).describe('Array of country names extracted from EXIF GPS data'),
|
||||
city: z.array(z.string().nullable()).optional().describe('Array of city names extracted from EXIF GPS data'),
|
||||
country: z.array(z.string().nullable()).optional().describe('Array of country names extracted from EXIF GPS data'),
|
||||
latitude: z
|
||||
.array(z.number().nullable())
|
||||
.optional()
|
||||
|
||||
@@ -384,8 +384,6 @@ with
|
||||
asset."fileCreatedAt" at time zone 'utc' as "fileCreatedAt",
|
||||
asset."createdAt" at time zone 'utc' as "createdAt",
|
||||
encode("asset"."thumbhash", 'base64') as "thumbhash",
|
||||
"asset_exif"."city",
|
||||
"asset_exif"."country",
|
||||
"asset_exif"."projectionType",
|
||||
coalesce(
|
||||
case
|
||||
@@ -398,6 +396,8 @@ with
|
||||
end,
|
||||
1
|
||||
) as "ratio",
|
||||
"asset_exif"."city",
|
||||
"asset_exif"."country",
|
||||
"stack"
|
||||
from
|
||||
"asset"
|
||||
@@ -432,8 +432,6 @@ with
|
||||
),
|
||||
"agg" as (
|
||||
select
|
||||
coalesce(array_agg("city"), '{}') as "city",
|
||||
coalesce(array_agg("country"), '{}') as "country",
|
||||
coalesce(array_agg("duration"), '{}') as "duration",
|
||||
coalesce(array_agg("id"), '{}') as "id",
|
||||
coalesce(array_agg("visibility"), '{}') as "visibility",
|
||||
@@ -449,6 +447,8 @@ with
|
||||
coalesce(array_agg("ratio"), '{}') as "ratio",
|
||||
coalesce(array_agg("status"), '{}') as "status",
|
||||
coalesce(array_agg("thumbhash"), '{}') as "thumbhash",
|
||||
coalesce(array_agg("city"), '{}') as "city",
|
||||
coalesce(array_agg("country"), '{}') as "country",
|
||||
coalesce(json_agg("stack"), '[]') as "stack"
|
||||
from
|
||||
"cte"
|
||||
|
||||
@@ -134,7 +134,7 @@ from
|
||||
"cte"
|
||||
where
|
||||
"cte"."distance" <= $4
|
||||
commit
|
||||
rollback
|
||||
|
||||
-- SearchRepository.searchPlaces
|
||||
select
|
||||
|
||||
@@ -786,8 +786,6 @@ export class AssetRepository {
|
||||
sql`asset."fileCreatedAt" at time zone 'utc'`.as('fileCreatedAt'),
|
||||
sql`asset."createdAt" at time zone 'utc'`.as('createdAt'),
|
||||
eb.fn('encode', ['asset.thumbhash', sql.lit('base64')]).as('thumbhash'),
|
||||
'asset_exif.city',
|
||||
'asset_exif.country',
|
||||
'asset_exif.projectionType',
|
||||
eb.fn
|
||||
.coalesce(
|
||||
@@ -801,6 +799,9 @@ export class AssetRepository {
|
||||
)
|
||||
.as('ratio'),
|
||||
])
|
||||
.$if(!auth.sharedLink || auth.sharedLink.showExif, (qb) =>
|
||||
qb.select(['asset_exif.city', 'asset_exif.country']),
|
||||
)
|
||||
.$if(!!options.withCoordinates, (qb) => qb.select(['asset_exif.latitude', 'asset_exif.longitude']))
|
||||
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||
.$if(options.visibility == undefined, withDefaultVisibility)
|
||||
@@ -875,8 +876,6 @@ export class AssetRepository {
|
||||
qb
|
||||
.selectFrom('cte')
|
||||
.select((eb) => [
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['city']), sql.lit('{}')).as('city'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['country']), sql.lit('{}')).as('country'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['duration']), sql.lit('{}')).as('duration'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['id']), sql.lit('{}')).as('id'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['visibility']), sql.lit('{}')).as('visibility'),
|
||||
@@ -894,6 +893,12 @@ export class AssetRepository {
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['status']), sql.lit('{}')).as('status'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['thumbhash']), sql.lit('{}')).as('thumbhash'),
|
||||
])
|
||||
.$if(!auth.sharedLink || auth.sharedLink.showExif, (qb) =>
|
||||
qb.select((eb) => [
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['city']), sql.lit('{}')).as('city'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['country']), sql.lit('{}')).as('country'),
|
||||
]),
|
||||
)
|
||||
.$if(!!options.withCoordinates, (qb) =>
|
||||
qb.select((eb) => [
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['latitude']), sql.lit('{}')).as('latitude'),
|
||||
|
||||
@@ -66,6 +66,10 @@ export class TimelineService extends BaseService {
|
||||
await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.tagId] });
|
||||
}
|
||||
|
||||
if (auth.sharedLink && !auth.sharedLink.showExif) {
|
||||
dto.withCoordinates = false;
|
||||
}
|
||||
|
||||
if (dto.withPartners) {
|
||||
const requestedArchived = dto.visibility === AssetVisibility.Archive || dto.visibility === undefined;
|
||||
const requestedFavorite = dto.isFavorite === true || dto.isFavorite === false;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { Kysely } from 'kysely';
|
||||
import { AssetVisibility } from 'src/enum';
|
||||
import { AssetVisibility, SharedLinkType } from 'src/enum';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { PartnerRepository } from 'src/repositories/partner.repository';
|
||||
import { SharedLinkRepository } from 'src/repositories/shared-link.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { TimelineService } from 'src/services/timeline.service';
|
||||
import { newMediumService } from 'test/medium.factory';
|
||||
@@ -207,4 +208,32 @@ describe(TimelineService.name, () => {
|
||||
expect(response2).toEqual(expect.objectContaining({ id: [asset2.id, asset1.id], isFavorite: [true, false] }));
|
||||
});
|
||||
});
|
||||
|
||||
it('should strip geodata metadata if shared link without exif', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
||||
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({
|
||||
ownerId: user.id,
|
||||
localDateTime: new Date('1970-02-12'),
|
||||
deletedAt: new Date(),
|
||||
});
|
||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
|
||||
const { id: sharedLinkId } = await sharedLinkRepo.create({
|
||||
allowUpload: false,
|
||||
key: Buffer.from('123'),
|
||||
type: SharedLinkType.Album,
|
||||
userId: user.id,
|
||||
albumId: album.id,
|
||||
});
|
||||
|
||||
await ctx.newExif({ assetId: asset.id, city: 'Austin', country: 'USA' });
|
||||
const auth = factory.auth({ sharedLink: { id: sharedLinkId, showExif: false } });
|
||||
const rawResponse = await sut.getTimeBucket(auth, { albumId: album.id, timeBucket: '1970-02-01', isTrashed: true });
|
||||
const response = JSON.parse(rawResponse);
|
||||
expect(response).not.toEqual(expect.objectContaining({ city: expect.any(Array), country: expect.any(Array) }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -179,8 +179,8 @@ export class TimelineMonth {
|
||||
);
|
||||
|
||||
const timelineAsset: TimelineAsset = {
|
||||
city: bucketAssets.city[i],
|
||||
country: bucketAssets.country[i],
|
||||
city: bucketAssets.city?.[i] ?? null,
|
||||
country: bucketAssets.country?.[i] ?? null,
|
||||
duration: bucketAssets.duration[i],
|
||||
id: bucketAssets.id[i],
|
||||
visibility: bucketAssets.visibility[i],
|
||||
|
||||
@@ -76,8 +76,8 @@ export const toResponseDto = (...timelineAsset: TimelineAsset[]) => {
|
||||
};
|
||||
for (const asset of timelineAsset) {
|
||||
const fileCreatedAt = fromTimelinePlainDateTime(asset.fileCreatedAt).toISO();
|
||||
bucketAssets.city.push(asset.city);
|
||||
bucketAssets.country.push(asset.country);
|
||||
bucketAssets.city?.push(asset.city);
|
||||
bucketAssets.country?.push(asset.country);
|
||||
bucketAssets.duration.push(asset.duration!);
|
||||
bucketAssets.id.push(asset.id);
|
||||
bucketAssets.visibility.push(asset.visibility);
|
||||
|
||||
Reference in New Issue
Block a user