Skip to content

Commit 86a97ee

Browse files
authored
fix: inherit dedup key from shared post (#3349)
1 parent 311e431 commit 86a97ee

5 files changed

Lines changed: 66 additions & 42 deletions

File tree

__tests__/postHooks.ts

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DeepPartial } from 'typeorm';
1+
import { DataSource, DeepPartial } from 'typeorm';
22
import {
33
removeAllSpecialCharactersForDedup,
44
normalizeContentForDeduplication,
@@ -13,6 +13,13 @@ import {
1313
FreeformPost,
1414
ArticlePost,
1515
} from '../src/entity';
16+
import createOrGetConnection from '../src/db';
17+
18+
let con: DataSource;
19+
20+
beforeAll(async () => {
21+
con = await createOrGetConnection();
22+
});
1623

1724
describe('Post Deduplication Hooks', () => {
1825
describe('removeAllSpecialCharactersForDedup', () => {
@@ -83,91 +90,91 @@ describe('Post Deduplication Hooks', () => {
8390

8491
describe('generateDeduplicationKey', () => {
8592
describe('shared posts', () => {
86-
it('should use sharedPostId when available', () => {
93+
it('should use sharedPostId when available', async () => {
8794
const post: DeepPartial<SharePost> = {
8895
type: PostType.Share,
8996
sharedPostId: 'shared-123',
9097
title: 'Some title',
9198
};
9299

93-
const result = generateDeduplicationKey(post);
100+
const result = await generateDeduplicationKey(post, con);
94101
expect(result).toBe('shared-123');
95102
});
96103

97-
it('should return undefined when sharedPostId is missing', () => {
104+
it('should return undefined when sharedPostId is missing', async () => {
98105
const post: DeepPartial<SharePost> = {
99106
type: PostType.Share,
100107
title: 'Some title',
101108
};
102109

103-
const result = generateDeduplicationKey(post);
110+
const result = await generateDeduplicationKey(post, con);
104111
expect(result).toBeUndefined();
105112
});
106113

107-
it('should use sharedPostId even when title and content exist', () => {
114+
it('should use sharedPostId even when title and content exist', async () => {
108115
const post: DeepPartial<SharePost> = {
109116
type: PostType.Share,
110117
sharedPostId: 'shared-456',
111118
title: 'Some title',
112119
};
113120

114-
const result = generateDeduplicationKey(post);
121+
const result = await generateDeduplicationKey(post, con);
115122
expect(result).toBe('shared-456');
116123
});
117124
});
118125

119126
describe('freeform posts', () => {
120-
it('should use content hash when content is available', () => {
127+
it('should use content hash when content is available', async () => {
121128
const post: DeepPartial<FreeformPost> = {
122129
type: PostType.Freeform,
123130
content: 'This is my content',
124131
title: 'This is my title',
125132
};
126133

127-
const result = generateDeduplicationKey(post);
134+
const result = await generateDeduplicationKey(post, con);
128135
const expectedHash = generateContentHash(
129136
normalizeContentForDeduplication('This is my content'),
130137
);
131138
expect(result).toBe(expectedHash);
132139
});
133140

134-
it('should fall back to title hash when content is empty', () => {
141+
it('should fall back to title hash when content is empty', async () => {
135142
const post: DeepPartial<FreeformPost> = {
136143
type: PostType.Freeform,
137144
content: '',
138145
title: 'This is my title',
139146
};
140147

141-
const result = generateDeduplicationKey(post);
148+
const result = await generateDeduplicationKey(post, con);
142149
const expectedHash = generateContentHash(
143150
normalizeContentForDeduplication('This is my title'),
144151
);
145152
expect(result).toBe(expectedHash);
146153
});
147154

148-
it('should fall back to title hash when content is undefined', () => {
155+
it('should fall back to title hash when content is undefined', async () => {
149156
const post: DeepPartial<FreeformPost> = {
150157
type: PostType.Freeform,
151158
title: 'This is my title',
152159
};
153160

154-
const result = generateDeduplicationKey(post);
161+
const result = await generateDeduplicationKey(post, con);
155162
const expectedHash = generateContentHash(
156163
normalizeContentForDeduplication('This is my title'),
157164
);
158165
expect(result).toBe(expectedHash);
159166
});
160167

161-
it('should return undefined when both content and title are missing', () => {
168+
it('should return undefined when both content and title are missing', async () => {
162169
const post: DeepPartial<FreeformPost> = {
163170
type: PostType.Freeform,
164171
};
165172

166-
const result = generateDeduplicationKey(post);
173+
const result = await generateDeduplicationKey(post, con);
167174
expect(result).toBeUndefined();
168175
});
169176

170-
it('should normalize content before hashing', () => {
177+
it('should normalize content before hashing', async () => {
171178
const post1: DeepPartial<FreeformPost> = {
172179
type: PostType.Freeform,
173180
content: ' HELLO WORLD! ',
@@ -178,61 +185,61 @@ describe('Post Deduplication Hooks', () => {
178185
content: 'hello world',
179186
};
180187

181-
const result1 = generateDeduplicationKey(post1);
182-
const result2 = generateDeduplicationKey(post2);
188+
const result1 = await generateDeduplicationKey(post1, con);
189+
const result2 = await generateDeduplicationKey(post2, con);
183190
expect(result1).toBe(result2);
184191
});
185192
});
186193

187194
describe('other post types', () => {
188-
it('should return undefined for article posts', () => {
195+
it('should return undefined for article posts', async () => {
189196
const post: DeepPartial<ArticlePost> = {
190197
type: PostType.Article,
191198
title: 'Article title',
192199
url: 'https://example.com',
193200
};
194201

195-
const result = generateDeduplicationKey(post);
202+
const result = await generateDeduplicationKey(post, con);
196203
expect(result).toBeUndefined();
197204
});
198205

199-
it('should return undefined for welcome posts', () => {
206+
it('should return undefined for welcome posts', async () => {
200207
const post: DeepPartial<Post> = {
201208
type: PostType.Welcome,
202209
title: 'Welcome title',
203210
};
204211

205-
const result = generateDeduplicationKey(post);
212+
const result = await generateDeduplicationKey(post, con);
206213
expect(result).toBeUndefined();
207214
});
208215

209-
it('should return undefined for collection posts', () => {
216+
it('should return undefined for collection posts', async () => {
210217
const post: DeepPartial<Post> = {
211218
type: PostType.Collection,
212219
title: 'Collection title',
213220
};
214221

215-
const result = generateDeduplicationKey(post);
222+
const result = await generateDeduplicationKey(post, con);
216223
expect(result).toBeUndefined();
217224
});
218225

219-
it('should return undefined for brief posts', () => {
226+
it('should return undefined for brief posts', async () => {
220227
const post: DeepPartial<Post> = {
221228
type: PostType.Brief,
222229
title: 'Brief title',
223230
};
224231

225-
const result = generateDeduplicationKey(post);
232+
const result = await generateDeduplicationKey(post, con);
226233
expect(result).toBeUndefined();
227234
});
228235

229-
it('should return undefined for video posts', () => {
236+
it('should return undefined for video posts', async () => {
230237
const post: DeepPartial<Post> = {
231238
type: PostType.VideoYouTube,
232239
title: 'Video title',
233240
};
234241

235-
const result = generateDeduplicationKey(post);
242+
const result = await generateDeduplicationKey(post, con);
236243
expect(result).toBeUndefined();
237244
});
238245
});
@@ -248,7 +255,7 @@ describe('Post Deduplication Hooks', () => {
248255
},
249256
};
250257

251-
const result = await applyDeduplicationHook(post);
258+
const result = await applyDeduplicationHook(post, con);
252259

253260
expect(result.flags?.dedupKey).toBeDefined();
254261
expect(result.flags?.visible).toBe(true); // Preserve existing flags
@@ -267,7 +274,7 @@ describe('Post Deduplication Hooks', () => {
267274
},
268275
};
269276

270-
const result = await applyDeduplicationHook(post);
277+
const result = await applyDeduplicationHook(post, con);
271278

272279
expect(result.flags?.dedupKey).toBe('shared-123');
273280
expect(result.flags?.visible).toBe(true);
@@ -284,7 +291,7 @@ describe('Post Deduplication Hooks', () => {
284291
},
285292
};
286293

287-
const result = await applyDeduplicationHook(post);
294+
const result = await applyDeduplicationHook(post, con);
288295

289296
expect(result).toEqual(post); // Should be unchanged
290297
expect(result.flags?.dedupKey).toBeUndefined();
@@ -296,7 +303,7 @@ describe('Post Deduplication Hooks', () => {
296303
content: 'test content',
297304
};
298305

299-
const result = await applyDeduplicationHook(post);
306+
const result = await applyDeduplicationHook(post, con);
300307

301308
expect(result.flags?.dedupKey).toBeDefined();
302309
expect(typeof result.flags?.dedupKey).toBe('string');
@@ -309,7 +316,7 @@ describe('Post Deduplication Hooks', () => {
309316
flags: {},
310317
};
311318

312-
const result = await applyDeduplicationHook(post);
319+
const result = await applyDeduplicationHook(post, con);
313320

314321
expect(result.flags?.dedupKey).toBeDefined();
315322
expect(typeof result.flags?.dedupKey).toBe('string');

bin/updateDedupKey.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const main = async () => {
3737
for (const post of posts) {
3838
try {
3939
// Apply deduplication hook to generate dedup key
40-
const updatedPost = await applyDeduplicationHook(post);
40+
const updatedPost = await applyDeduplicationHook(post, con);
4141

4242
// Check if dedup key was generated
4343
if (

src/common/post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ export const createSourcePostModeration = async ({
481481
});
482482

483483
const content = `${args.title} ${args.content}`.trim();
484-
const dedupKey = generateDeduplicationKey(args);
484+
const dedupKey = await generateDeduplicationKey(args, con);
485485

486486
const [warningReason, vordr] = await Promise.all([
487487
getModerationWarningFlag({

src/entity/posts/hooks.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,10 @@ export const generateContentHash = (content: string): string => {
123123
/**
124124
* Generate deduplication key based on post type and content
125125
*/
126-
export const generateDeduplicationKey = (
126+
export const generateDeduplicationKey = async (
127127
post: DeepPartial<Post>,
128-
): string | undefined => {
128+
con: DataSource | EntityManager,
129+
): Promise<string | undefined> => {
129130
if (!post.type || ![PostType.Share, PostType.Freeform].includes(post.type)) {
130131
return undefined;
131132
}
@@ -135,7 +136,17 @@ export const generateDeduplicationKey = (
135136
post.type === PostType.Share &&
136137
(post as DeepPartial<SharePost>).sharedPostId
137138
) {
138-
return (post as DeepPartial<SharePost>).sharedPostId;
139+
const sharedPost = await con.getRepository(Post).findOne({
140+
where: {
141+
id: (post as DeepPartial<SharePost>).sharedPostId,
142+
},
143+
select: ['flags'],
144+
});
145+
146+
return (
147+
sharedPost?.flags.dedupKey ||
148+
(post as DeepPartial<SharePost>).sharedPostId
149+
);
139150
}
140151

141152
// For freeform posts, generate hash from content or title
@@ -169,8 +180,9 @@ export const generateDeduplicationKey = (
169180
*/
170181
export const applyDeduplicationHook = async <T extends DeepPartial<Post>>(
171182
post: T,
183+
con: DataSource | EntityManager,
172184
): Promise<T> => {
173-
const dedupKey = generateDeduplicationKey(post);
185+
const dedupKey = await generateDeduplicationKey(post, con);
174186
if (dedupKey) {
175187
return {
176188
...post,
@@ -193,9 +205,13 @@ export const applyDeduplicationHookForUpdate = async <
193205
>(
194206
post: T,
195207
existingPost: DeepPartial<Post>,
208+
con: DataSource | EntityManager,
196209
): Promise<T> => {
197-
const dedupKey = generateDeduplicationKey({ ...existingPost, ...post });
198-
if (dedupKey) {
210+
const dedupKey = await generateDeduplicationKey(
211+
{ ...existingPost, ...post },
212+
con,
213+
);
214+
if (dedupKey !== undefined) {
199215
return {
200216
...post,
201217
flags: {

src/entity/posts/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export const preparePostForInsert = async <T extends DeepPartial<Post>>(
308308
context: PreparePostContext,
309309
): Promise<T> => {
310310
let preparedPost = await applyVordrHook(post, context);
311-
preparedPost = await applyDeduplicationHook(preparedPost);
311+
preparedPost = await applyDeduplicationHook(preparedPost, context.con);
312312

313313
return preparedPost;
314314
};
@@ -330,6 +330,7 @@ export const preparePostForUpdate = async <T extends DeepPartial<Post>>(
330330
preparedUpdates = await applyDeduplicationHookForUpdate(
331331
preparedUpdates,
332332
existingPost,
333+
context.con,
333334
);
334335
return preparedUpdates;
335336
};

0 commit comments

Comments
 (0)