11import { Injectable } from '@nestjs/common' ;
2- import { Insertable , Kysely , NotNull , Selectable , sql , Updateable , UpdateResult } from 'kysely' ;
2+ import { ExpressionBuilder , Insertable , Kysely , NotNull , Selectable , sql , Updateable , UpdateResult } from 'kysely' ;
33import { isEmpty , isUndefined , omitBy } from 'lodash' ;
44import { InjectKysely } from 'nestjs-kysely' ;
5- import { Stack } from 'src/database' ;
5+ import { LockableProperty , Stack } from 'src/database' ;
66import { Chunked , ChunkedArray , DummyValue , GenerateSql } from 'src/decorators' ;
77import { AuthDto } from 'src/dtos/auth.dto' ;
88import { AssetFileType , AssetMetadataKey , AssetOrder , AssetStatus , AssetType , AssetVisibility } from 'src/enum' ;
@@ -113,51 +113,77 @@ interface GetByIdsRelations {
113113 tags ?: boolean ;
114114}
115115
116+ const distinctLocked = < T extends LockableProperty [ ] | null > ( eb : ExpressionBuilder < DB , 'asset_exif' > , columns : T ) =>
117+ sql < T > `nullif(array(select distinct unnest(${ eb . ref ( 'asset_exif.lockedProperties' ) } || ${ columns } )), '{}')` ;
118+
116119@Injectable ( )
117120export class AssetRepository {
118121 constructor ( @InjectKysely ( ) private db : Kysely < DB > ) { }
119122
120- async upsertExif ( exif : Insertable < AssetExifTable > ) : Promise < void > {
121- const value = { ...exif , assetId : asUuid ( exif . assetId ) } ;
123+ @GenerateSql ( {
124+ params : [
125+ { dateTimeOriginal : DummyValue . DATE , lockedProperties : [ 'dateTimeOriginal' ] } ,
126+ { lockedPropertiesBehavior : 'append' } ,
127+ ] ,
128+ } )
129+ async upsertExif (
130+ exif : Insertable < AssetExifTable > ,
131+ { lockedPropertiesBehavior } : { lockedPropertiesBehavior : 'override' | 'append' | 'skip' } ,
132+ ) : Promise < void > {
122133 await this . db
123134 . insertInto ( 'asset_exif' )
124- . values ( value )
135+ . values ( exif )
125136 . onConflict ( ( oc ) =>
126- oc . column ( 'assetId' ) . doUpdateSet ( ( eb ) =>
127- removeUndefinedKeys (
128- {
129- description : eb . ref ( 'excluded.description' ) ,
130- exifImageWidth : eb . ref ( 'excluded.exifImageWidth' ) ,
131- exifImageHeight : eb . ref ( 'excluded.exifImageHeight' ) ,
132- fileSizeInByte : eb . ref ( 'excluded.fileSizeInByte' ) ,
133- orientation : eb . ref ( 'excluded.orientation' ) ,
134- dateTimeOriginal : eb . ref ( 'excluded.dateTimeOriginal' ) ,
135- modifyDate : eb . ref ( 'excluded.modifyDate' ) ,
136- timeZone : eb . ref ( 'excluded.timeZone' ) ,
137- latitude : eb . ref ( 'excluded.latitude' ) ,
138- longitude : eb . ref ( 'excluded.longitude' ) ,
139- projectionType : eb . ref ( 'excluded.projectionType' ) ,
140- city : eb . ref ( 'excluded.city' ) ,
141- livePhotoCID : eb . ref ( 'excluded.livePhotoCID' ) ,
142- autoStackId : eb . ref ( 'excluded.autoStackId' ) ,
143- state : eb . ref ( 'excluded.state' ) ,
144- country : eb . ref ( 'excluded.country' ) ,
145- make : eb . ref ( 'excluded.make' ) ,
146- model : eb . ref ( 'excluded.model' ) ,
147- lensModel : eb . ref ( 'excluded.lensModel' ) ,
148- fNumber : eb . ref ( 'excluded.fNumber' ) ,
149- focalLength : eb . ref ( 'excluded.focalLength' ) ,
150- iso : eb . ref ( 'excluded.iso' ) ,
151- exposureTime : eb . ref ( 'excluded.exposureTime' ) ,
152- profileDescription : eb . ref ( 'excluded.profileDescription' ) ,
153- colorspace : eb . ref ( 'excluded.colorspace' ) ,
154- bitsPerSample : eb . ref ( 'excluded.bitsPerSample' ) ,
155- rating : eb . ref ( 'excluded.rating' ) ,
156- fps : eb . ref ( 'excluded.fps' ) ,
157- } ,
158- value ,
159- ) ,
160- ) ,
137+ oc . column ( 'assetId' ) . doUpdateSet ( ( eb ) => {
138+ const updateLocked = < T extends keyof AssetExifTable > ( col : T ) => eb . ref ( `excluded.${ col } ` ) ;
139+ const skipLocked = < T extends keyof AssetExifTable > ( col : T ) =>
140+ eb
141+ . case ( )
142+ . when ( sql `${ col } ` , '=' , eb . fn . any ( 'asset_exif.lockedProperties' ) )
143+ . then ( eb . ref ( `asset_exif.${ col } ` ) )
144+ . else ( eb . ref ( `excluded.${ col } ` ) )
145+ . end ( ) ;
146+ const ref = lockedPropertiesBehavior === 'skip' ? skipLocked : updateLocked ;
147+ return {
148+ ...removeUndefinedKeys (
149+ {
150+ description : ref ( 'description' ) ,
151+ exifImageWidth : ref ( 'exifImageWidth' ) ,
152+ exifImageHeight : ref ( 'exifImageHeight' ) ,
153+ fileSizeInByte : ref ( 'fileSizeInByte' ) ,
154+ orientation : ref ( 'orientation' ) ,
155+ dateTimeOriginal : ref ( 'dateTimeOriginal' ) ,
156+ modifyDate : ref ( 'modifyDate' ) ,
157+ timeZone : ref ( 'timeZone' ) ,
158+ latitude : ref ( 'latitude' ) ,
159+ longitude : ref ( 'longitude' ) ,
160+ projectionType : ref ( 'projectionType' ) ,
161+ city : ref ( 'city' ) ,
162+ livePhotoCID : ref ( 'livePhotoCID' ) ,
163+ autoStackId : ref ( 'autoStackId' ) ,
164+ state : ref ( 'state' ) ,
165+ country : ref ( 'country' ) ,
166+ make : ref ( 'make' ) ,
167+ model : ref ( 'model' ) ,
168+ lensModel : ref ( 'lensModel' ) ,
169+ fNumber : ref ( 'fNumber' ) ,
170+ focalLength : ref ( 'focalLength' ) ,
171+ iso : ref ( 'iso' ) ,
172+ exposureTime : ref ( 'exposureTime' ) ,
173+ profileDescription : ref ( 'profileDescription' ) ,
174+ colorspace : ref ( 'colorspace' ) ,
175+ bitsPerSample : ref ( 'bitsPerSample' ) ,
176+ rating : ref ( 'rating' ) ,
177+ fps : ref ( 'fps' ) ,
178+ lockedProperties :
179+ lockedPropertiesBehavior === 'append'
180+ ? distinctLocked ( eb , exif . lockedProperties ?? null )
181+ : ref ( 'lockedProperties' ) ,
182+ } ,
183+ exif ,
184+ ) ,
185+ } ;
186+ } ) ,
161187 )
162188 . execute ( ) ;
163189 }
@@ -169,19 +195,26 @@ export class AssetRepository {
169195 return ;
170196 }
171197
172- await this . db . updateTable ( 'asset_exif' ) . set ( options ) . where ( 'assetId' , 'in' , ids ) . execute ( ) ;
198+ await this . db
199+ . updateTable ( 'asset_exif' )
200+ . set ( ( eb ) => ( {
201+ ...options ,
202+ lockedProperties : distinctLocked ( eb , Object . keys ( options ) as LockableProperty [ ] ) ,
203+ } ) )
204+ . where ( 'assetId' , 'in' , ids )
205+ . execute ( ) ;
173206 }
174207
175208 @GenerateSql ( { params : [ [ DummyValue . UUID ] , DummyValue . NUMBER , DummyValue . STRING ] } )
176209 @Chunked ( )
177- async updateDateTimeOriginal (
178- ids : string [ ] ,
179- delta ?: number ,
180- timeZone ?: string ,
181- ) : Promise < { assetId : string ; dateTimeOriginal : Date | null ; timeZone : string | null } [ ] > {
182- return await this . db
210+ updateDateTimeOriginal ( ids : string [ ] , delta ?: number , timeZone ?: string ) {
211+ return this . db
183212 . updateTable ( 'asset_exif' )
184- . set ( { dateTimeOriginal : sql `"dateTimeOriginal" + ${ ( delta ?? 0 ) + ' minute' } ::interval` , timeZone } )
213+ . set ( ( eb ) => ( {
214+ dateTimeOriginal : sql `"dateTimeOriginal" + ${ ( delta ?? 0 ) + ' minute' } ::interval` ,
215+ timeZone,
216+ lockedProperties : distinctLocked ( eb , [ 'dateTimeOriginal' , 'timeZone' ] ) ,
217+ } ) )
185218 . where ( 'assetId' , 'in' , ids )
186219 . returning ( [ 'assetId' , 'dateTimeOriginal' , 'timeZone' ] )
187220 . execute ( ) ;
0 commit comments