Purge: Hide missing files in UI and set new primary if needed #917
This commit is contained in:
parent
4018105796
commit
274c9347f5
8 changed files with 337 additions and 203 deletions
|
@ -1,197 +1,201 @@
|
|||
<template>
|
||||
<div class="p-tab p-tab-photo-files">
|
||||
<v-expansion-panel expand class="pa-0 elevation-0 secondary" :value="state">
|
||||
<v-expansion-panel-content v-for="(file, index) in model.fileModels()" :key="index"
|
||||
class="pa-0 elevation-0 secondary-light" style="margin-top: 1px;">
|
||||
<template #header>
|
||||
<div class="caption">{{ file.baseName(70) }}</div>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text class="white pa-0">
|
||||
<v-container fluid class="pa-0">
|
||||
<v-layout row wrap fill-height
|
||||
align-center
|
||||
justify-center>
|
||||
<v-flex xs12 class="pa-0">
|
||||
<div class="v-table__overflow">
|
||||
<table class="v-datatable v-table theme--light photo-files">
|
||||
<tbody>
|
||||
<tr v-if="file.Type === 'jpg'">
|
||||
<td>
|
||||
<translate>Preview</translate>
|
||||
</td>
|
||||
<td>
|
||||
<v-img :src="file.thumbnailUrl('tile_224')"
|
||||
aspect-ratio="1"
|
||||
max-width="112"
|
||||
max-height="112"
|
||||
class="accent lighten-2 elevation-0 clickable"
|
||||
@click.exact="openFile(file)"
|
||||
>
|
||||
</v-img>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate>Actions</translate>
|
||||
</td>
|
||||
<td>
|
||||
<v-btn small depressed dark color="primary-button" class="ma-0 action-download"
|
||||
@click.stop.prevent="downloadFile(file)">
|
||||
<translate>Download</translate>
|
||||
</v-btn>
|
||||
<v-btn v-if="features.edit && file.Type === 'jpg' && !file.Primary" small depressed dark color="primary-button"
|
||||
class="ma-0 action-primary"
|
||||
@click.stop.prevent="primaryFile(file)">
|
||||
<template v-for="(file, index) in model.fileModels()">
|
||||
<v-expansion-panel-content v-if="!file.Missing" :key="index" class="pa-0 elevation-0 secondary-light"
|
||||
style="margin-top: 1px;">
|
||||
<template #header>
|
||||
<div class="caption">{{ file.baseName(70) }}</div>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text class="white pa-0">
|
||||
<v-container fluid class="pa-0">
|
||||
<v-layout row wrap fill-height
|
||||
align-center
|
||||
justify-center>
|
||||
<v-flex xs12 class="pa-0">
|
||||
<div class="v-table__overflow">
|
||||
<table class="v-datatable v-table theme--light photo-files">
|
||||
<tbody>
|
||||
<tr v-if="file.Type === 'jpg'">
|
||||
<td>
|
||||
<translate>Preview</translate>
|
||||
</td>
|
||||
<td>
|
||||
<v-img :src="file.thumbnailUrl('tile_224')"
|
||||
aspect-ratio="1"
|
||||
max-width="112"
|
||||
max-height="112"
|
||||
class="accent lighten-2 elevation-0 clickable"
|
||||
@click.exact="openFile(file)"
|
||||
>
|
||||
</v-img>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate>Actions</translate>
|
||||
</td>
|
||||
<td>
|
||||
<v-btn small depressed dark color="primary-button" class="ma-0 action-download"
|
||||
@click.stop.prevent="downloadFile(file)">
|
||||
<translate>Download</translate>
|
||||
</v-btn>
|
||||
<v-btn v-if="features.edit && file.Type === 'jpg' && !file.Primary" small depressed dark
|
||||
color="primary-button"
|
||||
class="ma-0 action-primary"
|
||||
@click.stop.prevent="primaryFile(file)">
|
||||
<translate>Primary</translate>
|
||||
</v-btn>
|
||||
<v-btn v-if="features.edit && !file.Sidecar && !file.Primary && file.Root === '/'" small
|
||||
depressed dark color="primary-button"
|
||||
class="ma-0 action-unstack"
|
||||
@click.stop.prevent="unstackFile(file)">
|
||||
<translate>Unstack</translate>
|
||||
</v-btn>
|
||||
<v-btn v-if="features.delete && !file.Primary" small depressed dark color="primary-button"
|
||||
class="ma-0 action-delete"
|
||||
@click.stop.prevent="showDeleteDialog(file)">
|
||||
<translate>Delete</translate>
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td title="Unique ID">
|
||||
UID
|
||||
</td>
|
||||
<td>{{ file.UID | uppercase }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.InstanceID" title="XMP">
|
||||
<td>
|
||||
<translate>Instance ID</translate>
|
||||
</td>
|
||||
<td>{{ file.InstanceID | uppercase }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td title="SHA-1">
|
||||
<translate>Hash</translate>
|
||||
</td>
|
||||
<td>{{ file.Hash }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Root.length > 1">
|
||||
<td>
|
||||
<translate>Storage Folder</translate>
|
||||
</td>
|
||||
<td>{{ file.Root | capitalize }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Name">
|
||||
<td>
|
||||
<translate>Name</translate>
|
||||
</td>
|
||||
<td>{{ file.Name }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.OriginalName">
|
||||
<td>
|
||||
<translate>Original Name</translate>
|
||||
</td>
|
||||
<td>{{ file.OriginalName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate>Size</translate>
|
||||
</td>
|
||||
<td>{{ file.sizeInfo() }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Type">
|
||||
<td>
|
||||
<translate>Type</translate>
|
||||
</td>
|
||||
<td>{{ file.typeInfo() }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Codec">
|
||||
<td>
|
||||
<translate>Codec</translate>
|
||||
</td>
|
||||
<td>{{ file.Codec | uppercase }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Primary">
|
||||
<td>
|
||||
<translate>Primary</translate>
|
||||
</v-btn>
|
||||
<v-btn v-if="features.edit && !file.Sidecar && !file.Primary && file.Root === '/'" small depressed dark color="primary-button"
|
||||
class="ma-0 action-unstack"
|
||||
@click.stop.prevent="unstackFile(file)">
|
||||
<translate>Unstack</translate>
|
||||
</v-btn>
|
||||
<v-btn v-if="features.delete && !file.Primary" small depressed dark color="primary-button"
|
||||
class="ma-0 action-delete"
|
||||
@click.stop.prevent="showDeleteDialog(file)">
|
||||
<translate>Delete</translate>
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td title="Unique ID">
|
||||
UID
|
||||
</td>
|
||||
<td>{{ file.UID | uppercase }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.InstanceID" title="XMP">
|
||||
<td>
|
||||
<translate>Instance ID</translate>
|
||||
</td>
|
||||
<td>{{ file.InstanceID | uppercase }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td title="SHA-1">
|
||||
<translate>Hash</translate>
|
||||
</td>
|
||||
<td>{{ file.Hash }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Root.length > 1">
|
||||
<td>
|
||||
<translate>Storage Folder</translate>
|
||||
</td>
|
||||
<td>{{ file.Root | capitalize }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Name">
|
||||
<td>
|
||||
<translate>Name</translate>
|
||||
</td>
|
||||
<td>{{ file.Name }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.OriginalName">
|
||||
<td>
|
||||
<translate>Original Name</translate>
|
||||
</td>
|
||||
<td>{{ file.OriginalName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate>Size</translate>
|
||||
</td>
|
||||
<td>{{ file.sizeInfo() }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Type">
|
||||
<td>
|
||||
<translate>Type</translate>
|
||||
</td>
|
||||
<td>{{ file.typeInfo() }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Codec">
|
||||
<td>
|
||||
<translate>Codec</translate>
|
||||
</td>
|
||||
<td>{{ file.Codec | uppercase }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Primary">
|
||||
<td>
|
||||
<translate>Primary</translate>
|
||||
</td>
|
||||
<td>
|
||||
<translate>Yes</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="file.Portrait">
|
||||
<td>
|
||||
<translate>Portrait</translate>
|
||||
</td>
|
||||
<td>
|
||||
<translate>Yes</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="file.Projection">
|
||||
<td>
|
||||
<translate>Projection</translate>
|
||||
</td>
|
||||
<td>{{ file.Projection | capitalize }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.AspectRatio">
|
||||
<td>
|
||||
<translate>Aspect Ratio</translate>
|
||||
</td>
|
||||
<td>{{ file.AspectRatio }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.MainColor">
|
||||
<td>
|
||||
<translate>Main Color</translate>
|
||||
</td>
|
||||
<td>{{ file.MainColor | capitalize }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Type === 'jpg'">
|
||||
<td>
|
||||
<translate>Chroma</translate>
|
||||
</td>
|
||||
<td>{{ file.Chroma }} / 100</td>
|
||||
</tr>
|
||||
<tr v-if="file.Error">
|
||||
<td>
|
||||
<translate>Error</translate>
|
||||
</td>
|
||||
<td>{{ file.Error }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Missing">
|
||||
<td>
|
||||
<translate>Missing</translate>
|
||||
</td>
|
||||
<td>
|
||||
<translate>Yes</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate>Added</translate>
|
||||
</td>
|
||||
<td>{{ formatTime(file.CreatedAt) }}
|
||||
<translate>in</translate>
|
||||
{{ Math.round(file.CreatedIn / 1000000) | number('0,0') }} ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="file.UpdatedIn">
|
||||
<td>
|
||||
<translate>Updated</translate>
|
||||
</td>
|
||||
<td>{{ formatTime(file.UpdatedAt) }}
|
||||
<translate>in</translate>
|
||||
{{ Math.round(file.UpdatedIn / 1000000) | number('0,0') }} ms
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-expansion-panel-content>
|
||||
</td>
|
||||
<td>
|
||||
<translate>Yes</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="file.Portrait">
|
||||
<td>
|
||||
<translate>Portrait</translate>
|
||||
</td>
|
||||
<td>
|
||||
<translate>Yes</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="file.Projection">
|
||||
<td>
|
||||
<translate>Projection</translate>
|
||||
</td>
|
||||
<td>{{ file.Projection | capitalize }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.AspectRatio">
|
||||
<td>
|
||||
<translate>Aspect Ratio</translate>
|
||||
</td>
|
||||
<td>{{ file.AspectRatio }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.MainColor">
|
||||
<td>
|
||||
<translate>Main Color</translate>
|
||||
</td>
|
||||
<td>{{ file.MainColor | capitalize }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Type === 'jpg'">
|
||||
<td>
|
||||
<translate>Chroma</translate>
|
||||
</td>
|
||||
<td>{{ file.Chroma }} / 100</td>
|
||||
</tr>
|
||||
<tr v-if="file.Error">
|
||||
<td>
|
||||
<translate>Error</translate>
|
||||
</td>
|
||||
<td>{{ file.Error }}</td>
|
||||
</tr>
|
||||
<tr v-if="file.Missing">
|
||||
<td>
|
||||
<translate>Missing</translate>
|
||||
</td>
|
||||
<td>
|
||||
<translate>Yes</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate>Added</translate>
|
||||
</td>
|
||||
<td>{{ formatTime(file.CreatedAt) }}
|
||||
<translate>in</translate>
|
||||
{{ Math.round(file.CreatedIn / 1000000) | number('0,0') }} ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="file.UpdatedIn">
|
||||
<td>
|
||||
<translate>Updated</translate>
|
||||
</td>
|
||||
<td>{{ formatTime(file.UpdatedAt) }}
|
||||
<translate>in</translate>
|
||||
{{ Math.round(file.UpdatedIn / 1000000) | number('0,0') }} ms
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-expansion-panel-content>
|
||||
</template>
|
||||
</v-expansion-panel>
|
||||
<p-file-delete-dialog :show="deleteFile.dialog" @cancel="closeDeleteDialog"
|
||||
@confirm="confirmDeleteFile"></p-file-delete-dialog>
|
||||
|
|
|
@ -188,9 +188,18 @@ func (m *File) Delete(permanently bool) error {
|
|||
|
||||
// Purge removes a file from the index by marking it as missing.
|
||||
func (m *File) Purge() error {
|
||||
deletedAt := Timestamp()
|
||||
m.FileMissing = true
|
||||
m.FilePrimary = false
|
||||
return Db().Unscoped().Exec("UPDATE files SET file_missing = 1, file_primary = 0 WHERE id = ?", m.ID).Error
|
||||
m.DeletedAt = &deletedAt
|
||||
return UnscopedDb().Exec("UPDATE files SET file_missing = 1, file_primary = 0, deleted_at = ? WHERE id = ?", &deletedAt, m.ID).Error
|
||||
}
|
||||
|
||||
// Found restores a previously purged file.
|
||||
func (m *File) Found() error {
|
||||
m.FileMissing = false
|
||||
m.DeletedAt = nil
|
||||
return UnscopedDb().Exec("UPDATE files SET file_missing = 0, deleted_at = NULL WHERE id = ?", m.ID).Error
|
||||
}
|
||||
|
||||
// AllFilesMissing returns true, if all files for the photo of this file are missing.
|
||||
|
|
|
@ -1042,6 +1042,43 @@ func (m *Photo) PrimaryFile() (File, error) {
|
|||
return PrimaryFile(m.PhotoUID)
|
||||
}
|
||||
|
||||
// SetPrimary sets a new primary file.
|
||||
func (m *Photo) SetPrimary(fileUID string) error {
|
||||
if m.PhotoUID == "" {
|
||||
return fmt.Errorf("photo uid is empty")
|
||||
}
|
||||
|
||||
var files []string
|
||||
|
||||
if fileUID != "" {
|
||||
// Do nothing.
|
||||
} else if err := Db().Model(File{}).
|
||||
Where("photo_uid = ? AND file_missing = 0 AND file_type = 'jpg'", m.PhotoUID).
|
||||
Order("file_width DESC").Limit(1).
|
||||
Pluck("file_uid", &files).Error; err != nil {
|
||||
return err
|
||||
} else if len(files) == 0 {
|
||||
return fmt.Errorf("photo %s has no jpegs", m.PhotoUID)
|
||||
} else {
|
||||
fileUID = files[0]
|
||||
}
|
||||
|
||||
if fileUID == "" {
|
||||
return fmt.Errorf("file uid is empty")
|
||||
}
|
||||
|
||||
Db().Model(File{}).Where("photo_uid = ? AND file_uid <> ?", m.PhotoUID, fileUID).UpdateColumn("file_primary", false)
|
||||
|
||||
if err := Db().Model(File{}).Where("photo_uid = ? AND file_uid = ?", m.PhotoUID, fileUID).UpdateColumn("file_primary", true).Error; err != nil {
|
||||
return err
|
||||
} else if m.PhotoQuality < 0 {
|
||||
m.PhotoQuality = 0
|
||||
return m.UpdateQuality()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapKey returns a key referencing time and location for indexing.
|
||||
func (m *Photo) MapKey() string {
|
||||
return MapKey(m.TakenAt, m.CellID)
|
||||
|
|
|
@ -910,3 +910,13 @@ func TestPhoto_Links(t *testing.T) {
|
|||
assert.Equal(t, "7jxf3jfn2k", links[0].LinkToken)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPhoto_SetPrimary(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("19800101_000002_D640C559")
|
||||
|
||||
if err := m.SetPrimary(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ func (w *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPhot
|
|||
continue
|
||||
}
|
||||
|
||||
if err := file.Update("FileMissing", false); err != nil {
|
||||
if err := file.Found(); err != nil {
|
||||
log.Errorf("purge: %s", err)
|
||||
} else {
|
||||
log.Infof("purge: found %s", txt.Quote(file.FileName))
|
||||
|
@ -102,12 +102,23 @@ func (w *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPhot
|
|||
continue
|
||||
}
|
||||
|
||||
wasPrimary := file.FilePrimary
|
||||
|
||||
if err := file.Purge(); err != nil {
|
||||
log.Errorf("purge: %s", err)
|
||||
} else {
|
||||
w.files.Remove(file.FileName, file.FileRoot)
|
||||
purgedFiles[fileName] = true
|
||||
log.Infof("purge: flagged file %s as missing", txt.Quote(file.FileName))
|
||||
continue
|
||||
}
|
||||
|
||||
w.files.Remove(file.FileName, file.FileRoot)
|
||||
purgedFiles[fileName] = true
|
||||
log.Infof("purge: flagged file %s as missing", txt.Quote(file.FileName))
|
||||
|
||||
if !wasPrimary {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := query.SetPhotoPrimary(file.PhotoUID, ""); err != nil {
|
||||
log.Warnf("purge: %s (set new primary)", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,6 +239,12 @@ func (w *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPhot
|
|||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
log.Info("purge: searching index for unassigned primary files")
|
||||
|
||||
if err := query.FixPrimaries(); err != nil {
|
||||
log.Errorf("purge: %s (find unassigned primaries)", err.Error())
|
||||
}
|
||||
|
||||
log.Info("purge: searching index for hidden media files")
|
||||
|
||||
if err := query.ResetPhotoQuality(); err != nil {
|
||||
|
|
|
@ -103,6 +103,26 @@ func RenameFile(srcRoot, srcName, destRoot, destName string) error {
|
|||
|
||||
// SetPhotoPrimary sets a new primary image file for a photo.
|
||||
func SetPhotoPrimary(photoUID, fileUID string) error {
|
||||
if photoUID == "" {
|
||||
return fmt.Errorf("photo uid is missing")
|
||||
}
|
||||
|
||||
var files []string
|
||||
|
||||
if fileUID != "" {
|
||||
// Do nothing.
|
||||
} else if err := Db().Model(entity.File{}).Where("photo_uid = ? AND file_missing = 0 AND file_type = 'jpg'", photoUID).Order("file_width DESC").Limit(1).Pluck("file_uid", &files).Error; err != nil {
|
||||
return err
|
||||
} else if len(files) == 0 {
|
||||
return fmt.Errorf("photo %s has no jpegs", photoUID)
|
||||
} else {
|
||||
fileUID = files[0]
|
||||
}
|
||||
|
||||
if fileUID == "" {
|
||||
return fmt.Errorf("file uid is missing")
|
||||
}
|
||||
|
||||
Db().Model(entity.File{}).Where("photo_uid = ? AND file_uid <> ?", photoUID, fileUID).UpdateColumn("file_primary", false)
|
||||
return Db().Model(entity.File{}).Where("photo_uid = ? AND file_uid = ?", photoUID, fileUID).UpdateColumn("file_primary", true).Error
|
||||
}
|
||||
|
|
|
@ -165,15 +165,29 @@ func TestFileByHash(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSetPhotoPrimary(t *testing.T) {
|
||||
assert.Equal(t, false, entity.FileFixturesExampleXMP.FilePrimary)
|
||||
t.Run("success", func(t *testing.T) {
|
||||
assert.Equal(t, false, entity.FileFixturesExampleXMP.FilePrimary)
|
||||
|
||||
err := SetPhotoPrimary("pt9jtdre2lvl0yh7", "ft2es49whhbnlqdn")
|
||||
err := SetPhotoPrimary("pt9jtdre2lvl0yh7", "ft2es49whhbnlqdn")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//TODO How to assert
|
||||
//assert.Equal(t, true, entity.FileFixturesExampleXMP.FilePrimary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("no_file_uid", func(t *testing.T) {
|
||||
err := SetPhotoPrimary("pt9jtdre2lvl0yh7", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("no_uid", func(t *testing.T) {
|
||||
err := SetPhotoPrimary("", "")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetFileError(t *testing.T) {
|
||||
|
|
|
@ -129,3 +129,26 @@ func PhotosOrphaned() (photos entity.Photos, err error) {
|
|||
|
||||
return photos, err
|
||||
}
|
||||
|
||||
// FixPrimaries tries to set a primary file for photos that have none.
|
||||
func FixPrimaries() error {
|
||||
var photos entity.Photos
|
||||
|
||||
if err := UnscopedDb().
|
||||
Raw(`SELECT * FROM photos WHERE
|
||||
deleted_at IS NULL
|
||||
AND id NOT IN (SELECT photo_id FROM files WHERE file_primary = true)`).
|
||||
Find(&photos).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range photos {
|
||||
log.Debugf("photo: finding new primary for %s", p.PhotoUID)
|
||||
|
||||
if err := p.SetPrimary(""); err != nil {
|
||||
log.Warnf("photo: %s (set new primary)", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue