Files
Nexumi/apps/bot/src/premium.ts
smueller 08af99ed52 Implement premium features and GDPR compliance in bot and WebUI
- Added new models for premium tier configurations and assignments, enabling feature limits for free and premium users.
- Introduced GDPR deletion logging and commands for user data management.
- Enhanced logging configuration with retention days for audit logs.
- Updated bot commands and job processing to include new premium and GDPR functionalities.
- Improved localization with new keys for premium features and GDPR commands in both English and German.
- Added UI components for managing premium settings and logging retention in the WebUI.
2026-07-22 16:59:23 +02:00

101 lines
3.0 KiB
TypeScript

import type { PrismaClient } from '@prisma/client';
import {
DEFAULT_PREMIUM_TIER_CONFIGS,
featureKeyForResource,
isAssignmentActive,
limitKeyForResource,
parsePremiumFeatures,
PremiumTierSchema,
type PremiumLimitResource,
type PremiumTier,
type PremiumTierConfig
} from '@nexumi/shared';
export class PremiumLimitError extends Error {
constructor(
public readonly code: 'feature_disabled' | 'limit_reached',
public readonly resource: PremiumLimitResource,
public readonly limit: number,
public readonly current: number,
public readonly tier: PremiumTier
) {
super(code);
}
}
function toConfig(row: {
tier: string;
maxTags: number;
maxFeeds: number;
maxBackups: number;
features: unknown;
}): PremiumTierConfig {
return {
tier: PremiumTierSchema.parse(row.tier),
maxTags: row.maxTags,
maxFeeds: row.maxFeeds,
maxBackups: row.maxBackups,
features: parsePremiumFeatures(row.features)
};
}
export async function ensurePremiumTierConfigs(prisma: PrismaClient): Promise<PremiumTierConfig[]> {
const configs: PremiumTierConfig[] = [];
for (const tier of PremiumTierSchema.options) {
const defaults = DEFAULT_PREMIUM_TIER_CONFIGS[tier];
const row = await prisma.premiumTierConfig.upsert({
where: { tier },
create: {
tier,
maxTags: defaults.maxTags,
maxFeeds: defaults.maxFeeds,
maxBackups: defaults.maxBackups,
features: defaults.features
},
update: {}
});
configs.push(toConfig(row));
}
return configs;
}
export async function getPremiumTierConfigs(prisma: PrismaClient): Promise<PremiumTierConfig[]> {
return ensurePremiumTierConfigs(prisma);
}
export async function resolveGuildTier(prisma: PrismaClient, guildId: string): Promise<PremiumTier> {
const assignment = await prisma.guildPremiumAssignment.findUnique({ where: { guildId } });
if (!assignment || !isAssignmentActive(assignment.expiresAt)) {
return 'FREE';
}
const parsed = PremiumTierSchema.safeParse(assignment.tier);
return parsed.success ? parsed.data : 'FREE';
}
export async function resolveGuildPremiumConfig(
prisma: PrismaClient,
guildId: string
): Promise<PremiumTierConfig> {
const tier = await resolveGuildTier(prisma, guildId);
const configs = await ensurePremiumTierConfigs(prisma);
return configs.find((config) => config.tier === tier) ?? DEFAULT_PREMIUM_TIER_CONFIGS[tier];
}
export async function assertPremiumLimit(
prisma: PrismaClient,
guildId: string,
resource: PremiumLimitResource,
currentCount: number
): Promise<PremiumTierConfig> {
const config = await resolveGuildPremiumConfig(prisma, guildId);
const featureKey = featureKeyForResource(resource);
if (!config.features[featureKey]) {
throw new PremiumLimitError('feature_disabled', resource, 0, currentCount, config.tier);
}
const limit = config[limitKeyForResource(resource)];
if (currentCount >= limit) {
throw new PremiumLimitError('limit_reached', resource, limit, currentCount, config.tier);
}
return config;
}