Skip to content
Merged
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
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ eslint-results.sarif
.sentryclirc


.direnv
.direnv
.tldr*
66 changes: 66 additions & 0 deletions DB_CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Database Changes for Achievement System

## Summary

**1 new table** needs to be created. No existing tables require column modifications.

---

## New Table: `DDUserAchievements`

### Table Configuration

- **Table Name:** `DDUserAchievements`
- **Paranoid:** Yes (soft deletes with `deletedAt` column)
- **Timestamps:** Auto-managed (`createdAt`, `updatedAt`, `deletedAt`)

### Columns

| Column | Type | Constraints | Description |
| --------------- | ------------ | -------------------------------- | ---------------------------------------------------------- |
| `id` | INTEGER | PRIMARY KEY, AUTO INCREMENT | Unique record identifier (auto-generated) |
| `achievementId` | VARCHAR(255) | NOT NULL | Achievement definition ID (e.g., "bump_first", "level_10") |
| `ddUserId` | BIGINT | NOT NULL, FOREIGN KEY → Users.id | Reference to the user who earned the achievement |
| `createdAt` | DATETIME | NOT NULL | When the record was created |
| `updatedAt` | DATETIME | NOT NULL | When the record was last updated |
| `deletedAt` | DATETIME | NULL | Soft delete timestamp (paranoid mode) |

### Indexes

| Index Name | Type | Columns | Purpose |
| ----------------------------- | ------ | --------------------------- | ---------------------------------------------- |
| `unique_achievement_ddUserId` | UNIQUE | `achievementId`, `ddUserId` | Prevents duplicate achievement awards per user |

### Foreign Keys

| Column | References | On Delete | On Update |
| ---------- | ---------- | ----------------- | ----------------- |
| `ddUserId` | `Users.id` | CASCADE (default) | CASCADE (default) |

---

## Existing Table Changes: `Users` (DDUser)

**No column changes required.**

Only a Sequelize relationship decorator was added (`@HasMany`), which creates a TypeScript relationship but does not modify the database schema.

---

## SQL Migration (Reference)

```sql
-- Create DDUserAchievements table
CREATE TABLE "DDUserAchievements" (
"id" SERIAL PRIMARY KEY,
"achievementId" VARCHAR(255) NOT NULL,
"ddUserId" BIGINT NOT NULL REFERENCES "Users"("id") ON DELETE CASCADE ON UPDATE CASCADE,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"deletedAt" TIMESTAMP WITH TIME ZONE
);

-- Create unique composite index
CREATE UNIQUE INDEX "unique_achievement_ddUserId"
ON "DDUserAchievements" ("achievementId", "ddUserId");
```
7 changes: 6 additions & 1 deletion src/Config.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const config: Config = {
},
devbin: {
url: "https://devbin.developerden.org",
api_url: "https://devbin-api.developerden.org",
api_url: "https://devbin-api.developerden.org",
threshold: 20,
},
branding: {
Expand Down Expand Up @@ -181,4 +181,9 @@ https://discord.gg/devden`),
],
],
},

achievements: {
notificationMode: "trigger",
fallbackChannel: "821820015917006868", // botCommands
},
};
5 changes: 5 additions & 0 deletions src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ const devConfig: Config = {
},

informationMessage: prodConfig.informationMessage,

achievements: {
notificationMode: "trigger",
fallbackChannel: "906954540039938048", // botCommands
},
};

const isProd = process.env.NODE_ENV === "production";
Expand Down
20 changes: 15 additions & 5 deletions src/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import type { Snowflake } from "discord.js";
import type { InformationMessage } from "./modules/information/information.js";
import type { BrandingConfig } from "./util/branding.js";

export interface AchievementsConfig {
/** Where to send achievement notifications */
notificationMode: "channel" | "dm" | "trigger";
/** Dedicated channel for notifications (required if mode is "channel") */
notificationChannel?: string;
/** Fallback channel if trigger location unavailable (for "trigger" mode) */
fallbackChannel?: string;
}

export interface ThreatDetectionConfig {
enabled: boolean;
alertChannel?: Snowflake;
Expand Down Expand Up @@ -62,11 +71,11 @@ export interface Config {
yesEmojiId: string;
noEmojiId: string;
};
devbin: {
url: string;
api_url: string;
threshold: number
};
devbin: {
url: string;
api_url: string;
threshold: number;
};
channels: {
welcome: string;
botCommands: string;
Expand Down Expand Up @@ -133,4 +142,5 @@ export interface Config {
allowAppeals: boolean;
appealCooldown: string;
};
achievements?: AchievementsConfig;
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as Sentry from "@sentry/bun";
import * as schedule from "node-schedule";
import { startHealthCheck } from "./healthcheck.js";
import { logger } from "./logging.js";
import { AchievementsModule } from "./modules/achievements/achievements.module.js";
import AskToAskModule from "./modules/askToAsk.module.js";
import { CoreModule } from "./modules/core/core.module.js";
import FaqModule from "./modules/faq/faq.module.js";
Expand Down Expand Up @@ -70,6 +71,7 @@ export const moduleManager = new ModuleManager(
ModmailModule,
LeaderboardModule,
UserModule,
AchievementsModule,
ThreatDetectionModule,
],
);
Expand Down
Loading