Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/services/mediawiki/core.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Types } from 'mongoose';

import parentLogger from '../../config/logger';
import { ApiError } from '../../utils';
import temporalMediaWiki from '../temporal/mediawiki.service';

const logger = parentLogger.child({ module: 'MediaWikiCoreService' });

async function createMediaWikiSchedule(platformId: Types.ObjectId): Promise<string> {
try {
const schedule = await temporalMediaWiki.createSchedule(platformId);
logger.info(`Started schedule '${schedule.scheduleId}'`);
await schedule.trigger();
return schedule.scheduleId;
} catch (error) {
logger.error(error, 'Failed to trigger mediawiki schedule.');
throw new ApiError(590, 'Failed to create mediawiki schedule.');
}
}

async function deleteMediaWikiSchedule(scheduleId: string): Promise<void> {
try {
await temporalMediaWiki.deleteSchedule(scheduleId);
} catch (error) {
logger.error(error, 'Failed to delete mediawiki schedule.');
throw new ApiError(590, 'Failed to delete mediawiki schedule.');
}
}
Comment on lines +21 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Apply consistent error handling across all methods.

Similar to the previous comment, use standard HTTP error codes and include the original error message in the log.

  } catch (error) {
-    logger.error(error, 'Failed to delete mediawiki schedule.');
-    throw new ApiError(590, 'Failed to delete mediawiki schedule.');
+    logger.error(error, `Failed to delete mediawiki schedule: ${(error as Error).message}`);
+    throw new ApiError(500, 'Failed to delete mediawiki schedule.');
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function deleteMediaWikiSchedule(scheduleId: string): Promise<void> {
try {
await temporalMediaWiki.deleteSchedule(scheduleId);
} catch (error) {
logger.error(error, 'Failed to delete mediawiki schedule.');
throw new ApiError(590, 'Failed to delete mediawiki schedule.');
}
}
async function deleteMediaWikiSchedule(scheduleId: string): Promise<void> {
try {
await temporalMediaWiki.deleteSchedule(scheduleId);
} catch (error) {
logger.error(error, `Failed to delete mediawiki schedule: ${(error as Error).message}`);
throw new ApiError(500, 'Failed to delete mediawiki schedule.');
}
}


async function terminateMediaWikiWorkflow(communityId: string): Promise<void> {
try {
await temporalMediaWiki.terminateWorkflow(`api:mediawiki:${communityId}`);
} catch (error) {
logger.error(error, 'Failed to terminate mediawiki workflow.');
throw new ApiError(590, 'Failed to terminate mediawiki workflow.');
}
}
Comment on lines +30 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Apply consistent error handling across all methods.

Similar to the previous comments, use standard HTTP error codes and include the original error message in the log.

  } catch (error) {
-    logger.error(error, 'Failed to terminate mediawiki workflow.');
-    throw new ApiError(590, 'Failed to terminate mediawiki workflow.');
+    logger.error(error, `Failed to terminate mediawiki workflow: ${(error as Error).message}`);
+    throw new ApiError(500, 'Failed to terminate mediawiki workflow.');
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function terminateMediaWikiWorkflow(communityId: string): Promise<void> {
try {
await temporalMediaWiki.terminateWorkflow(`api:mediawiki:${communityId}`);
} catch (error) {
logger.error(error, 'Failed to terminate mediawiki workflow.');
throw new ApiError(590, 'Failed to terminate mediawiki workflow.');
}
}
async function terminateMediaWikiWorkflow(communityId: string): Promise<void> {
try {
await temporalMediaWiki.terminateWorkflow(`api:mediawiki:${communityId}`);
} catch (error) {
logger.error(error, `Failed to terminate mediawiki workflow: ${(error as Error).message}`);
throw new ApiError(500, 'Failed to terminate mediawiki workflow.');
}
}


export default {
createMediaWikiSchedule,
deleteMediaWikiSchedule,
terminateMediaWikiWorkflow,
};
5 changes: 5 additions & 0 deletions src/services/mediawiki/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import coreService from './core.service';

export default {
coreService,
};
37 changes: 34 additions & 3 deletions src/services/module.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IModule, IModuleUpdateBody, Module, PlatformNames, ModuleNames } from '

import platformService from './platform.service';
import websiteService from './website';
import mediawikiService from './mediawiki';

/**
* Create a module
Expand Down Expand Up @@ -91,9 +92,13 @@ const updateModule = async (
const existingPlatform = module.options.platforms.find((p) => p.name === newPlatform.name);

if (existingPlatform) {
// if (module.name === ModuleNames.Hivemind && newPlatform.name === PlatformNames.Website) {
// await handleHivemindWebsiteCase(newPlatform);
// }
if (module.name === ModuleNames.Hivemind) {
if (newPlatform.name === PlatformNames.Website) {
await handleHivemindWebsiteCase(newPlatform);
} else if (newPlatform.name === PlatformNames.MediaWiki) {
await handleHivemindMediaWikiCase(newPlatform);
}
}
existingPlatform.metadata = newPlatform.metadata;
} else {
module.options.platforms.push(newPlatform);
Expand Down Expand Up @@ -132,6 +137,32 @@ const handleHivemindWebsiteCase = async (platform: any) => {
}
};

const handleHivemindMediaWikiCase = async (platform: any) => {
const platformDoc = await platformService.getPlatformById(platform.platform);

if (!platformDoc) return;

const isActivated = platform.metadata?.activated;
const existingScheduleId = platformDoc.get('metadata.scheduleId');

if (isActivated === true) {
if (!existingScheduleId) {
const scheduleId = await mediawikiService.coreService.createMediaWikiSchedule(platform.platform);
platformDoc.set('metadata.scheduleId', scheduleId);

await platformDoc.save();
}
} else if (isActivated === false) {
if (existingScheduleId) {
await mediawikiService.coreService.deleteMediaWikiSchedule(existingScheduleId);
await mediawikiService.coreService.terminateMediaWikiWorkflow(platformDoc.community.toString());
platformDoc.set('metadata.scheduleId', null);

await platformDoc.save();
}
}
};
Comment on lines +140 to +164
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider defining a proper type for the platform parameter.

The platform: any type is too generic. Define a specific interface for better type safety and code clarity.

Also, note the code duplication between this function and handleHivemindWebsiteCase. Consider refactoring to reduce duplication in the future.

-const handleHivemindMediaWikiCase = async (platform: any) => {
+interface PlatformWithMetadata {
+  platform: Types.ObjectId;
+  metadata?: {
+    activated?: boolean;
+    scheduleId?: string;
+  };
+}
+
+const handleHivemindMediaWikiCase = async (platform: PlatformWithMetadata) => {

Let's check how similar these two handler functions are:


🏁 Script executed:

#!/bin/bash
# Compare the two handler functions to assess duplication level
echo "handleHivemindWebsiteCase:"
sed -n '/const handleHivemindWebsiteCase/,/^};/p' src/services/module.service.ts

echo -e "\nhandleHivemindMediaWikiCase:"
sed -n '/const handleHivemindMediaWikiCase/,/^};/p' src/services/module.service.ts

Length of output: 2111


Action Required: Refactor platform parameter typing and consolidate duplicated logic.

  • Type Safety: Replace the generic any type with a specific interface (e.g., a PlatformWithMetadata interface) to ensure clear type safety. For example:

    -const handleHivemindMediaWikiCase = async (platform: any) => {
    +interface PlatformWithMetadata {
    +  platform: Types.ObjectId;
    +  metadata?: {
    +    activated?: boolean;
    +    scheduleId?: string;
    +  };
    +}
    +
    +const handleHivemindMediaWikiCase = async (platform: PlatformWithMetadata) => {
  • Deduplication: The two functions—handleHivemindWebsiteCase and handleHivemindMediaWikiCase—share an almost identical control flow, differing only in the called service methods (e.g., createWebsiteSchedule vs. createMediaWikiSchedule). Consider abstracting the common logic into a shared helper function or employing a strategy pattern to reduce maintenance overhead.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleHivemindMediaWikiCase = async (platform: any) => {
const platformDoc = await platformService.getPlatformById(platform.platform);
if (!platformDoc) return;
const isActivated = platform.metadata?.activated;
const existingScheduleId = platformDoc.get('metadata.scheduleId');
if (isActivated === true) {
if (!existingScheduleId) {
const scheduleId = await mediawikiService.coreService.createMediaWikiSchedule(platform.platform);
platformDoc.set('metadata.scheduleId', scheduleId);
await platformDoc.save();
}
} else if (isActivated === false) {
if (existingScheduleId) {
await mediawikiService.coreService.deleteMediaWikiSchedule(existingScheduleId);
await mediawikiService.coreService.terminateMediaWikiWorkflow(platformDoc.community.toString());
platformDoc.set('metadata.scheduleId', null);
await platformDoc.save();
}
}
};
interface PlatformWithMetadata {
platform: Types.ObjectId;
metadata?: {
activated?: boolean;
scheduleId?: string;
};
}
const handleHivemindMediaWikiCase = async (platform: PlatformWithMetadata) => {
const platformDoc = await platformService.getPlatformById(platform.platform);
if (!platformDoc) return;
const isActivated = platform.metadata?.activated;
const existingScheduleId = platformDoc.get('metadata.scheduleId');
if (isActivated === true) {
if (!existingScheduleId) {
const scheduleId = await mediawikiService.coreService.createMediaWikiSchedule(platform.platform);
platformDoc.set('metadata.scheduleId', scheduleId);
await platformDoc.save();
}
} else if (isActivated === false) {
if (existingScheduleId) {
await mediawikiService.coreService.deleteMediaWikiSchedule(existingScheduleId);
await mediawikiService.coreService.terminateMediaWikiWorkflow(platformDoc.community.toString());
platformDoc.set('metadata.scheduleId', null);
await platformDoc.save();
}
}
};


/**
* Delete module
* @param {HydratedDocument<IModule>} module - module doc
Expand Down
73 changes: 73 additions & 0 deletions src/services/temporal/mediawiki.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Types } from 'mongoose';

import { CalendarSpec, Client, ScheduleHandle, ScheduleOverlapPolicy } from '@temporalio/client';

import parentLogger from '../../config/logger';
import { queues } from './configs/temporal.config';
import { TemporalCoreService } from './core.service';

const logger = parentLogger.child({ module: 'MediawikiTemporalService' });

class TemporalWMediaWikiService extends TemporalCoreService {
public async createSchedule(platformId: Types.ObjectId): Promise<ScheduleHandle> {
const initiationTime = new Date();
const dayNumber = initiationTime.getUTCDay();
const hour = initiationTime.getUTCHours();
const minute = initiationTime.getUTCMinutes();
const DAY_NAMES = ['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'] as const;
const dayOfWeek = DAY_NAMES[dayNumber];

const calendarSpec: CalendarSpec = {
dayOfWeek,
hour,
minute,
comment: `Weekly schedule for ${dayOfWeek} at ${hour}:${minute} UTC`,
};

try {
const client: Client = await this.getClient();
return client.schedule.create({
scheduleId: `api:mediawiki:${platformId}`,
spec: {
calendars: [calendarSpec],
},
action: {
type: 'startWorkflow',
workflowType: 'MediaWikiETLWorkflow',
args: [platformId.toString()],
taskQueue: queues.TEMPORAL_QUEUE_PYTHON_HEAVY,
},
policies: {
catchupWindow: '1 day',
overlap: ScheduleOverlapPolicy.SKIP,
},
});
} catch (error) {
throw new Error(`Failed to create or update mediawiki ingestion schedule: ${(error as Error).message}`);
}
}

public async pauseSchedule(scheduleId: string): Promise<void> {
const client: Client = await this.getClient();
const handle = client.schedule.getHandle(scheduleId);
await handle.pause();
}
Comment on lines +50 to +54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for the pauseSchedule method.

Consider adding proper error handling and checking if the schedule exists before attempting to pause it.

  public async pauseSchedule(scheduleId: string): Promise<void> {
-   const client: Client = await this.getClient();
-   const handle = client.schedule.getHandle(scheduleId);
-   await handle.pause();
+   try {
+     const client: Client = await this.getClient();
+     const handle = client.schedule.getHandle(scheduleId);
+     await handle.pause();
+   } catch (error) {
+     logger.error(error, `Failed to pause mediawiki schedule: ${(error as Error).message}`);
+     throw new Error(`Failed to pause mediawiki schedule: ${(error as Error).message}`);
+   }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async pauseSchedule(scheduleId: string): Promise<void> {
const client: Client = await this.getClient();
const handle = client.schedule.getHandle(scheduleId);
await handle.pause();
}
public async pauseSchedule(scheduleId: string): Promise<void> {
try {
const client: Client = await this.getClient();
const handle = client.schedule.getHandle(scheduleId);
await handle.pause();
} catch (error) {
logger.error(error, `Failed to pause mediawiki schedule: ${(error as Error).message}`);
throw new Error(`Failed to pause mediawiki schedule: ${(error as Error).message}`);
}
}


public async deleteSchedule(scheduleId: string): Promise<void> {
const client: Client = await this.getClient();
const handle = client.schedule.getHandle(scheduleId);

await handle.delete();
}
Comment on lines +56 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for the deleteSchedule method.

Consider adding proper error handling and checking if the schedule exists before attempting to delete it, similar to how it's done in the terminateWorkflow method.

  public async deleteSchedule(scheduleId: string): Promise<void> {
-   const client: Client = await this.getClient();
-   const handle = client.schedule.getHandle(scheduleId);
-
-   await handle.delete();
+   try {
+     const client: Client = await this.getClient();
+     const handle = client.schedule.getHandle(scheduleId);
+     await handle.delete();
+   } catch (error) {
+     logger.error(error, `Failed to delete mediawiki schedule: ${(error as Error).message}`);
+     throw new Error(`Failed to delete mediawiki schedule: ${(error as Error).message}`);
+   }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async deleteSchedule(scheduleId: string): Promise<void> {
const client: Client = await this.getClient();
const handle = client.schedule.getHandle(scheduleId);
await handle.delete();
}
public async deleteSchedule(scheduleId: string): Promise<void> {
try {
const client: Client = await this.getClient();
const handle = client.schedule.getHandle(scheduleId);
await handle.delete();
} catch (error) {
logger.error(error, `Failed to delete mediawiki schedule: ${(error as Error).message}`);
throw new Error(`Failed to delete mediawiki schedule: ${(error as Error).message}`);
}
}


public async terminateWorkflow(workflowId: string): Promise<void> {
const client: Client = await this.getClient();
const handle = client.workflow.getHandle(workflowId);
const description = await handle.describe();
if (description.status.name !== 'TERMINATED' && description.status.name !== 'COMPLETED') {
await handle.terminate('Terminated due to schedule deletion');
}
}
}

export default new TemporalWMediaWikiService();