fix: various fixes to sentry-reported errors
This commit is contained in:
@@ -89,7 +89,7 @@ export class GuildRoleRepository extends IGuildRoleRepository {
|
||||
}),
|
||||
(current) => ({
|
||||
pk: {guild_id: guildId, role_id: roleId},
|
||||
patch: buildPatchFromData(data, current, GUILD_ROLE_COLUMNS, ['guild_id', 'role_id']),
|
||||
patch: buildPatchFromData(data, oldData ?? current, GUILD_ROLE_COLUMNS, ['guild_id', 'role_id']),
|
||||
}),
|
||||
GuildRoles,
|
||||
{initialData: oldData},
|
||||
|
||||
@@ -26,6 +26,6 @@ export abstract class IGuildRoleRepository {
|
||||
abstract listRoles(guildId: GuildID): Promise<Array<GuildRole>>;
|
||||
abstract listRolesByIds(roleIds: Array<RoleID>, guildId: GuildID): Promise<Array<GuildRole>>;
|
||||
abstract countRoles(guildId: GuildID): Promise<number>;
|
||||
abstract upsertRole(data: GuildRoleRow): Promise<GuildRole>;
|
||||
abstract upsertRole(data: GuildRoleRow, oldData?: GuildRoleRow | null): Promise<GuildRole>;
|
||||
abstract deleteRole(guildId: GuildID, roleId: RoleID): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ export class GuildRoleService {
|
||||
mentionable: updateData.mentionable ?? role.isMentionable,
|
||||
};
|
||||
|
||||
const updatedRole = await this.guildRoleRepository.upsertRole(updatedRoleData);
|
||||
const updatedRole = await this.guildRoleRepository.upsertRole(updatedRoleData, role.toRow());
|
||||
|
||||
await this.dispatchGuildRoleUpdate({guildId, role: updatedRole});
|
||||
|
||||
@@ -424,10 +424,14 @@ export class GuildRoleService {
|
||||
const role = roleMap.get(update.roleId)!;
|
||||
if (role.hoistPosition === update.hoistPosition) continue;
|
||||
|
||||
const updatedRole = await this.guildRoleRepository.upsertRole({
|
||||
...role.toRow(),
|
||||
hoist_position: update.hoistPosition,
|
||||
});
|
||||
const roleRow = role.toRow();
|
||||
const updatedRole = await this.guildRoleRepository.upsertRole(
|
||||
{
|
||||
...roleRow,
|
||||
hoist_position: update.hoistPosition,
|
||||
},
|
||||
roleRow,
|
||||
);
|
||||
changedRoles.push(updatedRole);
|
||||
}
|
||||
|
||||
@@ -460,10 +464,14 @@ export class GuildRoleService {
|
||||
for (const role of allRoles) {
|
||||
if (role.hoistPosition === null) continue;
|
||||
|
||||
const updatedRole = await this.guildRoleRepository.upsertRole({
|
||||
...role.toRow(),
|
||||
hoist_position: null,
|
||||
});
|
||||
const roleRow = role.toRow();
|
||||
const updatedRole = await this.guildRoleRepository.upsertRole(
|
||||
{
|
||||
...roleRow,
|
||||
hoist_position: null,
|
||||
},
|
||||
roleRow,
|
||||
);
|
||||
changedRoles.push(updatedRole);
|
||||
}
|
||||
|
||||
@@ -649,7 +657,11 @@ export class GuildRoleService {
|
||||
const reorderedIds = targetOrder.map((r) => r.id);
|
||||
const reorderedRoles = this.reorderRolePositions({allRoles, reorderedIds, guildId});
|
||||
|
||||
const updatePromises = reorderedRoles.map((role) => this.guildRoleRepository.upsertRole(role.toRow()));
|
||||
const updatePromises = reorderedRoles.map((role) => {
|
||||
const roleRow = role.toRow();
|
||||
const oldRole = roleMap.get(role.id);
|
||||
return this.guildRoleRepository.upsertRole(roleRow, oldRole ? oldRole.toRow() : undefined);
|
||||
});
|
||||
await Promise.all(updatePromises);
|
||||
|
||||
const updatedRoles = await this.guildRoleRepository.listRoles(guildId);
|
||||
|
||||
@@ -82,6 +82,7 @@ export class ChannelHelpers {
|
||||
static validateChannelVoicePlacement(
|
||||
finalChannels: Array<Channel>,
|
||||
parentMap: Map<ChannelID, ChannelID | null>,
|
||||
parentIdsToValidate?: ReadonlySet<ChannelID>,
|
||||
): void {
|
||||
const orderedByParent = new Map<ChannelID | null, Array<Channel>>();
|
||||
|
||||
@@ -95,6 +96,9 @@ export class ChannelHelpers {
|
||||
|
||||
for (const [parentId, siblings] of orderedByParent.entries()) {
|
||||
if (parentId === null) continue;
|
||||
if (parentIdsToValidate && !parentIdsToValidate.has(parentId)) {
|
||||
continue;
|
||||
}
|
||||
let encounteredVoice = false;
|
||||
for (const sibling of siblings) {
|
||||
if (sibling.type === ChannelTypes.GUILD_VOICE) {
|
||||
|
||||
@@ -570,11 +570,18 @@ export class ChannelOperationsService {
|
||||
const desiredParentId = plan.desiredParentById.get(operation.channelId) ?? null;
|
||||
const targetChannel = allChannels.find((ch) => ch.id === operation.channelId);
|
||||
const currentParentId = targetChannel?.parentId ?? null;
|
||||
const parentIdsToValidate = new Set<ChannelID>();
|
||||
if (currentParentId) {
|
||||
parentIdsToValidate.add(currentParentId);
|
||||
}
|
||||
if (desiredParentId) {
|
||||
parentIdsToValidate.add(desiredParentId);
|
||||
}
|
||||
if (desiredParentId && desiredParentId !== currentParentId) {
|
||||
await this.ensureCategoryHasCapacity({guildId, categoryId: desiredParentId});
|
||||
}
|
||||
|
||||
ChannelHelpers.validateChannelVoicePlacement(plan.finalChannels, plan.desiredParentById);
|
||||
ChannelHelpers.validateChannelVoicePlacement(plan.finalChannels, plan.desiredParentById, parentIdsToValidate);
|
||||
|
||||
if (plan.orderUnchanged) {
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
|
||||
import {createTestAccount} from '@fluxer/api/src/auth/tests/AuthTestUtils';
|
||||
import {createGuildID} from '@fluxer/api/src/BrandedTypes';
|
||||
import {ChannelRepository} from '@fluxer/api/src/channel/ChannelRepository';
|
||||
import {
|
||||
acceptInvite,
|
||||
addMemberRole,
|
||||
@@ -385,6 +387,60 @@ describe('Guild Channel Positions', () => {
|
||||
expect(response.errors[0]?.code).toBe(ValidationErrorCodes.CANNOT_POSITION_CHANNEL_RELATIVE_TO_ITSELF);
|
||||
});
|
||||
|
||||
test('should reorder top-level categories despite unrelated legacy voice and text ordering', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const guild = await createGuild(harness, account.token, 'Test Guild');
|
||||
|
||||
const milsims = await createChannel(harness, account.token, guild.id, 'MILSIMS', ChannelTypes.GUILD_CATEGORY);
|
||||
const coopGames = await createChannel(harness, account.token, guild.id, 'COOP GAMES', ChannelTypes.GUILD_CATEGORY);
|
||||
const frontDoor = await createChannel(harness, account.token, guild.id, 'FRONT DOOR', ChannelTypes.GUILD_CATEGORY);
|
||||
const coopText = await createChannel(harness, account.token, guild.id, 'coop-text', ChannelTypes.GUILD_TEXT);
|
||||
const coopVoice = await createChannel(harness, account.token, guild.id, 'coop-voice', ChannelTypes.GUILD_VOICE);
|
||||
|
||||
await updateChannelPositions(harness, account.token, guild.id, [
|
||||
{id: coopText.id, parent_id: coopGames.id},
|
||||
{id: coopVoice.id, parent_id: coopGames.id},
|
||||
]);
|
||||
|
||||
const channelRepository = new ChannelRepository();
|
||||
const channelsBeforeMove = await channelRepository.listGuildChannels(createGuildID(BigInt(guild.id)));
|
||||
const storedCoopText = channelsBeforeMove.find((channel) => channel.id.toString() === coopText.id);
|
||||
const storedCoopVoice = channelsBeforeMove.find((channel) => channel.id.toString() === coopVoice.id);
|
||||
|
||||
expect(storedCoopText).toBeDefined();
|
||||
expect(storedCoopVoice).toBeDefined();
|
||||
if (!storedCoopText || !storedCoopVoice) {
|
||||
return;
|
||||
}
|
||||
|
||||
await channelRepository.upsert({...storedCoopVoice.toRow(), position: 1});
|
||||
await channelRepository.upsert({...storedCoopText.toRow(), position: 2});
|
||||
|
||||
await updateChannelPositions(harness, account.token, guild.id, [
|
||||
{
|
||||
id: frontDoor.id,
|
||||
parent_id: null,
|
||||
preceding_sibling_id: milsims.id,
|
||||
},
|
||||
]);
|
||||
|
||||
const channels = await getGuildChannels(harness, account.token, guild.id);
|
||||
const orderedRootCategories = channels
|
||||
.filter((channel) => channel.type === ChannelTypes.GUILD_CATEGORY && channel.parent_id == null)
|
||||
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
||||
.map((channel) => channel.id);
|
||||
|
||||
const milsimsIndex = orderedRootCategories.indexOf(milsims.id);
|
||||
const frontDoorIndex = orderedRootCategories.indexOf(frontDoor.id);
|
||||
const coopGamesIndex = orderedRootCategories.indexOf(coopGames.id);
|
||||
|
||||
expect(milsimsIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(frontDoorIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(coopGamesIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(frontDoorIndex).toBeGreaterThan(milsimsIndex);
|
||||
expect(frontDoorIndex).toBeLessThan(coopGamesIndex);
|
||||
});
|
||||
|
||||
test('should reject text channels being positioned below voice channels via preceding_sibling_id', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const guild = await createGuild(harness, account.token, 'Test Guild');
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
|
||||
import {createTestAccount} from '@fluxer/api/src/auth/tests/AuthTestUtils';
|
||||
import {createGuildID, createRoleID} from '@fluxer/api/src/BrandedTypes';
|
||||
import {GuildRoleRepository} from '@fluxer/api/src/guild/repositories/GuildRoleRepository';
|
||||
import {
|
||||
acceptInvite,
|
||||
addMemberRole,
|
||||
@@ -175,4 +177,43 @@ describe('Guild Role Operations', () => {
|
||||
|
||||
expect(newRole.name).toBe('Member Created Role');
|
||||
});
|
||||
|
||||
test('should preserve concurrent position updates when applying stale role snapshot', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const guild = await createGuild(harness, account.token, 'Test Guild');
|
||||
|
||||
const role = await createRole(harness, account.token, guild.id, {
|
||||
name: 'Role',
|
||||
});
|
||||
|
||||
const guildId = createGuildID(BigInt(guild.id));
|
||||
const roleId = createRoleID(BigInt(role.id));
|
||||
const roleRepository = new GuildRoleRepository();
|
||||
const staleRole = await roleRepository.getRole(roleId, guildId);
|
||||
expect(staleRole).toBeDefined();
|
||||
if (!staleRole) {
|
||||
return;
|
||||
}
|
||||
|
||||
const staleRoleRow = staleRole.toRow();
|
||||
const movedRole = await roleRepository.upsertRole(
|
||||
{
|
||||
...staleRoleRow,
|
||||
position: staleRoleRow.position + 5,
|
||||
},
|
||||
staleRoleRow,
|
||||
);
|
||||
|
||||
await roleRepository.upsertRole(
|
||||
{
|
||||
...staleRoleRow,
|
||||
name: 'Renamed Role',
|
||||
},
|
||||
staleRoleRow,
|
||||
);
|
||||
|
||||
const finalRole = await roleRepository.getRole(roleId, guildId);
|
||||
expect(finalRole?.name).toBe('Renamed Role');
|
||||
expect(finalRole?.position).toBe(movedRole.position);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -191,6 +191,7 @@ export async function initializeWorkerDependencies(snowflakeService: SnowflakeSe
|
||||
const limitConfigSubscriber = getKVClient();
|
||||
const limitConfigService = new LimitConfigService(instanceConfigRepository, cacheService, limitConfigSubscriber);
|
||||
await limitConfigService.initialize();
|
||||
limitConfigService.setAsGlobalInstance();
|
||||
const userCacheService = new UserCacheService(cacheService, userRepository);
|
||||
const storageService = createStorageService({s3Service: getInjectedS3Service()});
|
||||
const csamEvidenceRetentionService = new CsamEvidenceRetentionService(storageService);
|
||||
|
||||
Reference in New Issue
Block a user