From c2271485a5b1f57f14d51eb68388336f5bdb1e39 Mon Sep 17 00:00:00 2001 From: smueller Date: Wed, 22 Jul 2026 11:07:38 +0200 Subject: [PATCH] Remove unnecessary description and whitespace from nexumi.mdc file --- .cursor/rules/nexumi.mdc | 2 - .env.example | 11 +++++ .eslintrc.cjs | 19 ++++++++ .gitignore | 11 +++++ .prettierrc | 6 +++ apps/bot/Dockerfile | 25 +++++++++++ apps/bot/package.json | 29 +++++++++++++ apps/bot/prisma/schema.prisma | 81 +++++++++++++++++++++++++++++++++++ apps/bot/src/db.ts | 3 ++ apps/bot/src/env.ts | 17 ++++++++ apps/bot/src/logger.ts | 6 +++ apps/bot/src/redis.ts | 6 +++ apps/bot/tsconfig.json | 8 ++++ docker-compose.yml | 52 ++++++++++++++++++++++ package.json | 26 +++++++++++ packages/shared/package.json | 16 +++++++ packages/shared/src/index.ts | 50 +++++++++++++++++++++ packages/shared/tsconfig.json | 8 ++++ pnpm-workspace.yaml | 3 ++ tsconfig.base.json | 15 +++++++ turbo.json | 23 ++++++++++ 21 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 .eslintrc.cjs create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 apps/bot/Dockerfile create mode 100644 apps/bot/package.json create mode 100644 apps/bot/prisma/schema.prisma create mode 100644 apps/bot/src/db.ts create mode 100644 apps/bot/src/env.ts create mode 100644 apps/bot/src/logger.ts create mode 100644 apps/bot/src/redis.ts create mode 100644 apps/bot/tsconfig.json create mode 100644 docker-compose.yml create mode 100644 package.json create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/tsconfig.json create mode 100644 pnpm-workspace.yaml create mode 100644 tsconfig.base.json create mode 100644 turbo.json diff --git a/.cursor/rules/nexumi.mdc b/.cursor/rules/nexumi.mdc index a7eb7be..2b68353 100644 --- a/.cursor/rules/nexumi.mdc +++ b/.cursor/rules/nexumi.mdc @@ -1,8 +1,6 @@ --- -description: Nexumi – verbindliche Projektregeln alwaysApply: true --- - # Nexumi – Projektregeln ## Quelle der Wahrheit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..aef5ea9 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +NODE_ENV=development +BOT_TOKEN= +BOT_CLIENT_ID= +BOT_GUILD_ID= +DATABASE_URL=postgresql://nexumi:nexumi@postgres:5432/nexumi +REDIS_URL=redis://redis:6379 +LOG_LEVEL=info +DEFAULT_LOCALE=de +METRICS_TOKEN= +SENTRY_DSN= +BACKUP_RETENTION_DAYS=14 diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..76aef51 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,19 @@ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: "latest", + sourceType: "module" + }, + plugins: ["@typescript-eslint"], + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + env: { + node: true, + es2022: true + }, + ignorePatterns: ["dist", ".turbo", "node_modules"], + rules: { + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-floating-promises": "error" + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a29b732 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules +.turbo +dist +coverage +.env +.env.local +pnpm-lock.yaml +apps/*/dist +apps/*/.next +packages/*/dist +prisma/migrations diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e66ae3d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100 +} diff --git a/apps/bot/Dockerfile b/apps/bot/Dockerfile new file mode 100644 index 0000000..a02a319 --- /dev/null +++ b/apps/bot/Dockerfile @@ -0,0 +1,25 @@ +FROM node:22-alpine AS base +WORKDIR /app +RUN corepack enable + +FROM base AS deps +COPY package.json pnpm-workspace.yaml turbo.json tsconfig.base.json ./ +COPY apps/bot/package.json apps/bot/package.json +COPY packages/shared/package.json packages/shared/package.json +RUN pnpm install --frozen-lockfile=false + +FROM deps AS build +COPY . . +RUN pnpm --filter @nexumi/shared build +RUN pnpm --filter @nexumi/bot prisma:generate +RUN pnpm --filter @nexumi/bot build + +FROM node:22-alpine AS runner +WORKDIR /app +RUN corepack enable +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/apps/bot/dist ./apps/bot/dist +COPY --from=build /app/apps/bot/prisma ./apps/bot/prisma +COPY --from=build /app/packages/shared/dist ./packages/shared/dist +COPY --from=build /app/apps/bot/package.json ./apps/bot/package.json +CMD ["node", "apps/bot/dist/index.js"] diff --git a/apps/bot/package.json b/apps/bot/package.json new file mode 100644 index 0000000..59b5bfa --- /dev/null +++ b/apps/bot/package.json @@ -0,0 +1,29 @@ +{ + "name": "@nexumi/bot", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "tsx watch src/index.ts", + "lint": "eslint src --ext .ts", + "test": "vitest run", + "typecheck": "tsc --noEmit -p tsconfig.json", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate deploy" + }, + "dependencies": { + "@nexumi/shared": "workspace:*", + "@prisma/client": "^5.22.0", + "bullmq": "^5.34.0", + "discord.js": "^14.17.3", + "dotenv": "^16.4.7", + "ioredis": "^5.4.2", + "pino": "^9.6.0", + "zod": "^3.24.1" + }, + "devDependencies": { + "prisma": "^5.22.0", + "tsx": "^4.19.2" + } +} diff --git a/apps/bot/prisma/schema.prisma b/apps/bot/prisma/schema.prisma new file mode 100644 index 0000000..8b4b3af --- /dev/null +++ b/apps/bot/prisma/schema.prisma @@ -0,0 +1,81 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Guild { + id String @id + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + settings GuildSettings? + cases Case[] +} + +model GuildSettings { + id String @id @default(cuid()) + guildId String @unique + locale String @default("de") + moderationEnabled Boolean @default(true) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) +} + +model User { + id String @id + createdAt DateTime @default(now()) + warnings Warning[] + notes ModNote[] +} + +model Case { + id String @id @default(cuid()) + caseNumber Int + guildId String + targetUserId String + moderatorId String + action String + reason String? + metadata Json? + deletedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + warnings Warning[] + moderationNote ModNote[] + + @@unique([guildId, caseNumber]) + @@index([guildId, targetUserId]) +} + +model Warning { + id String @id @default(cuid()) + guildId String + userId String + moderatorId String + reason String? + caseId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + case Case? @relation(fields: [caseId], references: [id], onDelete: SetNull) + + @@index([guildId, userId]) +} + +model ModNote { + id String @id @default(cuid()) + guildId String + userId String + moderatorId String + note String + caseId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + case Case? @relation(fields: [caseId], references: [id], onDelete: SetNull) + + @@index([guildId, userId]) +} diff --git a/apps/bot/src/db.ts b/apps/bot/src/db.ts new file mode 100644 index 0000000..9b6c4ce --- /dev/null +++ b/apps/bot/src/db.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from '@prisma/client'; + +export const prisma = new PrismaClient(); diff --git a/apps/bot/src/env.ts b/apps/bot/src/env.ts new file mode 100644 index 0000000..a0f968a --- /dev/null +++ b/apps/bot/src/env.ts @@ -0,0 +1,17 @@ +import { config } from 'dotenv'; +import { z } from 'zod'; + +config(); + +const EnvSchema = z.object({ + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), + BOT_TOKEN: z.string().min(1), + BOT_CLIENT_ID: z.string().min(1), + DATABASE_URL: z.string().url(), + REDIS_URL: z.string().url(), + LOG_LEVEL: z.string().default('info'), + DEFAULT_LOCALE: z.enum(['de', 'en']).default('de'), + BACKUP_RETENTION_DAYS: z.coerce.number().int().positive().default(14) +}); + +export const env = EnvSchema.parse(process.env); diff --git a/apps/bot/src/logger.ts b/apps/bot/src/logger.ts new file mode 100644 index 0000000..14000c6 --- /dev/null +++ b/apps/bot/src/logger.ts @@ -0,0 +1,6 @@ +import pino from 'pino'; +import { env } from './env.js'; + +export const logger = pino({ + level: env.LOG_LEVEL +}); diff --git a/apps/bot/src/redis.ts b/apps/bot/src/redis.ts new file mode 100644 index 0000000..956d7c7 --- /dev/null +++ b/apps/bot/src/redis.ts @@ -0,0 +1,6 @@ +import IORedis from 'ioredis'; +import { env } from './env.js'; + +export const redis = new IORedis(env.REDIS_URL, { + maxRetriesPerRequest: null +}); diff --git a/apps/bot/tsconfig.json b/apps/bot/tsconfig.json new file mode 100644 index 0000000..034165f --- /dev/null +++ b/apps/bot/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src", "prisma"] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..770e164 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: nexumi + POSTGRES_USER: nexumi + POSTGRES_PASSWORD: nexumi + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U nexumi -d nexumi"] + interval: 5s + timeout: 5s + retries: 10 + + redis: + image: redis:7-alpine + command: redis-server --appendonly yes + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 10 + + bot: + build: + context: . + dockerfile: apps/bot/Dockerfile + env_file: .env + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + webui: + image: node:22-alpine + working_dir: /app + command: sh -c "node -e \"require('http').createServer((_,res)=>res.end('webui scaffold')).listen(3000)\"" + ports: + - "3000:3000" + labels: + - "traefik.enable=true" + - "traefik.http.routers.nexumi.rule=Host(`nexumi.de`)" + - "traefik.http.services.nexumi.loadbalancer.server.port=3000" + +volumes: + postgres_data: + redis_data: + backups: diff --git a/package.json b/package.json new file mode 100644 index 0000000..a4faeeb --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "nexumi", + "version": "0.1.0", + "private": true, + "description": "Nexumi monorepo", + "packageManager": "pnpm@9.0.0", + "scripts": { + "build": "turbo build", + "dev": "turbo dev", + "lint": "turbo lint", + "test": "turbo test", + "typecheck": "turbo typecheck", + "prisma:generate": "pnpm --filter @nexumi/bot prisma:generate", + "prisma:migrate": "pnpm --filter @nexumi/bot prisma:migrate" + }, + "devDependencies": { + "@types/node": "^22.10.1", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint": "^9.16.0", + "prettier": "^3.4.1", + "turbo": "^2.3.3", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + } +} diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..63ce93d --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,16 @@ +{ + "name": "@nexumi/shared", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "lint": "eslint src --ext .ts", + "test": "vitest run", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "zod": "^3.24.1" + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..4607005 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,50 @@ +import { z } from 'zod'; + +export const LocaleSchema = z.enum(['de', 'en']); +export type Locale = z.infer; + +export const GuildSettingsSchema = z.object({ + guildId: z.string(), + locale: LocaleSchema.default('de'), + moderationEnabled: z.boolean().default(true) +}); + +export const ModerationActionSchema = z.enum([ + 'BAN', + 'UNBAN', + 'KICK', + 'TIMEOUT', + 'UN_TIMEOUT', + 'WARN_ADD', + 'WARN_REMOVE', + 'WARN_CLEAR', + 'PURGE', + 'LOCK', + 'UNLOCK', + 'SLOWMODE', + 'NICK_SET', + 'NICK_RESET', + 'CASE_EDIT', + 'CASE_DELETE', + 'MODNOTE_ADD' +]); + +export type ModerationAction = z.infer; + +type Dictionary = Record; +const de: Dictionary = { + 'generic.noPermission': 'Du hast keine Berechtigung für diese Aktion.', + 'moderation.success': 'Aktion erfolgreich ausgeführt.', + 'moderation.reason.none': 'Kein Grund angegeben' +}; +const en: Dictionary = { + 'generic.noPermission': 'You do not have permission for this action.', + 'moderation.success': 'Action executed successfully.', + 'moderation.reason.none': 'No reason provided' +}; + +const locales: Record = { de, en }; + +export function t(locale: Locale, key: string): string { + return locales[locale][key] ?? key; +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..5285d28 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"] +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..286cf7f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - apps/* + - packages/* diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..71ca403 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "sourceMap": true, + "types": ["node"] + } +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..9d49fd4 --- /dev/null +++ b/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "lint": { + "dependsOn": ["^lint"] + }, + "test": { + "dependsOn": ["^test"], + "outputs": ["coverage/**"] + }, + "typecheck": { + "dependsOn": ["^typecheck"] + } + } +}