Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 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
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
32 changes: 28 additions & 4 deletions bot/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,20 @@ 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) {
try {
await bot.telegram.sendMessage(
user.tg_id,
i18n.t('order_channel_validation_failed'),
);
} catch (error) {
logger.error(error);
}
order.status = 'CLOSED';
await order.save();
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Get the community language if available
let communityI18n = i18n;
Expand Down Expand Up @@ -782,8 +794,20 @@ 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) {
try {
await ctx.telegram.sendMessage(
user.tg_id,
i18n.t('order_channel_validation_failed'),
);
} catch (error) {
logger.error(error);
}
order.status = 'CLOSED';
await order.save();
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Get the community language if available
let communityI18n = i18n;
Expand Down
1 change: 1 addition & 0 deletions locales/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ unblock_failed: "Fehler beim Freigeben des Benutzers"
check_solvers: Ihre Community ${communityName} hat keine Solver. Bitte fügen Sie innerhalb von ${remainingDays} Tagen mindestens einen hinzu, um zu verhindern, dass die Community deaktiviert wird.
check_solvers_last_warning: Ihre Community ${communityName} hat keine Solver. Bitte fügen Sie noch heute mindestens einen hinzu, um zu verhindern, dass die Community deaktiviert wird.
image_processing_error: Wir hatten einen Fehler beim Verarbeiten des Bildes, bitte warten Sie ein paar Minuten und versuchen Sie es erneut.
order_channel_validation_failed: "Ihre Bestellung konnte nicht veröffentlicht werden. Versuchen Sie, die Standard-Community mit /setcomm off zu deaktivieren, oder bitten Sie Ihren Community-Administrator, die Kanäle und Gruppen der Community neu zu konfigurieren."
community_payment_methods: "Zahlungsmethoden"
enter_community_payment_methods: "Gib die in deiner Community akzeptierten Zahlungsmethoden ein, getrennt durch Kommas (z.B.: Banküberweisung, Bargeld, PayPal)"
current_payment_methods: "Aktuelle Zahlungsmethoden: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ unblock_failed: "Error unblocking the user"
check_solvers: Your community ${communityName} does not have any solvers. Please add at least one within ${remainingDays} days to prevent the community from being disabled.
check_solvers_last_warning: Your community ${communityName} does not have any solvers. Please add at least one today to prevent the community from being disabled.
image_processing_error: We had an error processing the image, please wait a few minutes and try again
order_channel_validation_failed: "Unable to publish your order. Try disabling the default community /setcomm off, or ask your community administrator to reconfigure the community channels and groups."
community_payment_methods: "Payment methods"
enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)"
current_payment_methods: "Current payment methods: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ unblock_failed: "Error al desbloquear al usuario"
image_processing_error: Hemos tenido un error procesando la imagen, por favor espera unos minutos y vuelve a intentarlo.
check_solvers: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno dentro de ${remainingDays} días para evitar que se deshabilite la comunidad.
check_solvers_last_warning: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno hoy para evitar que la comunidad quede inhabilitada.
order_channel_validation_failed: "No se pudo publicar la orden, prueba desactivando la comunidad por defecto /setcomm off, o pídele al administrador de tu comunidad que reconfigure los canales y grupos de comunidad."
community_payment_methods: "Métodos de pago"
enter_community_payment_methods: "Ingresa los métodos de pago aceptados en tu comunidad, separados por comas (ej.: Transferencia bancaria, Efectivo, PayPal)"
current_payment_methods: "Métodos de pago actuales: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/fa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ unblock_failed: "خطا در رفع مسدودیت کاربر"
check_solvers: اجتماع شما ${communityName} هیچ داوری ندارد. لطفاً برای جلوگیری از غیرفعال شدن اجتماع، تا ${remainingDays} روز آینده حداقل یک داور به آن اضافه کنید.
check_solvers_last_warning: اجتماع شما ${communityName} هیچ داوری ندارد. برای جلوگیری از غیرفعال شدن اجتماع، امروز حداقل یک داور به آن اضافه کنید.
image_processing_error: هنگام پردازش تصویر با خطایی مواجه شدیم، لطفاً چند دقیقه صبر کرده و دوباره امتحان کنید.
order_channel_validation_failed: "امکان انتشار سفارش شما وجود ندارد. سعی کنید انجمن پیش‌فرض را با /setcomm off غیرفعال کنید یا از مدیر انجمن خود بخواهید کانال‌ها و گروه‌های انجمن را مجدداً پیکربندی کند."
community_payment_methods: "روش‌های پرداخت"
enter_community_payment_methods: "روش‌های پرداخت پذیرفته‌شده در اجتماع خود را با کاما از هم جدا کرده وارد کنید (مثال: انتقال بانکی، نقد، PayPal)"
current_payment_methods: "روش‌های پرداخت فعلی: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ unblock_failed: "Erreur lors du déblocage de l'utilisateur"
check_solvers: Votre communauté ${communityName} ne possède aucun solveur. Veuillez en ajouter au moins un dans les ${remainingDays} jours pour éviter que la communauté ne soit désactivée.
check_solvers_last_warning: Votre communauté ${communityName} ne possède aucun solveur. Veuillez en ajouter au moins un aujourd'hui pour éviter que la communauté ne soit désactivée.
image_processing_error: Nous avons eu une erreur lors du traitement de l'image, veuillez attendre quelques minutes et réessayer.
order_channel_validation_failed: "Impossible de publier votre commande. Essayez de désactiver la communauté par défaut avec /setcomm off, ou demandez à l'administrateur de votre communauté de reconfigurer les canaux et groupes de la communauté."
community_payment_methods: "Méthodes de paiement"
enter_community_payment_methods: "Entrez les méthodes de paiement acceptées dans votre communauté, séparées par des virgules (ex. : Virement bancaire, Espèces, PayPal)"
current_payment_methods: "Méthodes de paiement actuelles : ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/it.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ unblock_failed: "Errore nello sbloccare l'utente"
check_solvers: La tua community ${communityName} non ha risolutori. Aggiungine almeno uno entro ${remainingDays} giorni per evitare che la community venga disabilitata.
check_solvers_last_warning: La tua community ${communityName} non ha risolutori. Per favore aggiungine almeno uno oggi per evitare che la community venga disabilitata.
image_processing_error: Abbiamo avuto un errore nel processare l'immagine, per favore attendi qualche minuto e prova di nuovo.
order_channel_validation_failed: "Impossibile pubblicare il tuo ordine. Prova a disattivare la community predefinita con /setcomm off, o chiedi all'amministratore della tua community di riconfigurare i canali e i gruppi della community."
community_payment_methods: "Metodi di pagamento"
enter_community_payment_methods: "Inserisci i metodi di pagamento accettati nella tua community, separati da virgole (es.: Bonifico bancario, Contanti, PayPal)"
current_payment_methods: "Metodi di pagamento attuali: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/ko.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ unblock_failed: "사용자 차단 해제 중 오류 발생"
check_solvers: ${communityName} 커뮤니티에 해결사가 없습니다. 커뮤니티가 비활성화되는 것을 방지하려면 ${remainingDays}일 이내에 하나 이상 추가하세요.
check_solvers_last_warning: ${communityName} 커뮤니티에 해결사가 없습니다. 커뮤니티가 비활성화되는 것을 방지하려면 오늘 하나 이상 추가하세요.
image_processing_error: 이미지 처리에 오류가 발생했습니다. 몇 분 후에 다시 시도해 주세요.
order_channel_validation_failed: "주문을 등록할 수 없습니다. /setcomm off 로 기본 커뮤니티를 비활성화하거나, 커뮤니티 관리자에게 커뮤니티 채널 및 그룹을 재설정해 달라고 요청하세요."
community_payment_methods: "결제 방법"
enter_community_payment_methods: "커뮤니티에서 허용되는 결제 방법을 쉼표로 구분하여 입력하세요 (예: 은행 이체, 현금, PayPal)"
current_payment_methods: "현재 결제 방법: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/pt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ unblock_failed: "Erro ao desbloquear o usuário"
check_solvers: Sua comunidade ${communityName} não possui solucionadores. Adicione pelo menos um dentro de ${remainingDays} dias para evitar que a comunidade seja desativada.
check_solvers_last_warning: Sua comunidade ${communityName} não possui solucionadores. Adicione pelo menos um hoje para evitar que a comunidade seja desativada.
image_processing_error: Tivemos um erro ao processar a imagem, por favor aguarde alguns minutos e tente novamente.
order_channel_validation_failed: "Não foi possível publicar a sua ordem. Tente desativar a comunidade padrão com /setcomm off ou peça ao administrador da comunidade para reconfigurar os canais e grupos da comunidade."
community_payment_methods: "Métodos de pagamento"
enter_community_payment_methods: "Insira os métodos de pagamento aceitos em sua comunidade, separados por vírgulas (ex.: Transferência bancária, Dinheiro, PayPal)"
current_payment_methods: "Métodos de pagamento atuais: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ unblock_failed: "Ошибка при разблокировке пользова
check_solvers: В вашем сообществе ${communityName} нет решателей. Добавьте хотя бы одно в течение ${remainingDays} дн., чтобы сообщество не было отключено.
check_solvers_last_warning: В вашем сообществе ${communityName} нет решателей. Пожалуйста, добавьте хотя бы один сегодня, чтобы предотвратить отключение сообщества.
image_processing_error: У нас возникла ошибка при обработке изображения, пожалуйста, подождите несколько минут и попробуйте снова.
order_channel_validation_failed: "Не удалось опубликовать ваш ордер. Попробуйте отключить сообщество по умолчанию с помощью /setcomm off или попросите администратора сообщества перенастроить каналы и группы сообщества."
community_payment_methods: "Способы оплаты"
enter_community_payment_methods: "Введите способы оплаты, принятые в вашем сообществе, разделённые запятыми (напр.: Банковский перевод, Наличные, PayPal)"
current_payment_methods: "Текущие способы оплаты: ${methods}"
Expand Down
1 change: 1 addition & 0 deletions locales/uk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ unblock_failed: "Помилка при розблокуванні користу
check_solvers: У вашій спільноті ${communityName} немає розв’язувачів. Додайте принаймні одну протягом ${remainingDays} днів, щоб запобігти вимкненню спільноти.
check_solvers_last_warning: У вашій спільноті ${communityName} немає розв’язувачів. Будь ласка, додайте принаймні одну сьогодні, щоб запобігти вимкненню спільноти.
image_processing_error: У нас виникла помилка при обробці зображення, будь ласка, почекайте кілька хвилин і спробуйте знову.
order_channel_validation_failed: "Не вдалося опублікувати ваше замовлення. Спробуйте вимкнути спільноту за замовчуванням за допомогою /setcomm off або попросіть адміністратора спільноти переналаштувати канали та групи спільноти."
community_payment_methods: "Способи оплати"
enter_community_payment_methods: "Введіть способи оплати, прийняті у вашій спільноті, розділені комами (напр.: Банківський переказ, Готівка, PayPal)"
current_payment_methods: "Поточні способи оплати: ${methods}"
Expand Down
8 changes: 4 additions & 4 deletions models/community.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isValidLanguage, SUPPORTED_LANGUAGES } from '../util/languages';
const CURRENCIES: number = parseInt(process.env.COMMUNITY_CURRENCIES || '10');

const arrayLimits = (val: any[]): boolean => {
return val.length > 0 && val.length <= 2;
return val.length <= 2;
};

const currencyLimits = (val: string): boolean => {
Expand All @@ -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, trim: true },
type: {
type: String,
enum: ['buy', 'sell', 'mixed'],
Expand Down Expand Up @@ -65,7 +65,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 @@ -74,7 +74,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
157 changes: 157 additions & 0 deletions scripts/migrate_duplicated_community_channels_and_groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
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.`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Warning: this migration does not guarantee that the new unique indexes will be satisfiable. It only removes duplicates when isGroupAdmin() fails, so duplicated group / dispute_channel / order_channels.name values can remain for admin-owned communities. That means the schema change can still fail to apply cleanly or leave the collection in a state that violates the new constraints.

);

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`,
);
c.order_channels = [] as any;
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