mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -0,0 +1,78 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: accessPolicy.util.js
|
||||
* Type of Program: Utility
|
||||
* Description: Evaluates a user's access to a course based on their active plan's access_rules JSONB.
|
||||
*
|
||||
* Rule types:
|
||||
* course_subscription_access — { type, levels: ['free','premium','exclusive'] }
|
||||
* → The plan grants access to these subscription levels only.
|
||||
* required_active_tier — { type, tier: 'premium' | 'exclusive' }
|
||||
* → The user's active tier must be at least this rank (exclusive satisfies premium).
|
||||
* group_restriction — { type, group_ids: [number, ...] }
|
||||
* → The user must belong to at least one of these groups.
|
||||
*
|
||||
* Fallback (no access_rules): uses simple tier rank comparison.
|
||||
***********************************************************************************************************************************************************************/
|
||||
'use strict';
|
||||
|
||||
// Default rank map used as fallback when a live DB map is not available.
|
||||
// Overridden at call time with ranks loaded from tier_categories.
|
||||
const TIER_RANK = { free: 0, premium: 1, exclusive: 2 };
|
||||
|
||||
/**
|
||||
* Evaluates whether a user can access a course.
|
||||
*
|
||||
* @param {object} ctx
|
||||
* @param {string} ctx.tier — user's active tier slug
|
||||
* @param {Array} ctx.access_rules — plan_policies.access_rules (may be empty)
|
||||
* @param {number[]} ctx.group_ids — group IDs the user belongs to
|
||||
* @param {object} course
|
||||
* @param {string} course.subscription — course subscription level (slug)
|
||||
* @param {Object} tierRankMap — { [slug]: rank } loaded from tier_categories; falls back to TIER_RANK
|
||||
* @returns {{ allowed: boolean, reason: string|null }}
|
||||
*/
|
||||
function evaluateCourseAccess(ctx, course, tierRankMap = TIER_RANK) {
|
||||
const { tier = 'free', access_rules = [], group_ids = [] } = ctx;
|
||||
const courseSubscription = course.subscription ?? 'free';
|
||||
const userRank = tierRankMap[tier] ?? 0;
|
||||
// Unknown required slug → Infinity so access is always denied (safe default)
|
||||
const courseRank = tierRankMap[courseSubscription] ?? Infinity;
|
||||
|
||||
// Rank-0 courses (default/free tier) are always accessible
|
||||
if (courseRank === 0) return { allowed: true, reason: null };
|
||||
|
||||
// No plan policy — fallback: compare user rank vs course subscription rank
|
||||
if (!access_rules || access_rules.length === 0) {
|
||||
return userRank >= courseRank
|
||||
? { allowed: true, reason: null }
|
||||
: { allowed: false, reason: 'tier_rank' };
|
||||
}
|
||||
|
||||
for (const rule of access_rules) {
|
||||
if (rule.type === 'course_subscription_access') {
|
||||
if (!(rule.levels ?? []).includes(courseSubscription)) {
|
||||
return { allowed: false, reason: 'subscription_access' };
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.type === 'required_active_tier') {
|
||||
// Unknown rule tier slug → Infinity, so the rule always blocks
|
||||
const reqRank = tierRankMap[rule.tier] ?? Infinity;
|
||||
if (userRank < reqRank) {
|
||||
return { allowed: false, reason: 'required_tier' };
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.type === 'group_restriction') {
|
||||
const required = (rule.group_ids ?? []).map(Number);
|
||||
if (required.length > 0) {
|
||||
const inGroup = required.some((gid) => group_ids.includes(gid));
|
||||
if (!inGroup) return { allowed: false, reason: 'group_restriction' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { allowed: true, reason: null };
|
||||
}
|
||||
|
||||
module.exports = { evaluateCourseAccess, TIER_RANK };
|
||||
Reference in New Issue
Block a user