Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(typeorm): simplify DB API #2646

Merged
merged 9 commits into from
Jul 23, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"#lib/setup": "./dist/lib/setup/index.js",
"#lib/types": "./dist/lib/types/index.js",
"#lib/i18n/languageKeys": "./dist/lib/i18n/languageKeys/index.js",
"#lib/i18n": "./dist/lib/i18n/index.js",
"#lib/*": "./dist/lib/*.js",
"#languages": "./dist/languages/index.js",
"#root/*": "./dist/*.js"
Expand Down
80 changes: 38 additions & 42 deletions src/commands/Admin/conf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { configurableGroups, isSchemaGroup, isSchemaKey, readSettings, remove, reset, SchemaKey, set, writeSettings } from '#lib/database';
import { configurableGroups, isSchemaKey, readSettings, remove, reset, SchemaKey, set, writeSettingsTransaction } from '#lib/database';
import { LanguageKeys } from '#lib/i18n/languageKeys';
import { SettingsMenu, SkyraSubcommand } from '#lib/structures';
import type { GuildMessage } from '#lib/types';
Expand All @@ -7,10 +7,9 @@ import { isValidCustomEmoji, isValidSerializedTwemoji, isValidTwemoji } from '#l
import { inlineCode } from '@discordjs/builders';
import { ApplyOptions, RequiresClientPermissions } from '@sapphire/decorators';
import { CommandOptionsRunTypeEnum } from '@sapphire/framework';
import { filter } from '@sapphire/iterator-utilities/filter';
import { map } from '@sapphire/iterator-utilities/map';
import { filter, map, toArray } from '@sapphire/iterator-utilities';
import { send } from '@sapphire/plugin-editable-commands';
import { toTitleCase } from '@sapphire/utilities';
import { isNullish, toTitleCase } from '@sapphire/utilities';
import { PermissionFlagsBits } from 'discord.js';

@ApplyOptions<SkyraSubcommand.Options>({
Expand Down Expand Up @@ -40,15 +39,12 @@ export class UserCommand extends SkyraSubcommand {
const schemaValue = configurableGroups.getPathString(key.toLowerCase());
if (schemaValue === null) this.error(LanguageKeys.Commands.Admin.ConfGetNoExt, { key });

const output = await readSettings(message.guild, (settings) => {
return schemaValue.display(settings, args.t);
});
const settings = await readSettings(message.guild);
const output = schemaValue.display(settings, args.t);

if (isSchemaKey(schemaValue)) {
return send(message, {
content: args.t(LanguageKeys.Commands.Admin.ConfGet, { key: schemaValue.name, value: output }),
allowedMentions: { users: [], roles: [] }
});
const content = args.t(LanguageKeys.Commands.Admin.ConfGet, { key: schemaValue.name, value: output });
return send(message, { content, allowedMentions: { users: [], roles: [] } });
}

const title = key
Expand All @@ -64,64 +60,64 @@ export class UserCommand extends SkyraSubcommand {
}

public async set(message: GuildMessage, args: SkyraSubcommand.Args) {
const [key, schemaKey] = await this.fetchKey(args);
const response = await writeSettings(message.guild, async (settings) => {
await set(settings, schemaKey, args);
return schemaKey.display(settings, args.t);
});
const [key, schemaKey] = await this.#fetchKey(args);

await using trx = await writeSettingsTransaction(message.guild);
await trx.write(await set(trx.settings, schemaKey, args)).submit();

const response = schemaKey.display(trx.settings, args.t);
return send(message, {
content: args.t(LanguageKeys.Commands.Admin.ConfUpdated, { key, response: this.getTextResponse(response) }),
content: args.t(LanguageKeys.Commands.Admin.ConfUpdated, { key, response: this.#getTextResponse(response) }),
allowedMentions: { users: [], roles: [] }
});
}

public async remove(message: GuildMessage, args: SkyraSubcommand.Args) {
const [key, schemaKey] = await this.fetchKey(args);
const response = await writeSettings(message.guild, async (settings) => {
await remove(settings, schemaKey, args);
return schemaKey.display(settings, args.t);
});
const [key, schemaKey] = await this.#fetchKey(args);

await using trx = await writeSettingsTransaction(message.guild);
await trx.write(await remove(trx.settings, schemaKey, args)).submit();

const response = schemaKey.display(trx.settings, args.t);
return send(message, {
content: args.t(LanguageKeys.Commands.Admin.ConfUpdated, { key, response: this.getTextResponse(response) }),
content: args.t(LanguageKeys.Commands.Admin.ConfUpdated, { key, response: this.#getTextResponse(response) }),
allowedMentions: { users: [], roles: [] }
});
}

public async reset(message: GuildMessage, args: SkyraSubcommand.Args) {
const [key, schemaKey] = await this.fetchKey(args);
const response = await writeSettings(message.guild, async (settings) => {
reset(settings, schemaKey);
return schemaKey.display(settings, args.t);
});
const [key, schemaKey] = await this.#fetchKey(args);

await using trx = await writeSettingsTransaction(message.guild);
await trx.write(reset(schemaKey)).submit();

const response = schemaKey.display(trx.settings, args.t);
return send(message, {
content: args.t(LanguageKeys.Commands.Admin.ConfReset, { key, value: response }),
allowedMentions: { users: [], roles: [] }
});
}

private getTextResponse(response: string) {
#getTextResponse(response: string) {
return isValidCustomEmoji(response) || isValidSerializedTwemoji(response) || isValidTwemoji(response) ? response : inlineCode(response);
}

private async fetchKey(args: SkyraSubcommand.Args) {
async #fetchKey(args: SkyraSubcommand.Args) {
const key = await args.pick('string');

const value = configurableGroups.getPathString(key.toLowerCase());
if (value === null) this.error(LanguageKeys.Commands.Admin.ConfGetNoExt, { key });
if (value.dashboardOnly) this.error(LanguageKeys.Commands.Admin.ConfDashboardOnlyKey, { key });
if (isSchemaGroup(value)) {
this.error(LanguageKeys.Settings.Gateway.ChooseKey, {
keys: [
...map(
filter(value.childValues(), (value) => !value.dashboardOnly),
(value) => `\`${value.name}\``
)
]
});
if (isNullish(value) || value.dashboardOnly) {
this.error(LanguageKeys.Commands.Admin.ConfGetNoExt, { key });
}

if (isSchemaKey(value)) {
return [value.name, value as SchemaKey] as const;
}

return [value.name, value as SchemaKey] as const;
const keys = map(
filter(value.childValues(), (value) => !value.dashboardOnly),
(value) => inlineCode(value.name)
);
this.error(LanguageKeys.Settings.Gateway.ChooseKey, { keys: toArray(keys) });
}
}
103 changes: 51 additions & 52 deletions src/commands/Admin/roleset.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { GuildEntity, GuildSettings, readSettings, writeSettings, type UniqueRoleSet } from '#lib/database';
import { GuildEntity, readSettings, writeSettings, writeSettingsTransaction, type UniqueRoleSet } from '#lib/database';
import { LanguageKeys } from '#lib/i18n/languageKeys';
import { SkyraCommand, SkyraSubcommand } from '#lib/structures';
import { PermissionLevels, type GuildMessage } from '#lib/types';
import { ApplyOptions } from '@sapphire/decorators';
import { CommandOptionsRunTypeEnum } from '@sapphire/framework';
import { send } from '@sapphire/plugin-editable-commands';

const Root = LanguageKeys.Commands.Admin;

@ApplyOptions<SkyraSubcommand.Options>({
aliases: ['rs'],
description: LanguageKeys.Commands.Admin.RoleSetDescription,
detailedDescription: LanguageKeys.Commands.Admin.RoleSetExtended,
description: Root.RoleSetDescription,
detailedDescription: Root.RoleSetExtended,
permissionLevel: PermissionLevels.Administrator,
runIn: [CommandOptionsRunTypeEnum.GuildAny],
subcommands: [
Expand All @@ -32,46 +34,46 @@ export class UserCommand extends SkyraSubcommand {
const roles = await args.repeat('roleName');

// Get all rolesets from settings and check if there is an existing set with the name provided by the user
await writeSettings(message.guild, (settings) => {
await writeSettings(message.guild, (settings) => ({
// The set does exist so we want to only REMOVE provided roles from it
// Create a new array that we can use to overwrite the existing one in settings
settings[GuildSettings.Roles.UniqueRoleSets] = settings[GuildSettings.Roles.UniqueRoleSets].map((set) =>
rolesUniqueRoleSets: settings.rolesUniqueRoleSets.map((set) =>
set.name === name ? { name, roles: set.roles.filter((id: string) => !roles.find((role) => role.id === id)) } : set
);
});
)
}));

return send(message, args.t(LanguageKeys.Commands.Admin.RoleSetRemoved, { name, roles: roles.map((role) => role.name) }));
return send(message, args.t(Root.RoleSetRemoved, { name, roles: roles.map((role) => role.name) }));
}

public async reset(message: GuildMessage, args: SkyraSubcommand.Args) {
const [name, sets] = await Promise.all([
args.pick('string').catch(() => null),
// Get all rolesets from settings and check if there is an existing set with the name provided by the user
readSettings(message.guild, GuildSettings.Roles.UniqueRoleSets)
this.#getUniqueRoleSets(message)
]);
if (sets.length === 0) this.error(LanguageKeys.Commands.Admin.RoleSetResetEmpty);
if (sets.length === 0) this.error(Root.RoleSetResetEmpty);

if (!name) {
await writeSettings(message.guild, [[GuildSettings.Roles.UniqueRoleSets, []]]);
return send(message, args.t(LanguageKeys.Commands.Admin.RoleSetResetAll));
await writeSettings(message.guild, { rolesUniqueRoleSets: [] });
return send(message, args.t(Root.RoleSetResetAll));
}

const arrayIndex = sets.findIndex((set) => set.name === name);
if (arrayIndex === -1) this.error(LanguageKeys.Commands.Admin.RoleSetResetNotExists, { name });
if (arrayIndex === -1) this.error(Root.RoleSetResetNotExists, { name });

await writeSettings(message.guild, (settings) => {
settings[GuildSettings.Roles.UniqueRoleSets].splice(arrayIndex, 1);
await writeSettings(message.guild, {
rolesUniqueRoleSets: sets.toSpliced(arrayIndex, 1)
});

return send(message, args.t(LanguageKeys.Commands.Admin.RoleSetResetGroup, { name }));
return send(message, args.t(Root.RoleSetResetGroup, { name }));
}

// This subcommand will run if a user doesn't type add or remove. The bot will then add AND remove based on whether that role is in the set already.
public async auto(message: GuildMessage, args: SkyraSubcommand.Args) {
const name = await args.pick('string');

// Get all role sets from settings and check if there is an existing set with the name provided by the user
const sets = await readSettings(message.guild, GuildSettings.Roles.UniqueRoleSets);
const sets = await this.#getUniqueRoleSets(message);
const set = sets.find((set) => set.name === name);

// If this role set does not exist we have to create it
Expand All @@ -92,15 +94,15 @@ export class UserCommand extends SkyraSubcommand {
return { name, roles: newRoles };
});

await writeSettings(message.guild, [[GuildSettings.Roles.UniqueRoleSets, newSets]]);
return send(message, args.t(LanguageKeys.Commands.Admin.RoleSetUpdated, { name }));
await writeSettings(message.guild, { rolesUniqueRoleSets: newSets });
return send(message, args.t(Root.RoleSetUpdated, { name }));
}

// This subcommand will show the user a list of role sets and each role in that set.
public async list(message: GuildMessage, args: SkyraCommand.Args) {
// Get all rolesets from settings
const sets = await readSettings(message.guild, GuildSettings.Roles.UniqueRoleSets);
if (sets.length === 0) this.error(LanguageKeys.Commands.Admin.RoleSetNoRoleSets);
const sets = await this.#getUniqueRoleSets(message);
if (sets.length === 0) this.error(Root.RoleSetNoRoleSets);

const list = await this.handleList(message, args, sets);
return send(message, list.join('\n'));
Expand Down Expand Up @@ -135,21 +137,23 @@ export class UserCommand extends SkyraSubcommand {
if (changed) {
// If after cleaning up, all sets end up empty, reset and return error:
if (list.length === 0) {
await writeSettings(message.guild, [[GuildSettings.Roles.UniqueRoleSets, []]]);
this.error(LanguageKeys.Commands.Admin.RoleSetNoRoleSets);
await writeSettings(message.guild, { rolesUniqueRoleSets: [] });
this.error(Root.RoleSetNoRoleSets);
}

// Else, clean up:
await writeSettings(message.guild, (settings) => this.cleanRoleSets(message, settings));
await writeSettings(message.guild, (settings) => ({
rolesUniqueRoleSets: this.cleanRoleSets(message, settings)
}));
}

return list;
}

private cleanRoleSets(message: GuildMessage, settings: GuildEntity) {
private cleanRoleSets(message: GuildMessage, settings: Readonly<GuildEntity>) {
const guildRoles = message.guild.roles.cache;

settings[GuildSettings.Roles.UniqueRoleSets] = settings[GuildSettings.Roles.UniqueRoleSets]
return settings.rolesUniqueRoleSets
.map((set) => ({ name: set.name, roles: set.roles.filter((role) => guildRoles.has(role)) }))
.filter((set) => set.roles.length > 0);
}
Expand All @@ -158,36 +162,31 @@ export class UserCommand extends SkyraSubcommand {
const roles = await args.repeat('roleName');

// Get all rolesets from settings and check if there is an existing set with the name provided by the user
const created = await writeSettings(message.guild, (settings) => {
const allRoleSets = settings[GuildSettings.Roles.UniqueRoleSets];
const roleSet = allRoleSets.some((set) => set.name === name);

// If it does not exist we need to create a brand new set
if (!roleSet) {
allRoleSets.push({ name, roles: roles.map((role) => role.id) });
return true;
}
await using trx = await writeSettingsTransaction(message.guild);

const entries = trx.settings.rolesUniqueRoleSets;
const index = entries.findIndex((set) => set.name === name);

// If it does not exist we need to create a brand new set
if (index === -1) {
const rolesUniqueRoleSets = entries.concat({ name, roles: roles.map((role) => role.id) });
trx.write({ rolesUniqueRoleSets });
} else {
// The set does exist so we want to only ADD new roles in
// Create a new array that we can use to overwrite the existing one in settings
const sets = allRoleSets.map((set) => {
if (set.name !== name) return set;
const finalRoles = [...set.roles];
for (const role of roles) if (!finalRoles.includes(role.id)) finalRoles.push(role.id);

return { name, roles: finalRoles };
});
settings[GuildSettings.Roles.UniqueRoleSets] = sets;
const entry = entries[index];
const rolesUniqueRoleSets = entries.with(index, { name, roles: entry.roles.concat(roles.map((role) => role.id)) });
trx.write({ rolesUniqueRoleSets });
}
await trx.submit();

return false;
});
const created = index === -1;
const key = created ? Root.RoleSetCreated : Root.RoleSetAdded;
return send(message, args.t(key, { name, roles: roles.map((role) => role.name) }));
}

return send(
message,
args.t(created ? LanguageKeys.Commands.Admin.RoleSetCreated : LanguageKeys.Commands.Admin.RoleSetAdded, {
name,
roles: roles.map((role) => role.name)
})
);
async #getUniqueRoleSets(message: GuildMessage) {
const settings = await readSettings(message.guild);
return settings.rolesUniqueRoleSets;
}
}
14 changes: 6 additions & 8 deletions src/commands/Management/AutoModeration/automod-attachments.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { GuildSettings } from '#lib/database';
import { LanguageKeys } from '#lib/i18n/languageKeys';
import { AutoModerationCommand } from '#lib/moderation';
import { ApplyOptions } from '@sapphire/decorators';

const Root = LanguageKeys.Commands.AutoModeration;
const SettingsRoot = GuildSettings.AutoModeration.Attachments;

@ApplyOptions<AutoModerationCommand.Options>({
aliases: ['attachment-mode', 'attachments-mode', 'manage-attachment', 'manage-attachments'],
description: Root.AttachmentsDescription,
localizedNameKey: Root.AttachmentsName,
adderPropertyName: 'attachments',
keyEnabled: SettingsRoot.Enabled,
keyOnInfraction: SettingsRoot.SoftAction,
keyPunishment: SettingsRoot.HardAction,
keyPunishmentDuration: SettingsRoot.HardActionDuration,
keyPunishmentThreshold: SettingsRoot.ThresholdMaximum,
keyPunishmentThresholdPeriod: SettingsRoot.ThresholdDuration
keyEnabled: 'selfmodAttachmentsEnabled',
keyOnInfraction: 'selfmodAttachmentsSoftAction',
keyPunishment: 'selfmodAttachmentsHardAction',
keyPunishmentDuration: 'selfmodAttachmentsHardActionDuration',
keyPunishmentThreshold: 'selfmodAttachmentsThresholdMaximum',
keyPunishmentThresholdPeriod: 'selfmodAttachmentsThresholdDuration'
})
export class UserAutoModerationCommand extends AutoModerationCommand {}
14 changes: 6 additions & 8 deletions src/commands/Management/AutoModeration/automod-capitals.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { GuildSettings } from '#lib/database';
import { LanguageKeys } from '#lib/i18n/languageKeys';
import { AutoModerationCommand } from '#lib/moderation';
import { ApplyOptions } from '@sapphire/decorators';

const Root = LanguageKeys.Commands.AutoModeration;
const SettingsRoot = GuildSettings.AutoModeration.Capitals;

@ApplyOptions<AutoModerationCommand.Options>({
aliases: ['capitals-mode', 'manage-capitals'],
description: Root.CapitalsDescription,
localizedNameKey: Root.CapitalsName,
adderPropertyName: 'capitals',
keyEnabled: SettingsRoot.Enabled,
keyOnInfraction: SettingsRoot.SoftAction,
keyPunishment: SettingsRoot.HardAction,
keyPunishmentDuration: SettingsRoot.HardActionDuration,
keyPunishmentThreshold: SettingsRoot.ThresholdMaximum,
keyPunishmentThresholdPeriod: SettingsRoot.ThresholdDuration
keyEnabled: 'selfmodCapitalsEnabled',
keyOnInfraction: 'selfmodCapitalsSoftAction',
keyPunishment: 'selfmodCapitalsHardAction',
keyPunishmentDuration: 'selfmodCapitalsHardActionDuration',
keyPunishmentThreshold: 'selfmodCapitalsThresholdMaximum',
keyPunishmentThresholdPeriod: 'selfmodCapitalsThresholdDuration'
})
export class UserAutoModerationCommand extends AutoModerationCommand {}
Loading