Skip to content

Commit

Permalink
feat: When photos are trashed, delete the reference to albums
Browse files Browse the repository at this point in the history
  • Loading branch information
cballevre committed Oct 13, 2023
1 parent 6f14f42 commit 210faf6
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 18 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"mui-bottom-sheet": "https://github.com/cozy/mui-bottom-sheet.git#v1.0.6",
"node-fetch": "2.6.7",
"node-polyglot": "2.4.2",
"p-limit": "2.3.0",
"popper.js": "1.16.1",
"pouchdb-adapter-cordova-sqlite": "^2.0.7",
"pouchdb-adapter-idb": "7.2.2",
Expand Down
51 changes: 51 additions & 0 deletions src/photos/lib/onPhotoTrashed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { getReferencedBy } from 'cozy-client'
import log from 'cozy-logger'
import pLimit from 'p-limit'

import { buildPhotosTrashedWithReferencedBy } from 'photos/queries/queries'
import { DOCTYPE_ALBUMS, DOCTYPE_FILES } from 'drive/lib/doctypes'

const onPhotoTrashed = async client => {
const photosTrashedQuery = buildPhotosTrashedWithReferencedBy()
try {
const photos = await client.queryAll(
photosTrashedQuery.definition,
photosTrashedQuery.options
)

if (photos.length > 0) {
log(
'info',
`Start deleting album references on ${photos.length} trashed photos`
)
const fileCollection = client.collection(DOCTYPE_FILES)
const limit = pLimit(20)

const removePromises = []
photos.forEach(photo => {
const ablumsReferenced = getReferencedBy(photo, DOCTYPE_ALBUMS)
if (ablumsReferenced.length > 0) {
const albumsReferencedNormalized = ablumsReferenced.map(c => ({
_id: c.id,
_type: c.type
}))
removePromises.push(
limit(() =>
fileCollection.removeReferencedBy(
photo,
albumsReferencedNormalized
)
)
)
}
})

await Promise.all(removePromises)
}
} catch (e) {
log('error', 'Failure to delete references with albums on trashed photos')
log('error', e)
}
}

export { onPhotoTrashed }
117 changes: 117 additions & 0 deletions src/photos/lib/onPhotoTrashed.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { getReferencedBy } from 'cozy-client'
import log from 'cozy-logger'

import { onPhotoTrashed } from 'photos/lib/onPhotoTrashed'

jest.mock('cozy-logger')
jest.mock('cozy-client', () => ({
...jest.requireActual('cozy-client'),
getReferencedBy: jest.fn()
}))

describe('onPhotoTrashed', () => {
beforeEach(() => {
getReferencedBy.mockReturnValue([])
})

it('should not remove album references if there are no trashed photos', async () => {
const removeSpy = jest.fn()
const mockClient = {
queryAll: () => [],
collection: () => ({ removeReferencedBy: removeSpy })
}

await onPhotoTrashed(mockClient)

expect(removeSpy).toHaveBeenCalledTimes(0)
expect(log).toBeCalledTimes(0)
})

it('should remove album references from trashed photos', async () => {
const removeSpy = jest.fn()
const mockClient = {
queryAll: () => [
{
_id: 'photo_1'
},
{
_id: 'photo_2'
}
],
collection: () => ({ removeReferencedBy: removeSpy })
}
getReferencedBy
.mockReturnValueOnce([
{
id: '123',
type: 'albums'
},
{
id: '456',
type: 'albums'
}
])
.mockReturnValueOnce([])

await onPhotoTrashed(mockClient)

expect(removeSpy).toBeCalledTimes(1)
expect(removeSpy).toBeCalledWith({ _id: 'photo_1' }, [
{
_id: '123',
_type: 'albums'
},
{
_id: '456',
_type: 'albums'
}
])
})

it('should not remove album references if there are no references', async () => {
const removeSpy = jest.fn()
const mockClient = {
queryAll: [
{
_id: 'photo_1'
}
],
collection: () => ({ removeReferencedBy: removeSpy })
}

await onPhotoTrashed(mockClient)

expect(removeSpy).toHaveBeenCalledTimes(0)
})

it('should handle errors when removing album references', async () => {
const error = new Error('Failed to remove album references')
const removeSpy = jest.fn(() => {
throw new Error('Failed to remove album references')
})
const mockClient = {
queryAll: () => [
{
_id: 'photo_1'
}
],
collection: () => ({ removeReferencedBy: removeSpy })
}
getReferencedBy.mockReturnValue([
{
id: '123',
type: 'albums'
}
])

await onPhotoTrashed(mockClient)

expect(removeSpy).toBeCalledWith({ _id: 'photo_1' }, [
{
_id: '123',
_type: 'albums'
}
])
expect(log).toHaveBeenLastCalledWith('error', error)
})
})
21 changes: 16 additions & 5 deletions src/photos/queries/queries.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { Q, fetchPolicies } from 'cozy-client'

import { DOCTYPE_ALBUMS } from 'drive/lib/doctypes'
import { DOCTYPE_ALBUMS, DOCTYPE_FILES } from 'drive/lib/doctypes'

const older30s = 30 * 1000

// Export to doctypes
const FILES_DOCTYPE = 'io.cozy.files'

export const buildTimelineQuery = () => ({
definition: Q(FILES_DOCTYPE)
definition: Q(DOCTYPE_FILES)
.where({
class: 'image',
'metadata.datetime': {
Expand Down Expand Up @@ -44,6 +41,20 @@ export const buildTimelineQuery = () => ({
}
})

export const buildPhotosTrashedWithReferencedBy = () => ({
definition: Q(DOCTYPE_FILES).partialIndex({
class: 'image',
trashed: true,
referenced_by: {
$exists: true
}
}),
options: {
as: `${DOCTYPE_FILES}/class/image/trashed/referenced_by`,
fetchPolicy: fetchPolicies.olderThan(older30s)
}
})

// Albums doctype -------------

export const buildAlbumsQuery = albumId => ({
Expand Down
13 changes: 7 additions & 6 deletions src/photos/targets/manifest.webapp
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,19 @@
"file": "services/onPhotoUpload/photos.js",
"trigger": "@event io.cozy.files:CREATED:image:class",
"debounce": "5m"
},
"onPhotoTrashed": {
"type": "node",
"file": "services/onPhotoTrashed/photos.js",
"trigger": "@event io.cozy.files:UPDATED:image:class",
"debounce": "5m"
}
},
"permissions": {
"files": {
"description": "Required for photo access",
"type": "io.cozy.files",
"verbs": [
"GET",
"POST",
"PUT",
"PATCH"
]
"verbs": ["ALL"]
},
"apps": {
"description": "Required by the cozy-bar to display the icons of the apps",
Expand Down
30 changes: 30 additions & 0 deletions src/photos/targets/services/onPhotoTrashed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import fetch from 'node-fetch'
global.fetch = fetch

import CozyClient from 'cozy-client'
import { Document } from 'cozy-doctypes'
import log from 'cozy-logger'

import { onPhotoTrashed } from 'photos/lib/onPhotoTrashed'

const attachProcessEventHandlers = () => {
process.on('uncaughtException', err => {
log('warn', JSON.stringify(err.stack))
})

process.on('unhandledRejection', err => {
log('warn', JSON.stringify(err.stack))
})
}

const main = async () => {
attachProcessEventHandlers()
const client = CozyClient.fromEnv(process.env)
Document.registerClient(client)
await onPhotoTrashed(client)
}

main().catch(e => {
log('critical', e)
process.exit(1)
})
14 changes: 7 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18473,20 +18473,20 @@ p-is-promise@^2.0.0:
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==

p-limit@2.3.0, p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"

p-limit@^1.0.0, p-limit@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
dependencies:
p-try "^1.0.0"

p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"

p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
Expand Down

0 comments on commit 210faf6

Please sign in to comment.