chore: tweak note update handler

to prevent possible version corruption caused by deliver delay
This commit is contained in:
Nya Candy 2024-01-26 23:03:11 +08:00 committed by fly_mc
parent 678aa493de
commit 7dc31a3ac1
3 changed files with 49 additions and 17 deletions

View file

@ -68,6 +68,15 @@ export class NoteUpdateService {
* @param ps New note info * @param ps New note info
*/ */
async update(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, ps: Pick<MiNote, 'text' | 'cw' | 'updatedAt'>, quiet = false, updater?: MiUser) { async update(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, ps: Pick<MiNote, 'text' | 'cw' | 'updatedAt'>, quiet = false, updater?: MiUser) {
if (!ps.updatedAt) {
throw new Error('update time is required');
}
if (note.history?.findIndex(h => h.createdAt === ps.updatedAt?.toISOString()) !== -1) {
// Same history already exists, skip this
return;
}
const newNote = { const newNote = {
...note, ...note,
...ps, // Overwrite updated fields ...ps, // Overwrite updated fields
@ -90,18 +99,33 @@ export class NoteUpdateService {
} }
} }
this.searchService.indexNote(newNote); // Check if is latest or previous version
const history = [...(note.history || []), {
await this.notesRepository.update({ id: note.id }, {
updatedAt: ps.updatedAt,
history: [...(note.history || []), {
createdAt: (note.updatedAt || this.idService.parse(note.id).date).toISOString(), createdAt: (note.updatedAt || this.idService.parse(note.id).date).toISOString(),
cw: note.cw, cw: note.cw,
text: note.text, text: note.text,
}], }];
if (note.updatedAt && note.updatedAt >= ps.updatedAt) {
// Previous version, just update history
history.sort((h1, h2) => new Date(h1.createdAt).getTime() - new Date(h2.createdAt).getTime()); // earliest -> latest
await this.notesRepository.update({ id: note.id }, {
history,
});
} else {
// Latest version
// Update index
this.searchService.indexNote(newNote);
// Update note info
await this.notesRepository.update({ id: note.id }, {
updatedAt: ps.updatedAt,
history,
cw: ps.cw, cw: ps.cw,
text: ps.text, text: ps.text,
}); });
}
// Currently not implemented // Currently not implemented
// if (updater && (note.userId !== updater.id)) { // if (updater && (note.userId !== updater.id)) {

View file

@ -335,17 +335,22 @@ export class ApNoteService {
const uri = typeof value === 'string' ? value : value.id; const uri = typeof value === 'string' ? value : value.id;
if (uri == null) throw new Error('uri is null'); if (uri == null) throw new Error('uri is null');
// Is from local // Check if note is from local
if (uri.startsWith(`${this.config.url}/`)) throw new Error('uri points local'); if (uri.startsWith(`${this.config.url}/`)) throw new Error('uri points local');
const originNote = await this.notesRepository.findOneBy({ uri }); const originNote = await this.notesRepository.findOneBy({ uri });
if (originNote == null) throw new Error('Note is not registered'); if (originNote === null) throw new Error('note is not registered');
// Process new note // Process new note
const note = value as IPost; const note = value as IPost;
// Check updated timestamp
if (!note.updated) {
throw new Error('note.updated field is required');
}
// Fetch note author // Fetch note author
if (note.attributedTo == null) { if (!note.attributedTo) {
throw new Error('invalid note.attributedTo: ' + note.attributedTo); throw new Error('invalid note.attributedTo: ' + note.attributedTo);
} }
@ -363,12 +368,10 @@ export class ApNoteService {
text = this.apMfmService.htmlToMfm(note.content, note.tag); text = this.apMfmService.htmlToMfm(note.content, note.tag);
} }
const updatedAt = note.updated || new Date();
await this.noteUpdateService.update(actor, originNote, { await this.noteUpdateService.update(actor, originNote, {
cw, cw,
text, text,
updatedAt, updatedAt: note.updated,
}, silent); }, silent);
} }

View file

@ -70,6 +70,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchNote); throw new ApiError(meta.errors.noSuchNote);
} }
if (note.text === ps.text && note.cw === ps.cw) {
// The same as old note, nothing to do
return;
}
await this.noteUpdateService.update(await this.usersRepository.findOneByOrFail({ id: note.userId }), note, { await this.noteUpdateService.update(await this.usersRepository.findOneByOrFail({ id: note.userId }), note, {
text: ps.text, text: ps.text,
cw: ps.cw, cw: ps.cw,