Skip to content

Commit

Permalink
fix: message attachment id
Browse files Browse the repository at this point in the history
  • Loading branch information
marrouchi committed Jan 11, 2025
1 parent 1d89683 commit d7cb39f
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 0 deletions.
2 changes: 2 additions & 0 deletions api/src/migration/migration.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

import { AttachmentModule } from '@/attachment/attachment.module';
import { LoggerModule } from '@/logger/logger.module';

import { MigrationCommand } from './migration.command';
Expand All @@ -23,6 +24,7 @@ import { MigrationService } from './migration.service';
MongooseModule.forFeature([MigrationModel]),
LoggerModule,
HttpModule,
AttachmentModule,
],
providers: [
MigrationService,
Expand Down
8 changes: 8 additions & 0 deletions api/src/migration/migration.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { getModelToken, MongooseModule } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';

import { AttachmentService } from '@/attachment/services/attachment.service';
import { LoggerService } from '@/logger/logger.service';
import { MetadataRepository } from '@/setting/repositories/metadata.repository';
import { Metadata, MetadataModel } from '@/setting/schemas/metadata.schema';
Expand Down Expand Up @@ -54,6 +55,10 @@ describe('MigrationService', () => {
provide: HttpService,
useValue: {},
},
{
provide: AttachmentService,
useValue: {},
},
{
provide: ModuleRef,
useValue: {
Expand Down Expand Up @@ -278,6 +283,7 @@ describe('MigrationService', () => {
});
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
expect(migrationMock.up).toHaveBeenCalledWith({
attachmentService: service['attachmentService'],
logger: service['logger'],
http: service['httpService'],
});
Expand Down Expand Up @@ -308,6 +314,7 @@ describe('MigrationService', () => {
});
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
expect(migrationMock.up).toHaveBeenCalledWith({
attachmentService: service['attachmentService'],
logger: service['logger'],
http: service['httpService'],
});
Expand Down Expand Up @@ -338,6 +345,7 @@ describe('MigrationService', () => {
});
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
expect(migrationMock.up).toHaveBeenCalledWith({
attachmentService: service['attachmentService'],
logger: service['logger'],
http: service['httpService'],
});
Expand Down
3 changes: 3 additions & 0 deletions api/src/migration/migration.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import leanDefaults from 'mongoose-lean-defaults';
import leanGetters from 'mongoose-lean-getters';
import leanVirtuals from 'mongoose-lean-virtuals';

import { AttachmentService } from '@/attachment/services/attachment.service';
import { config } from '@/config';
import { LoggerService } from '@/logger/logger.service';
import { MetadataService } from '@/setting/services/metadata.service';
Expand All @@ -43,6 +44,7 @@ export class MigrationService implements OnApplicationBootstrap {
private readonly logger: LoggerService,
private readonly metadataService: MetadataService,
private readonly httpService: HttpService,
private readonly attachmentService: AttachmentService,
@InjectModel(Migration.name)
private readonly migrationModel: Model<Migration>,
) {}
Expand Down Expand Up @@ -253,6 +255,7 @@ module.exports = {
const result = await migration[action]({
logger: this.logger,
http: this.httpService,
attachmentService: this.attachmentService,
});

if (result) {
Expand Down
104 changes: 104 additions & 0 deletions api/src/migration/migrations/1735836154221-v-2-2-0.migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import { existsSync } from 'fs';
import { join, resolve } from 'path';

import mongoose, { HydratedDocument } from 'mongoose';
import { v4 as uuidv4 } from 'uuid';

import attachmentSchema, {
Attachment,
} from '@/attachment/schemas/attachment.schema';
import blockSchema, { Block } from '@/chat/schemas/block.schema';
import messageSchema, { Message } from '@/chat/schemas/message.schema';
import subscriberSchema, { Subscriber } from '@/chat/schemas/subscriber.schema';
import { StdOutgoingAttachmentMessage } from '@/chat/schemas/types/message';
import contentSchema, { Content } from '@/cms/schemas/content.schema';
Expand Down Expand Up @@ -372,12 +374,114 @@ const migrateAttachmentContents = async (
}
};

/**
* Updates message documents that contain attachment "message.attachment"
* to apply one of the following operation:
* - Rename 'attachment_id' to 'id'
* - Parse internal url for to get the 'id'
* - Fetch external url, stores the attachment and store the 'id'
*
* @returns Resolves when the migration process is complete.
*/
const migrateAttachmentMessages = async ({
logger,
http,
attachmentService,
}: MigrationServices) => {
const MessageModel = mongoose.model<Message>(Message.name, messageSchema);

// Find blocks where "message.attachment" exists
const cursor = MessageModel.find({
'message.attachment.payload': { $exists: true },
'message.attachment.payload.id': { $exists: false },
}).cursor();

// Helper function to update the attachment ID in the database
const updateAttachmentId = async (
messageId: mongoose.Types.ObjectId,
attachmentId: string | null,
) => {
await MessageModel.updateOne(
{ _id: messageId },
{ $set: { 'message.attachment.payload.id': attachmentId } },
);
};

for await (const msg of cursor) {
try {
if (
'attachment' in msg.message &&
'payload' in msg.message.attachment &&
msg.message.attachment.payload
) {
if ('attachment_id' in msg.message.attachment.payload) {
await updateAttachmentId(
msg._id,
msg.message.attachment.payload.attachment_id as string,
);
} else if ('url' in msg.message.attachment.payload) {
const url = msg.message.attachment.payload.url;
const regex =
/^https?:\/\/[\w.-]+\/attachment\/download\/([a-f\d]{24})\/.+$/;
// Test the URL and extract the ID
const match = url.match(regex);
if (match) {
const [, attachmentId] = match;
await updateAttachmentId(msg._id, attachmentId);
} else if (url) {
logger.log(
`Migrate message ${msg._id}: Handling an external url ...`,
);
const response = await http.axiosRef.get(url, {
responseType: 'arraybuffer', // Ensures the response is returned as a Buffer
});
const fileBuffer = Buffer.from(response.data);
const attachment = await attachmentService.store(fileBuffer, {
name: uuidv4(),
size: fileBuffer.length,
type: response.headers['content-type'],
channel: {},
});
await updateAttachmentId(msg._id, attachment.id);
}
} else {
logger.warn(
`Unable to migrate message ${msg._id}: No ID nor URL was found`,
);

throw new Error(
'Unable to process message attachment: No ID or URL to be processed',
);
}
} else {
throw new Error(
'Unable to process message attachment: Invalid Payload',
);
}
} catch (error) {
logger.error(
`Failed to update message ${msg._id}: ${error.message}, defaulting to null`,
);
try {
await updateAttachmentId(msg._id, null);
} catch (err) {
logger.error(
`Failed to update message ${msg._id}: ${error.message}, unable to default to null`,
);
}
}
}
};

module.exports = {
async up(services: MigrationServices) {
await populateSubscriberAvatar(services);
await updateOldAvatarsPath(services);
await migrateAttachmentBlocks(MigrationAction.UP, services);
await migrateAttachmentContents(MigrationAction.UP, services);
// Given the complexity and inconsistency data, this method does not have
// a revert equivalent, at the same time, thus, it doesn't "unset" any attribute
await migrateAttachmentMessages(services);
return true;
},
async down(services: MigrationServices) {
Expand Down
2 changes: 2 additions & 0 deletions api/src/migration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { HttpService } from '@nestjs/axios';

import { AttachmentService } from '@/attachment/services/attachment.service';
import { LoggerService } from '@/logger/logger.service';

import { MigrationDocument } from './migration.schema';
Expand All @@ -34,4 +35,5 @@ export interface MigrationSuccessCallback extends MigrationRunParams {
export type MigrationServices = {
logger: LoggerService;
http: HttpService;
attachmentService: AttachmentService;
};

0 comments on commit d7cb39f

Please sign in to comment.