Remove unnecessary description and whitespace from nexumi.mdc file
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
---
|
---
|
||||||
description: Nexumi – verbindliche Projektregeln
|
|
||||||
alwaysApply: true
|
alwaysApply: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Nexumi – Projektregeln
|
# Nexumi – Projektregeln
|
||||||
|
|
||||||
## Quelle der Wahrheit
|
## Quelle der Wahrheit
|
||||||
|
|||||||
11
.env.example
Normal file
11
.env.example
Normal file
@@ -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
|
||||||
19
.eslintrc.cjs
Normal file
19
.eslintrc.cjs
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
};
|
||||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
node_modules
|
||||||
|
.turbo
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
pnpm-lock.yaml
|
||||||
|
apps/*/dist
|
||||||
|
apps/*/.next
|
||||||
|
packages/*/dist
|
||||||
|
prisma/migrations
|
||||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
25
apps/bot/Dockerfile
Normal file
25
apps/bot/Dockerfile
Normal file
@@ -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"]
|
||||||
29
apps/bot/package.json
Normal file
29
apps/bot/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
81
apps/bot/prisma/schema.prisma
Normal file
81
apps/bot/prisma/schema.prisma
Normal file
@@ -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])
|
||||||
|
}
|
||||||
3
apps/bot/src/db.ts
Normal file
3
apps/bot/src/db.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
export const prisma = new PrismaClient();
|
||||||
17
apps/bot/src/env.ts
Normal file
17
apps/bot/src/env.ts
Normal file
@@ -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);
|
||||||
6
apps/bot/src/logger.ts
Normal file
6
apps/bot/src/logger.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import pino from 'pino';
|
||||||
|
import { env } from './env.js';
|
||||||
|
|
||||||
|
export const logger = pino({
|
||||||
|
level: env.LOG_LEVEL
|
||||||
|
});
|
||||||
6
apps/bot/src/redis.ts
Normal file
6
apps/bot/src/redis.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import IORedis from 'ioredis';
|
||||||
|
import { env } from './env.js';
|
||||||
|
|
||||||
|
export const redis = new IORedis(env.REDIS_URL, {
|
||||||
|
maxRetriesPerRequest: null
|
||||||
|
});
|
||||||
8
apps/bot/tsconfig.json
Normal file
8
apps/bot/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src", "prisma"]
|
||||||
|
}
|
||||||
52
docker-compose.yml
Normal file
52
docker-compose.yml
Normal file
@@ -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:
|
||||||
26
package.json
Normal file
26
package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
packages/shared/package.json
Normal file
16
packages/shared/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
50
packages/shared/src/index.ts
Normal file
50
packages/shared/src/index.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const LocaleSchema = z.enum(['de', 'en']);
|
||||||
|
export type Locale = z.infer<typeof LocaleSchema>;
|
||||||
|
|
||||||
|
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<typeof ModerationActionSchema>;
|
||||||
|
|
||||||
|
type Dictionary = Record<string, string>;
|
||||||
|
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<Locale, Dictionary> = { de, en };
|
||||||
|
|
||||||
|
export function t(locale: Locale, key: string): string {
|
||||||
|
return locales[locale][key] ?? key;
|
||||||
|
}
|
||||||
8
packages/shared/tsconfig.json
Normal file
8
packages/shared/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
packages:
|
||||||
|
- apps/*
|
||||||
|
- packages/*
|
||||||
15
tsconfig.base.json
Normal file
15
tsconfig.base.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
23
turbo.json
Normal file
23
turbo.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user