Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fa777c3
* Add unique constraints on community group, dispute channel and orde…
Luquitasjeffrey May 12, 2026
08a3f6c
Code formatting
Luquitasjeffrey May 12, 2026
bff1b51
add migration script to resolve community channel and group duplicate…
Luquitasjeffrey May 14, 2026
4f5ffe0
Add test cases for community migration script
Luquitasjeffrey May 14, 2026
26c23e6
Remove redundant logs from migration script unit tests
Luquitasjeffrey May 14, 2026
67c71c6
Use db_connect to connect to mongoose in the migration script
Luquitasjeffrey May 14, 2026
d4e9b60
Set channel name to undefined when community owner is not admin
Luquitasjeffrey May 14, 2026
80a063a
Fix lint errors
Luquitasjeffrey May 14, 2026
2ce96f9
Test should reflect script behaviour. It should set order channel nam…
Luquitasjeffrey May 14, 2026
518f5ad
Send a message to the user if community channel validations fails
Luquitasjeffrey May 16, 2026
d6a7a6f
Merge remote-tracking branch 'upstream/main' into revalidate-communit…
Luquitasjeffrey May 16, 2026
e4ae6e5
Wrap sendMessage in a try/catch block in the case community channel v…
Luquitasjeffrey May 16, 2026
9b1c8f3
Code formatting
Luquitasjeffrey May 16, 2026
d79306f
Update migration script: communities with duplicated order channels s…
Luquitasjeffrey May 16, 2026
a6fa750
Add logging when community channel validation fails, and remove the u…
Luquitasjeffrey May 16, 2026
673b23e
Community order channel name should not be undefined, update model ac…
Luquitasjeffrey May 16, 2026
5a0c53f
Fix fallback in getOrderChannel
Luquitasjeffrey May 19, 2026
1cf0c03
Added manual resolution on the migration script for communities that …
Luquitasjeffrey May 20, 2026
8ee02f1
Fix CI
Luquitasjeffrey May 20, 2026
f52b917
Code formatting
Luquitasjeffrey May 20, 2026
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
16 changes: 12 additions & 4 deletions bot/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,12 @@ const publishBuyOrderMessage = async (
let publishMessage = `⚡️🍊⚡️\n${order.description}\n`;
publishMessage += `:${order._id}:`;

const channel = await getOrderChannel(order);
if (channel === undefined) throw new Error('channel is undefined');
const channel = await getOrderChannel(order, bot.telegram);
if (channel === undefined) {
order.status = 'CLOSED';
await order.save();
throw new Error('channel is undefined');
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Get the community language if available
let communityI18n = i18n;
Expand Down Expand Up @@ -782,8 +786,12 @@ const publishSellOrderMessage = async (
try {
let publishMessage = `⚡️🍊⚡️\n${order.description}\n`;
publishMessage += `:${order._id}:`;
const channel = await getOrderChannel(order);
if (channel === undefined) throw new Error('channel is undefined');
const channel = await getOrderChannel(order, ctx.telegram);
if (channel === undefined) {
order.status = 'CLOSED';
await order.save();
throw new Error('channel is undefined');
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Get the community language if available
let communityI18n = i18n;
Expand Down
6 changes: 3 additions & 3 deletions models/community.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface IOrderChannel extends Document {
}

const OrderChannelSchema = new Schema<IOrderChannel>({
name: { type: String, required: true, trim: true },
name: { type: String, unique: true, sparse: true },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
type: {
type: String,
enum: ['buy', 'sell', 'mixed'],
Expand Down Expand Up @@ -64,7 +64,7 @@ const CommunitySchema = new Schema<ICommunity>({
required: true,
},
creator_id: { type: String },
group: { type: String, trim: true }, // group Id or public name
group: { type: String, unique: true, sparse: true }, // group Id or public name
order_channels: {
// array of Id or public name of channels
type: [OrderChannelSchema],
Expand All @@ -73,7 +73,7 @@ const CommunitySchema = new Schema<ICommunity>({
fee: { type: Number, min: 0, max: 100, default: 0 },
earnings: { type: Number, default: 0 }, // Sats amount to be paid to the community
orders_to_redeem: { type: Number, default: 0 }, // Number of orders calculated to be redeemed
dispute_channel: { type: String }, // Id or public name, channel to send new disputes
dispute_channel: { type: String, unique: true, sparse: true }, // Id or public name, channel to send new disputes
solvers: [usernameIdSchema], // users that are dispute solvers
banned_users: [usernameIdSchema], // users that are banned from the community
public: { type: Boolean, default: true },
Expand Down
159 changes: 159 additions & 0 deletions scripts/migrate_duplicated_community_channels_and_groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import 'dotenv/config';
import { connect as mongoConnect } from '../db_connect';
import { Community, User } from '../models';
import { Telegraf } from 'telegraf';
import { isGroupAdmin } from '../util';
import * as readline from 'readline';
import { logger } from '../logger';

const { BOT_TOKEN, MONGO_URI } = process.env;

if (!BOT_TOKEN) {
console.error('BOT_TOKEN is missing');
process.exit(1);
}

if (!MONGO_URI) {
console.error('MONGO_URI is missing');
process.exit(1);
}

const bot = new Telegraf(BOT_TOKEN);

export const runMigration = async () => {
const mongoose = mongoConnect();
await new Promise((resolve, reject) => {
mongoose.connection.once('open', resolve);
mongoose.connection.on('error', reject);
});
logger.info('Connected to database.');

const communities = await Community.find({});
const groupCounts: Record<string, number> = {};
const disputeCounts: Record<string, number> = {};
const orderChannelCounts: Record<string, number> = {};

// Count occurrences
communities.forEach(c => {
if (c.group) {
groupCounts[c.group] = (groupCounts[c.group] || 0) + 1;
}
if (c.dispute_channel) {
disputeCounts[c.dispute_channel] =
(disputeCounts[c.dispute_channel] || 0) + 1;
}
c.order_channels.forEach(ch => {
if (ch.name) {
orderChannelCounts[ch.name] = (orderChannelCounts[ch.name] || 0) + 1;
}
});
});

const duplicateGroups = Object.keys(groupCounts).filter(
k => groupCounts[k] > 1,
);
const duplicateDispute = Object.keys(disputeCounts).filter(
k => disputeCounts[k] > 1,
);
const duplicateOrderChannels = Object.keys(orderChannelCounts).filter(
k => orderChannelCounts[k] > 1,
);

console.log(`Found ${duplicateGroups.length} duplicated groups.`);
console.log(`Found ${duplicateDispute.length} duplicated dispute channels.`);
console.log(
`Found ${duplicateOrderChannels.length} duplicated order channels.`,
Comment thread
Luquitasjeffrey marked this conversation as resolved.
);

const affectedCommunities = new Set<string>();

for (const c of communities) {
const creator = await User.findById(c.creator_id);

let modified = false;

if (c.group && duplicateGroups.includes(c.group)) {
const isAdmin = creator
? await isGroupAdmin(c.group, creator, bot.telegram)
: { success: false };
if (!isAdmin.success) {
console.log(`Community ${c.name} (${c._id}) loses group ${c.group}`);
c.group = undefined as any;
modified = true;
}
}

if (c.dispute_channel && duplicateDispute.includes(c.dispute_channel)) {
const isAdmin = creator
? await isGroupAdmin(c.dispute_channel, creator, bot.telegram)
: { success: false };
if (!isAdmin.success) {
console.log(
`Community ${c.name} (${c._id}) loses dispute_channel ${c.dispute_channel}`,
);
c.dispute_channel = undefined as any;
modified = true;
}
}

let channelsModified = false;
for (const ch of c.order_channels) {
if (ch.name && duplicateOrderChannels.includes(ch.name)) {
const isAdmin = creator
? await isGroupAdmin(ch.name, creator, bot.telegram)
: { success: false };
if (!isAdmin.success) {
channelsModified = true;
break;
}
}
}

if (channelsModified) {
console.log(
`Community ${c.name} (${c._id}) loses all order_channels due to conflict in one of them`,
);
for (const ch of c.order_channels) {
ch.name = undefined as any;
}
Comment thread
Luquitasjeffrey marked this conversation as resolved.
Outdated
modified = true;
}

if (modified) {
affectedCommunities.add(c._id.toString());
}
}

if (affectedCommunities.size === 0) {
console.log('No modifications needed. Exiting.');
process.exit(0);
}

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

rl.question(
`\nType 'YES' to save changes to ${affectedCommunities.size} communities: `,
async answer => {
if (answer === 'YES') {
console.log('Saving changes...');
for (const c of communities) {
if (affectedCommunities.has(c._id.toString())) {
await c.save();
}
}
console.log('Done.');
} else {
console.log('Aborted.');
}
rl.close();
process.exit(0);
},
);
};

if (require.main === module) {
runMigration().catch(console.error);
}
Loading
Loading