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.
This commit is contained in:
94
packages/shared/src/premium.ts
Normal file
94
packages/shared/src/premium.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const snowflake = z.string().regex(/^\d{17,20}$/);
|
||||
|
||||
export const PremiumTierSchema = z.enum(['FREE', 'PREMIUM']);
|
||||
export type PremiumTier = z.infer<typeof PremiumTierSchema>;
|
||||
|
||||
export const PremiumFeatureKeySchema = z.enum(['tags', 'feeds', 'guildbackup']);
|
||||
export type PremiumFeatureKey = z.infer<typeof PremiumFeatureKeySchema>;
|
||||
|
||||
export const PremiumFeaturesSchema = z.object({
|
||||
tags: z.boolean(),
|
||||
feeds: z.boolean(),
|
||||
guildbackup: z.boolean()
|
||||
});
|
||||
export type PremiumFeatures = z.infer<typeof PremiumFeaturesSchema>;
|
||||
|
||||
export const PremiumTierConfigSchema = z.object({
|
||||
tier: PremiumTierSchema,
|
||||
maxTags: z.number().int().min(0).max(10_000),
|
||||
maxFeeds: z.number().int().min(0).max(10_000),
|
||||
maxBackups: z.number().int().min(0).max(10_000),
|
||||
features: PremiumFeaturesSchema
|
||||
});
|
||||
export type PremiumTierConfig = z.infer<typeof PremiumTierConfigSchema>;
|
||||
|
||||
export const PremiumTierConfigPatchSchema = PremiumTierConfigSchema.omit({ tier: true }).partial().refine(
|
||||
(value) => Object.keys(value).length > 0,
|
||||
{ message: 'At least one field is required' }
|
||||
);
|
||||
export type PremiumTierConfigPatch = z.infer<typeof PremiumTierConfigPatchSchema>;
|
||||
|
||||
export const DEFAULT_PREMIUM_TIER_CONFIGS: Record<PremiumTier, PremiumTierConfig> = {
|
||||
FREE: {
|
||||
tier: 'FREE',
|
||||
maxTags: 15,
|
||||
maxFeeds: 3,
|
||||
maxBackups: 2,
|
||||
features: { tags: true, feeds: true, guildbackup: true }
|
||||
},
|
||||
PREMIUM: {
|
||||
tier: 'PREMIUM',
|
||||
maxTags: 100,
|
||||
maxFeeds: 25,
|
||||
maxBackups: 25,
|
||||
features: { tags: true, feeds: true, guildbackup: true }
|
||||
}
|
||||
};
|
||||
|
||||
export const GuildPremiumAssignSchema = z.object({
|
||||
guildId: snowflake,
|
||||
tier: PremiumTierSchema,
|
||||
note: z.string().max(500).nullable().optional(),
|
||||
expiresAt: z.string().datetime().nullable().optional()
|
||||
});
|
||||
export type GuildPremiumAssign = z.infer<typeof GuildPremiumAssignSchema>;
|
||||
|
||||
export const UserPremiumAssignSchema = z.object({
|
||||
userId: snowflake,
|
||||
tier: PremiumTierSchema,
|
||||
note: z.string().max(500).nullable().optional(),
|
||||
expiresAt: z.string().datetime().nullable().optional()
|
||||
});
|
||||
export type UserPremiumAssign = z.infer<typeof UserPremiumAssignSchema>;
|
||||
|
||||
export const PremiumLimitResourceSchema = z.enum(['tags', 'feeds', 'backups']);
|
||||
export type PremiumLimitResource = z.infer<typeof PremiumLimitResourceSchema>;
|
||||
|
||||
export function limitKeyForResource(resource: PremiumLimitResource): 'maxTags' | 'maxFeeds' | 'maxBackups' {
|
||||
if (resource === 'tags') return 'maxTags';
|
||||
if (resource === 'feeds') return 'maxFeeds';
|
||||
return 'maxBackups';
|
||||
}
|
||||
|
||||
export function featureKeyForResource(resource: PremiumLimitResource): PremiumFeatureKey {
|
||||
if (resource === 'backups') return 'guildbackup';
|
||||
return resource;
|
||||
}
|
||||
|
||||
export function parsePremiumFeatures(raw: unknown): PremiumFeatures {
|
||||
const parsed = PremiumFeaturesSchema.safeParse(raw);
|
||||
if (parsed.success) {
|
||||
return parsed.data;
|
||||
}
|
||||
return { tags: true, feeds: true, guildbackup: true };
|
||||
}
|
||||
|
||||
export function isAssignmentActive(expiresAt: Date | string | null | undefined, now = new Date()): boolean {
|
||||
if (!expiresAt) {
|
||||
return true;
|
||||
}
|
||||
const date = typeof expiresAt === 'string' ? new Date(expiresAt) : expiresAt;
|
||||
return date.getTime() > now.getTime();
|
||||
}
|
||||
Reference in New Issue
Block a user