Phase 438: structured role and permission overwrite error logging (v0.8.401).
Wrap createGuildCustomRole, updateGuildCustomRole, deleteGuildCustomRole, reorderGuildCustomRoles, setMemberCustomRoles, upsertChannelPermissionOverwrite, and deleteChannelPermissionOverwrite with timed spans and structured error logs.
This commit is contained in:
@@ -143,6 +143,8 @@ Primary consumer: `apps/pearcord/index.js` sidecar reading pear-pipe JSON.
|
||||
|
||||
**v0.8.400:** `createChannelWebhook`/`deleteChannelWebhook`/`executeChannelWebhook` log `webhook.*` spans and errors; `registerSlashCommands`/`deleteSlashCommand`/`executeSlashCommand` log `slash.*` spans and errors.
|
||||
|
||||
**v0.8.401:** `createGuildCustomRole`/`updateGuildCustomRole`/`deleteGuildCustomRole`/`reorderGuildCustomRoles`/`setMemberCustomRoles` log `role.*` spans and errors; `upsertChannelPermissionOverwrite`/`deleteChannelPermissionOverwrite` log `permission.overwrite*` spans and errors.
|
||||
|
||||
**v0.8.252:** Guild search mesh skipped when `guildOpenNoViewableChannels`; emoji batch prefetch skips negative miss TTL; sticker gossip prefetch gated on `attachments`; `session.startup` logs INFO when initial guild mesh flush was bounded. Smoke: `test:guild-search-no-viewable-skip`, `test:sticker-prefetch-attachments-ready`.
|
||||
|
||||
**v0.8.248:** Guild open filters unread/last picks with `_canViewChannel`; sets `guildOpenNoViewableChannels` when none accessible; defers `_ensureGuildModules` via `setTimeout(0)` after channel pick; emoji mesh prefetch clears miss cache on success. Smokes: `test:guild-viewable-channel-pick`, `test:guild-open-no-viewable`, `test:guild-plain-beats-dual-combo`.
|
||||
|
||||
@@ -4896,87 +4896,142 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async createGuildCustomRole ({ name, color, permissions } = {}) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
const span = this.log.time('role.create', { name: String(name || '').slice(0, 32) })
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const row = await this.guildRoles.createRole(this.guild.guild.id, {
|
||||
name,
|
||||
color,
|
||||
permissions
|
||||
})
|
||||
this.guild.gossipRoleUpsert(row)
|
||||
await this._audit('role.create', {
|
||||
guildId: this.guild.guild.id,
|
||||
targetId: row.id
|
||||
})
|
||||
this.emit('guild-role', row)
|
||||
span.end({ roleId: row.id })
|
||||
return row
|
||||
} catch (err) {
|
||||
this.log.error('role.create error', {
|
||||
name: String(name || '').slice(0, 32),
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const row = await this.guildRoles.createRole(this.guild.guild.id, {
|
||||
name,
|
||||
color,
|
||||
permissions
|
||||
})
|
||||
this.guild.gossipRoleUpsert(row)
|
||||
await this._audit('role.create', {
|
||||
guildId: this.guild.guild.id,
|
||||
targetId: row.id
|
||||
})
|
||||
this.emit('guild-role', row)
|
||||
return row
|
||||
}
|
||||
|
||||
async updateGuildCustomRole (roleId, patch = {}) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
const span = this.log.time('role.update', { roleId })
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const row = await this.guildRoles.updateRole(this.guild.guild.id, roleId, patch)
|
||||
this.guild.gossipRoleUpsert(row)
|
||||
this.emit('guild-role', row)
|
||||
span.end({ roleId: row?.id || roleId })
|
||||
return row
|
||||
} catch (err) {
|
||||
this.log.error('role.update error', {
|
||||
roleId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const row = await this.guildRoles.updateRole(this.guild.guild.id, roleId, patch)
|
||||
this.guild.gossipRoleUpsert(row)
|
||||
this.emit('guild-role', row)
|
||||
return row
|
||||
}
|
||||
|
||||
async deleteGuildCustomRole (roleId) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
const span = this.log.time('role.delete', { roleId })
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const prev = await this.guildRoles.deleteRole(this.guild.guild.id, roleId)
|
||||
if (prev) {
|
||||
this.guild.gossipRoleDelete({ guildId: this.guild.guild.id, id: roleId })
|
||||
this.emit('guild-role-delete', { guildId: this.guild.guild.id, id: roleId })
|
||||
}
|
||||
span.end({ roleId })
|
||||
return prev
|
||||
} catch (err) {
|
||||
this.log.error('role.delete error', {
|
||||
roleId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const prev = await this.guildRoles.deleteRole(this.guild.guild.id, roleId)
|
||||
if (prev) {
|
||||
this.guild.gossipRoleDelete({ guildId: this.guild.guild.id, id: roleId })
|
||||
this.emit('guild-role-delete', { guildId: this.guild.guild.id, id: roleId })
|
||||
}
|
||||
return prev
|
||||
}
|
||||
|
||||
async reorderGuildCustomRoles (roleIds = []) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
const span = this.log.time('role.reorder', { count: (roleIds || []).length })
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const rows = await this.guildRoles.reorderRoles(this.guild.guild.id, roleIds)
|
||||
for (const row of rows) {
|
||||
if (row.updatedAt) this.guild.gossipRoleUpsert(row)
|
||||
}
|
||||
this.emit('guild-role-reorder', { guildId: this.guild.guild.id, roleIds })
|
||||
span.end({ count: rows.length })
|
||||
return rows
|
||||
} catch (err) {
|
||||
this.log.error('role.reorder error', {
|
||||
count: (roleIds || []).length,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const rows = await this.guildRoles.reorderRoles(this.guild.guild.id, roleIds)
|
||||
for (const row of rows) {
|
||||
if (row.updatedAt) this.guild.gossipRoleUpsert(row)
|
||||
}
|
||||
this.emit('guild-role-reorder', { guildId: this.guild.guild.id, roleIds })
|
||||
return rows
|
||||
}
|
||||
|
||||
async setMemberCustomRoles (userId, roleIds = []) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
const span = this.log.time('role.member', { userId })
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) {
|
||||
throw new Error('no permission to manage roles')
|
||||
}
|
||||
if (userId === this.guild.guild.ownerId) {
|
||||
throw new Error('cannot change owner role assignments')
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const link = await this.guildRoles.setMemberRoleIds(
|
||||
this.guild.guild.id,
|
||||
userId,
|
||||
roleIds
|
||||
)
|
||||
this.guild.gossipMemberCustomRoles(link)
|
||||
this.emit('guild-member-roles', link)
|
||||
await this._fanoutAppEvent(APP_EVENTS.MEMBER_ROLE_UPDATE, { memberRoles: link })
|
||||
this.emit('member', await this.db.get(COLLECTIONS.MEMBERS, {
|
||||
guildId: this.guild.guild.id,
|
||||
userId
|
||||
}))
|
||||
span.end({ userId, roleCount: (roleIds || []).length })
|
||||
return link
|
||||
} catch (err) {
|
||||
this.log.error('role.member error', {
|
||||
userId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
if (userId === this.guild.guild.ownerId) {
|
||||
throw new Error('cannot change owner role assignments')
|
||||
}
|
||||
await this._initGuildRoles(this.guild.guild.id)
|
||||
const link = await this.guildRoles.setMemberRoleIds(
|
||||
this.guild.guild.id,
|
||||
userId,
|
||||
roleIds
|
||||
)
|
||||
this.guild.gossipMemberCustomRoles(link)
|
||||
this.emit('guild-member-roles', link)
|
||||
await this._fanoutAppEvent(APP_EVENTS.MEMBER_ROLE_UPDATE, { memberRoles: link })
|
||||
this.emit('member', await this.db.get(COLLECTIONS.MEMBERS, {
|
||||
guildId: this.guild.guild.id,
|
||||
userId
|
||||
}))
|
||||
return link
|
||||
}
|
||||
|
||||
async getChannelSettings (channelId, guildId) {
|
||||
@@ -5132,69 +5187,96 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async upsertChannelPermissionOverwrite (channelId, payload = {}) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
|
||||
throw new Error('no permission to edit channel permissions')
|
||||
}
|
||||
const chId = channelId || this.activeChannelId
|
||||
if (!chId) throw new Error('channelId required')
|
||||
const ch = await this.db.get(COLLECTIONS.CHANNELS, {
|
||||
guildId: this.guild.guild.id,
|
||||
id: chId
|
||||
const span = this.log.time('permission.overwrite', {
|
||||
channelId: channelId || this.activeChannelId,
|
||||
targetType: payload.targetType
|
||||
})
|
||||
if (!ch) throw new Error('channel not found')
|
||||
await this._initChannelPermissions(this.guild.guild.id)
|
||||
const allow =
|
||||
payload.allow !== undefined
|
||||
? Number(payload.allow) || 0
|
||||
: viewToMask(payload.allowView || {})
|
||||
const deny =
|
||||
payload.deny !== undefined
|
||||
? Number(payload.deny) || 0
|
||||
: viewToMask(payload.denyView || {})
|
||||
const row = await this.channelPermissions.upsertOverwrite(
|
||||
this.guild.guild.id,
|
||||
chId,
|
||||
{
|
||||
targetType: payload.targetType,
|
||||
targetId: payload.targetId,
|
||||
allow,
|
||||
deny
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
|
||||
throw new Error('no permission to edit channel permissions')
|
||||
}
|
||||
)
|
||||
this.guild.gossipChannelOverwriteUpsert(row)
|
||||
await this._audit('channel.overwrite', { guildId: this.guild.guild.id, targetId: chId })
|
||||
this.emit('channel-overwrite', row)
|
||||
await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_UPDATE, { overwrite: row })
|
||||
return row
|
||||
const chId = channelId || this.activeChannelId
|
||||
if (!chId) throw new Error('channelId required')
|
||||
const ch = await this.db.get(COLLECTIONS.CHANNELS, {
|
||||
guildId: this.guild.guild.id,
|
||||
id: chId
|
||||
})
|
||||
if (!ch) throw new Error('channel not found')
|
||||
await this._initChannelPermissions(this.guild.guild.id)
|
||||
const allow =
|
||||
payload.allow !== undefined
|
||||
? Number(payload.allow) || 0
|
||||
: viewToMask(payload.allowView || {})
|
||||
const deny =
|
||||
payload.deny !== undefined
|
||||
? Number(payload.deny) || 0
|
||||
: viewToMask(payload.denyView || {})
|
||||
const row = await this.channelPermissions.upsertOverwrite(
|
||||
this.guild.guild.id,
|
||||
chId,
|
||||
{
|
||||
targetType: payload.targetType,
|
||||
targetId: payload.targetId,
|
||||
allow,
|
||||
deny
|
||||
}
|
||||
)
|
||||
this.guild.gossipChannelOverwriteUpsert(row)
|
||||
await this._audit('channel.overwrite', { guildId: this.guild.guild.id, targetId: chId })
|
||||
this.emit('channel-overwrite', row)
|
||||
await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_UPDATE, { overwrite: row })
|
||||
span.end({ channelId: chId, targetId: payload.targetId })
|
||||
return row
|
||||
} catch (err) {
|
||||
this.log.error('permission.overwrite error', {
|
||||
channelId: channelId || this.activeChannelId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async deleteChannelPermissionOverwrite (channelId, targetType, targetId) {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
|
||||
throw new Error('no permission to edit channel permissions')
|
||||
}
|
||||
const chId = channelId || this.activeChannelId
|
||||
await this._initChannelPermissions(this.guild.guild.id)
|
||||
const prev = await this.channelPermissions.deleteOverwrite(
|
||||
this.guild.guild.id,
|
||||
chId,
|
||||
targetType,
|
||||
targetId
|
||||
)
|
||||
if (prev && this.guild) {
|
||||
this.guild.gossipChannelOverwriteDelete({
|
||||
guildId: this.guild.guild.id,
|
||||
channelId: chId,
|
||||
targetType: prev.targetType,
|
||||
targetId: prev.targetId
|
||||
const span = this.log.time('permission.overwrite.delete', { channelId, targetType, targetId })
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
|
||||
throw new Error('no permission to edit channel permissions')
|
||||
}
|
||||
const chId = channelId || this.activeChannelId
|
||||
await this._initChannelPermissions(this.guild.guild.id)
|
||||
const prev = await this.channelPermissions.deleteOverwrite(
|
||||
this.guild.guild.id,
|
||||
chId,
|
||||
targetType,
|
||||
targetId
|
||||
)
|
||||
if (prev && this.guild) {
|
||||
this.guild.gossipChannelOverwriteDelete({
|
||||
guildId: this.guild.guild.id,
|
||||
channelId: chId,
|
||||
targetType: prev.targetType,
|
||||
targetId: prev.targetId
|
||||
})
|
||||
}
|
||||
this.emit('channel-overwrite-delete', { channelId: chId, targetType, targetId })
|
||||
await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_DELETE, {
|
||||
delete: { channelId: chId, targetType, targetId }
|
||||
})
|
||||
span.end({ channelId: chId, targetId })
|
||||
return prev
|
||||
} catch (err) {
|
||||
this.log.error('permission.overwrite.delete error', {
|
||||
channelId,
|
||||
targetType,
|
||||
targetId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
this.emit('channel-overwrite-delete', { channelId: chId, targetType, targetId })
|
||||
await this._fanoutAppEvent(APP_EVENTS.CHANNEL_PERMISSION_DELETE, {
|
||||
delete: { channelId: chId, targetType, targetId }
|
||||
})
|
||||
return prev
|
||||
}
|
||||
|
||||
async _assertVoiceCapacity (channelId) {
|
||||
|
||||
Reference in New Issue
Block a user