Skip to content

Commit

Permalink
feat: initial case model (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
didinele authored Jul 10, 2024
1 parent af3f0fc commit 3c8cd28
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 50 deletions.
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
},
"dependencies": {
"@chatsift/pino-rotate-file": "^0.3.0",
"@discordjs/brokers": "^1.0.0-dev.1720311088-d8e94d8f1",
"@discordjs/brokers": "^0.3.0",
"@discordjs/builders": "^1.8.2",
"@discordjs/core": "^1.2.0",
"@discordjs/rest": "^2.3.0",
"@msgpack/msgpack": "^3.0.0-beta2",
"@naval-base/ms": "^3.1.0",
"@sapphire/bitfield": "^1.2.2",
"@sapphire/discord-utilities": "^3.3.0",
"coral-command": "^0.4.1",
"coral-command": "^0.4.2",
"inversify": "^6.0.2",
"ioredis": "5.3.2",
"kysely": "^0.27.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import type { Selectable } from 'kysely';
import type { Experiment, ExperimentOverride, Incident } from '../db.js';
import type { Experiment, ExperimentOverride, Incident, ModCase, ModCaseKind } from '../db.js';

export type ExperimentWithOverrides = Selectable<Experiment> & { overrides: Selectable<ExperimentOverride>[] };

export interface CreateModCaseData {
guildId: string;
kind: ModCaseKind;
modId: string;
reason: string;
userId: string;
}

/**
* Abstraction over all database interactions (things like Redis included)
* Abstraction over all database interactions
*/
export abstract class IDataManager {
public abstract getExperiments(): Promise<ExperimentWithOverrides[]>;
public abstract createIncident(error: Error, guildId?: string): Promise<Selectable<Incident>>;

public abstract createModCase(data: CreateModCaseData): Promise<Selectable<ModCase>>;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Kysely, Selectable } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import type { DB, Incident } from '../db.js';
import { IDataManager, type ExperimentWithOverrides } from './IDataManager.js';
import type { DB, Incident, ModCase } from '../db.js';
import { IDataManager, type CreateModCaseData, type ExperimentWithOverrides } from './IDataManager.js';

/**
* Our current databse implementation, using kysely with types generated by prisma-kysely
Expand Down Expand Up @@ -42,4 +42,8 @@ export class KyselyDataManager extends IDataManager {
.returningAll()
.executeTakeFirstOrThrow();
}

public override async createModCase(data: CreateModCaseData): Promise<Selectable<ModCase>> {
return this.#database.insertInto('ModCase').values(data).returningAll().executeTakeFirstOrThrow();
}
}
File renamed without changes.
4 changes: 2 additions & 2 deletions packages/core/src/command-framework/CoralCommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { inject, injectable } from 'inversify';
import type { Selectable } from 'kysely';
import { type Logger } from 'pino';
import type { IDataManager } from '../applicationData/IDataManager.js';
import type { IDataManager } from '../application-data/IDataManager.js';
import { INJECTION_TOKENS } from '../container.js';
import type { Incident } from '../db.js';
import type { Env } from '../util/Env.js';
Expand Down Expand Up @@ -167,7 +167,7 @@ export class CoralCommandHandler extends ICommandHandler<CoralInteractionHandler
}
}

public register(options: RegisterOptions): void {
public override register(options: RegisterOptions): void {
if (options.interactions) {
this.#interactions.push(...options.interactions);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/command-framework/ICommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export interface HandlerModule<TReturnType> {

export type HandlerModuleConstructor<TReturnType> = new (...args: unknown[]) => HandlerModule<TReturnType>;

export const BASE_HANDLERS_PATH = join(dirname(fileURLToPath(import.meta.url)), 'handlers');
export const USEFUL_HANDLERS_PATH = join(dirname(fileURLToPath(import.meta.url)), 'handlers');

export interface RegisterOptions<TReturnType = any> {
applicationCommands?: [ApplicationCommandIdentifier, ApplicationCommandHandler<TReturnType>][];
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
: ColumnType<T, T | undefined, T>;
export type Timestamp = ColumnType<Date, Date | string, Date | string>;

export const ModCaseKind = {
Warn: "Warn",
Timeout: "Timeout",
Kick: "Kick",
Ban: "Ban",
Unmute: "Unmute",
Unban: "Unban"
} as const;
export type ModCaseKind = (typeof ModCaseKind)[keyof typeof ModCaseKind];
export type Experiment = {
name: string;
createdAt: Generated<Timestamp>;
Expand All @@ -23,8 +32,18 @@ export type Incident = {
createdAt: Generated<Timestamp>;
resolved: Generated<boolean>;
};
export type ModCase = {
id: Generated<number>;
guildId: string;
kind: ModCaseKind;
createdAt: Generated<Timestamp>;
reason: string;
modId: string;
userId: string;
};
export type DB = {
Experiment: Experiment;
ExperimentOverride: ExperimentOverride;
Incident: Incident;
ModCase: ModCase;
};
2 changes: 1 addition & 1 deletion packages/core/src/experiments/ExperimentHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { setInterval } from 'node:timers';
import { inject, injectable } from 'inversify';
import murmurhash from 'murmurhash';
import type { Logger } from 'pino';
import { IDataManager, type ExperimentWithOverrides } from '../applicationData/IDataManager.js';
import { IDataManager, type ExperimentWithOverrides } from '../application-data/IDataManager.js';
import { INJECTION_TOKENS } from '../container.js';
import { IExperimentHandler } from './IExperimentHandler.js';

Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Deliberately don't export impl
export * from './applicationData/IDataManager.js';
export * from './application-data/IDataManager.js';

export * from './binary-encoding/Data.js';
export * from './binary-encoding/Reader.js';
Expand All @@ -14,7 +14,9 @@ export * from './cache/entities/ICacheEntity.js';
export * from './cache/CacheFactory.js';
export * from './cache/ICache.js';

// Deliberately don't export impl
// Here we actually do, because unlike other parts of the codebases, we don't rely on the WHOLE stack using the same impl
// every service can decide what to do.
export * from './command-framework/CoralCommandHandler.js';
export * from './command-framework/ICommandHandler.js';

// Deliberately don't export impl
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/util/DependencyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,5 @@ export class DependencyManager {
.bind<ICacheEntity<CachedGuild>>(INJECTION_TOKENS.cacheEntities.guild)
.to(GuildCacheEntity)
.inSingletonScope();

// command handler
globalContainer.bind<ICommandHandler<any>>(ICommandHandler).to(CoralCommandHandler).inSingletonScope();
}
}
19 changes: 19 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,22 @@ model Incident {
createdAt DateTime @default(now())
resolved Boolean @default(false)
}

enum ModCaseKind {
Warn
Timeout
Kick
Ban
Unmute
Unban
}

model ModCase {
id Int @id @default(autoincrement())
guildId String
kind ModCaseKind
createdAt DateTime @default(now())
reason String
modId String
userId String
}
3 changes: 2 additions & 1 deletion services/interactions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"@discordjs/core": "^1.2.0",
"@discordjs/rest": "^2.3.0",
"@discordjs/ws": "^1.1.1",
"coral-command": "^0.4.1",
"@sapphire/discord-utilities": "^3.3.0",
"coral-command": "^0.4.2",
"inversify": "^6.0.2",
"ioredis": "5.3.2",
"reflect-metadata": "^0.2.2",
Expand Down
75 changes: 75 additions & 0 deletions services/interactions/src/handlers/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ModCaseKind, type HandlerModule, type ICommandHandler, type IDataManager } from '@automoderator/core';
import {
ApplicationCommandOptionType,
InteractionContextType,
MessageFlags,
PermissionFlagsBits,
type APIInteraction,
} from '@discordjs/core';
import type { InteractionOptionResolver } from '@sapphire/discord-utilities';
import { ActionKind, type InteractionHandler as CoralInteractionHandler } from 'coral-command';
import { injectable } from 'inversify';

@injectable()
export default class DevHandler implements HandlerModule<CoralInteractionHandler> {
public constructor(private readonly dataManager: IDataManager) {}

public register(handler: ICommandHandler<CoralInteractionHandler>) {
handler.register({
interactions: [
{
name: 'warn',
description: 'Warn a user',
options: [
{
name: 'target',
description: 'The user to warn',
type: ApplicationCommandOptionType.User,
required: true,
},
{
name: 'reason',
description: 'The reason for the warning',
type: ApplicationCommandOptionType.String,
required: true,
},
],
contexts: [InteractionContextType.Guild],
default_member_permissions: String(PermissionFlagsBits.ModerateMembers),
},
],
applicationCommands: [['warn:none:none', this.hanadleWarn.bind(this)]],
});
}

public async *hanadleWarn(interaction: APIInteraction, options: InteractionOptionResolver): CoralInteractionHandler {
if (!interaction.guild_id) {
throw new Error('This command can only be used in a guild');
}

yield {
action: ActionKind.EnsureDefer,
data: {
flags: MessageFlags.Ephemeral,
},
};

const target = options.getUser('target', true);
const reason = options.getString('reason', true);

const modCase = await this.dataManager.createModCase({
guildId: interaction.guild_id,
userId: target.id,
modId: interaction.member!.user.id,
reason,
kind: ModCaseKind.Warn,
});

yield {
action: ActionKind.Respond,
data: {
content: 'Successfully warned the user. DM sent: ',
},
};
}
}
Loading

0 comments on commit 3c8cd28

Please sign in to comment.