feat(auth): include subscription tier in token and session payload

- Adds subscription tier info to access/refresh tokens.
- Stores tier details in session Redis record.
- Returns tier data in login/register response.
- Uses default tier when user has none.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-07 21:03:19 -03:00
1 parent 7f42def478
commit d27526e026
20 files changed
+2133 -40

No files matched your search

+112
View File
@@ -199,6 +199,118 @@ export const registerServerCharacterSchema = z.object({
isMain: z.boolean().optional().default(false),
});
// ============================================
// SCHEMAS DE ASSINATURAS E PERMISSÕES
// ============================================
const tierSlugRegex = /^[a-z0-9-]+$/;
const featureKeyRegex = /^[a-z0-9_.-]+$/;
const subscriptionTierFeatureSchema = z.object({
featureKey: z
.string()
.min(2, "featureKey deve ter no mínimo 2 caracteres")
.max(100, "featureKey deve ter no máximo 100 caracteres")
.regex(featureKeyRegex, "featureKey deve conter apenas letras, números, ponto, hífen ou underscore")
.transform((key) => key.trim().toLowerCase()),
label: z
.string()
.min(3, "Label deve ter no mínimo 3 caracteres")
.max(255, "Label deve ter no máximo 255 caracteres")
.transform((value) => value.trim()),
description: z
.string()
.max(500, "Descrição deve ter no máximo 500 caracteres")
.transform((value) => value.trim())
.optional()
.nullable(),
sortOrder: z.number().int().min(0).max(1000).optional(),
});
export const createSubscriptionTierSchema = z
.object({
name: z
.string()
.min(3, "Nome deve ter no mínimo 3 caracteres")
.max(100, "Nome deve ter no máximo 100 caracteres")
.transform((value) => value.trim()),
slug: z
.string()
.min(2, "Slug deve ter no mínimo 2 caracteres")
.max(50, "Slug deve ter no máximo 50 caracteres")
.regex(tierSlugRegex, "Slug deve conter apenas letras minúsculas, números e hífen")
.transform((value) => value.trim().toLowerCase()),
description: z
.string()
.max(500, "Descrição deve ter no máximo 500 caracteres")
.transform((value) => value.trim())
.optional()
.nullable(),
level: z.number().int().min(0).max(99).optional(),
priceMonthly: z.number().min(0).max(100000).nullable().optional(),
priceYearly: z.number().min(0).max(1000000).nullable().optional(),
isActive: z.boolean().optional(),
features: z.array(subscriptionTierFeatureSchema).max(50).optional(),
})
.refine(
(data) => {
if (
data.priceMonthly !== undefined &&
data.priceYearly !== undefined &&
data.priceYearly !== null &&
data.priceMonthly !== null
) {
return data.priceYearly >= data.priceMonthly;
}
return true;
},
{
message: "Preço anual deve ser maior ou igual ao mensal",
path: ["priceYearly"],
}
);
export const updateSubscriptionTierSchema = createSubscriptionTierSchema
.partial()
.extend({
slug: z
.string()
.min(2, "Slug deve ter no mínimo 2 caracteres")
.max(50, "Slug deve ter no máximo 50 caracteres")
.regex(tierSlugRegex, "Slug deve conter apenas letras minúsculas, números e hífen")
.transform((value) => value.trim().toLowerCase())
.optional(),
features: z
.array(
subscriptionTierFeatureSchema.extend({
featureId: z.number().int().positive().optional(),
_delete: z.boolean().optional(),
})
)
.max(50)
.optional(),
});
export const assignSubscriptionTierSchema = z
.object({
userId: z.number().int().positive(),
tierId: z.number().int().positive().nullable().optional(),
tierSlug: z
.string()
.min(2, "Slug deve ter no mínimo 2 caracteres")
.max(50, "Slug deve ter no máximo 50 caracteres")
.regex(tierSlugRegex, "Slug deve conter apenas letras minúsculas, números e hífen")
.transform((value) => value.trim().toLowerCase())
.optional(),
})
.refine(
(data) => data.tierId !== undefined || data.tierSlug !== undefined,
{
message: "Informe tierId ou tierSlug",
path: ["tierId"],
}
);
// Schema para registro de usuário
export const registerSchema = z.object({
username: usernameSchema,