diff --git a/src/providers/mssql/queries/category.js b/src/providers/mssql/queries/category.js index 5c7f074..1838bf6 100644 --- a/src/providers/mssql/queries/category.js +++ b/src/providers/mssql/queries/category.js @@ -206,3 +206,37 @@ WHERE ca.category_id = @categoryId ORDER BY a.id ASC `; +export const TOUCH_SUBTREE_CATEGORIES = ` +;WITH cats AS ( + SELECT id FROM category WHERE id = @id + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id +) +UPDATE category +SET updated_at = SYSUTCDATETIME() +WHERE id IN (SELECT id FROM cats) +`; + +export const TOUCH_SUBTREE_ARTICLES = ` +;WITH cats AS ( + SELECT id FROM category WHERE id = @id + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id +) +UPDATE article +SET updated_at = SYSUTCDATETIME() +WHERE id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL +) +`; + diff --git a/src/providers/sqlite3/queries/category.js b/src/providers/sqlite3/queries/category.js index 75d52cf..df4cb69 100644 --- a/src/providers/sqlite3/queries/category.js +++ b/src/providers/sqlite3/queries/category.js @@ -129,4 +129,38 @@ FROM categoryarticle ca INNER JOIN article a ON a.id = ca.article_id WHERE ca.category_id IN (SELECT id FROM tree) AND a.parentarticle_id IS NULL +`; + +export const TOUCH_SUBTREE_CATEGORIES = ` +WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @id + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id +) +UPDATE category +SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') +WHERE id IN (SELECT id FROM cats) +`; + +export const TOUCH_SUBTREE_ARTICLES = ` +WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @id + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id +) +UPDATE article +SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') +WHERE id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL +) `; \ No newline at end of file diff --git a/src/store/category/move.js b/src/store/category/move.js index 29663f0..016b9ca 100644 --- a/src/store/category/move.js +++ b/src/store/category/move.js @@ -2,11 +2,15 @@ import { GET_NEXT_SIBLING_SORT as MSSQL_GET_NEXT_SIBLING_SORT, IS_ANCESTOR as MSSQL_IS_ANCESTOR, MOVE as MSSQL_MOVE, + TOUCH_SUBTREE_CATEGORIES as MSSQL_TOUCH_SUBTREE_CATEGORIES, + TOUCH_SUBTREE_ARTICLES as MSSQL_TOUCH_SUBTREE_ARTICLES, } from '../../providers/mssql/queries/category.js'; import { GET_NEXT_SIBLING_SORT as SQLITE_GET_NEXT_SIBLING_SORT, IS_ANCESTOR as SQLITE_IS_ANCESTOR, MOVE as SQLITE_MOVE, + TOUCH_SUBTREE_CATEGORIES as SQLITE_TOUCH_SUBTREE_CATEGORIES, + TOUCH_SUBTREE_ARTICLES as SQLITE_TOUCH_SUBTREE_ARTICLES, } from '../../providers/sqlite3/queries/category.js'; import resolveCategoryAccess from './resolveCategoryAccess.js'; import makeRunner from '../runner.js'; @@ -20,6 +24,12 @@ export default function move(provider) { ? SQLITE_GET_NEXT_SIBLING_SORT : MSSQL_GET_NEXT_SIBLING_SORT; const moveSql = isSqlite ? SQLITE_MOVE : MSSQL_MOVE; + const touchCategoriesSql = isSqlite + ? SQLITE_TOUCH_SUBTREE_CATEGORIES + : MSSQL_TOUCH_SUBTREE_CATEGORIES; + const touchArticlesSql = isSqlite + ? SQLITE_TOUCH_SUBTREE_ARTICLES + : MSSQL_TOUCH_SUBTREE_ARTICLES; /** * Moves a category to a new parent, preventing cycles and cross-company moves. @@ -70,6 +80,9 @@ export default function move(provider) { throw new Error('Category not found'); } + await runner.query(touchCategoriesSql, { id }); + await runner.query(touchArticlesSql, { id }); + return { id, parentId, sort }; }; diff --git a/test/category.test.js b/test/category.test.js index 86c6902..1e157ba 100644 --- a/test/category.test.js +++ b/test/category.test.js @@ -225,6 +225,42 @@ describe('store.category', () => { /Not authorized|Parent not found/, ); }); + + it('touches updated_at on moved category, subcategories, and associated articles', async () => { + const topTarget = await create('Move Target'); + const movedCat = await create('Moved Cat'); + const subCat = await create('Sub Cat', movedCat.id); + const artMoved = await createArticle(store, tenant, { parentId: movedCat.id, name: 'Article in Moved' }); + const artSub = await createArticle(store, tenant, { parentId: subCat.id, name: 'Article in Sub' }); + + const oldDate = '2020-01-01T00:00:00.000Z'; + await ctx.provider.query( + `UPDATE category SET updated_at = '${oldDate}' WHERE id IN (${movedCat.id}, ${subCat.id})` + ); + await ctx.provider.query( + `UPDATE article SET updated_at = '${oldDate}' WHERE id IN (${artMoved.id}, ${artSub.id})` + ); + + await store.category.move({ + googleAuthId: tenant.googleAuthId, + id: movedCat.id, + parentId: topTarget.id, + }); + + const cats = await ctx.provider.query( + `SELECT id, updated_at FROM category WHERE id IN (${movedCat.id}, ${subCat.id})` + ); + for (const row of cats.rows) { + assert.ok(new Date(row.updated_at) > new Date(oldDate), `Category ${row.id} updated_at was touched`); + } + + const arts = await ctx.provider.query( + `SELECT id, updated_at FROM article WHERE id IN (${artMoved.id}, ${artSub.id})` + ); + for (const row of arts.rows) { + assert.ok(new Date(row.updated_at) > new Date(oldDate), `Article ${row.id} updated_at was touched`); + } + }); }); describe('order', () => {