mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 15:35:55 +08:00
Feat/education api (#17168)
This commit is contained in:
parent
d1801b1f2e
commit
9c4be5d098
@ -848,6 +848,11 @@ class AccountConfig(BaseSettings):
|
|||||||
default=5,
|
default=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
EDUCATION_ENABLED: bool = Field(
|
||||||
|
description="whether to enable education identity",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FeatureConfig(
|
class FeatureConfig(
|
||||||
# place the configs in alphabet order
|
# place the configs in alphabet order
|
||||||
|
@ -103,6 +103,18 @@ class AccountInFreezeError(BaseHTTPException):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EducationVerifyLimitError(BaseHTTPException):
|
||||||
|
error_code = "education_verify_limit"
|
||||||
|
description = "Rate limit exceeded"
|
||||||
|
code = 429
|
||||||
|
|
||||||
|
|
||||||
|
class EducationActivateLimitError(BaseHTTPException):
|
||||||
|
error_code = "education_activate_limit"
|
||||||
|
description = "Rate limit exceeded"
|
||||||
|
code = 429
|
||||||
|
|
||||||
|
|
||||||
class CompilanceRateLimitError(BaseHTTPException):
|
class CompilanceRateLimitError(BaseHTTPException):
|
||||||
error_code = "compilance_rate_limit"
|
error_code = "compilance_rate_limit"
|
||||||
description = "Rate limit exceeded for downloading compliance report."
|
description = "Rate limit exceeded for downloading compliance report."
|
||||||
|
@ -15,7 +15,13 @@ from controllers.console.workspace.error import (
|
|||||||
InvalidInvitationCodeError,
|
InvalidInvitationCodeError,
|
||||||
RepeatPasswordNotMatchError,
|
RepeatPasswordNotMatchError,
|
||||||
)
|
)
|
||||||
from controllers.console.wraps import account_initialization_required, enterprise_license_required, setup_required
|
from controllers.console.wraps import (
|
||||||
|
account_initialization_required,
|
||||||
|
cloud_edition_billing_enabled,
|
||||||
|
enterprise_license_required,
|
||||||
|
only_edition_cloud,
|
||||||
|
setup_required,
|
||||||
|
)
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.member_fields import account_fields
|
from fields.member_fields import account_fields
|
||||||
from libs.helper import TimestampField, timezone
|
from libs.helper import TimestampField, timezone
|
||||||
@ -292,6 +298,79 @@ class AccountDeleteUpdateFeedbackApi(Resource):
|
|||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
|
|
||||||
|
class EducationVerifyApi(Resource):
|
||||||
|
verify_fields = {
|
||||||
|
"token": fields.String,
|
||||||
|
}
|
||||||
|
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@only_edition_cloud
|
||||||
|
@cloud_edition_billing_enabled
|
||||||
|
@marshal_with(verify_fields)
|
||||||
|
def get(self):
|
||||||
|
account = current_user
|
||||||
|
|
||||||
|
return BillingService.EducationIdentity.verify(account.id, account.email)
|
||||||
|
|
||||||
|
|
||||||
|
class EducationApi(Resource):
|
||||||
|
status_fields = {
|
||||||
|
"result": fields.Boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@only_edition_cloud
|
||||||
|
@cloud_edition_billing_enabled
|
||||||
|
def post(self):
|
||||||
|
account = current_user
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("token", type=str, required=True, location="json")
|
||||||
|
parser.add_argument("institution", type=str, required=True, location="json")
|
||||||
|
parser.add_argument("role", type=str, required=True, location="json")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
return BillingService.EducationIdentity.activate(account, args["token"], args["institution"], args["role"])
|
||||||
|
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@only_edition_cloud
|
||||||
|
@cloud_edition_billing_enabled
|
||||||
|
@marshal_with(status_fields)
|
||||||
|
def get(self):
|
||||||
|
account = current_user
|
||||||
|
|
||||||
|
return BillingService.EducationIdentity.is_active(account.id)
|
||||||
|
|
||||||
|
|
||||||
|
class EducationAutoCompleteApi(Resource):
|
||||||
|
data_fields = {
|
||||||
|
"data": fields.List(fields.String),
|
||||||
|
"curr_page": fields.Integer,
|
||||||
|
"has_next": fields.Boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@only_edition_cloud
|
||||||
|
@cloud_edition_billing_enabled
|
||||||
|
@marshal_with(data_fields)
|
||||||
|
def get(self):
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("keywords", type=str, required=True, location="args")
|
||||||
|
parser.add_argument("page", type=int, required=False, location="args", default=0)
|
||||||
|
parser.add_argument("limit", type=int, required=False, location="args", default=20)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
return BillingService.EducationIdentity.autocomplete(args["keywords"], args["page"], args["limit"])
|
||||||
|
|
||||||
|
|
||||||
# Register API resources
|
# Register API resources
|
||||||
api.add_resource(AccountInitApi, "/account/init")
|
api.add_resource(AccountInitApi, "/account/init")
|
||||||
api.add_resource(AccountProfileApi, "/account/profile")
|
api.add_resource(AccountProfileApi, "/account/profile")
|
||||||
@ -305,5 +384,8 @@ api.add_resource(AccountIntegrateApi, "/account/integrates")
|
|||||||
api.add_resource(AccountDeleteVerifyApi, "/account/delete/verify")
|
api.add_resource(AccountDeleteVerifyApi, "/account/delete/verify")
|
||||||
api.add_resource(AccountDeleteApi, "/account/delete")
|
api.add_resource(AccountDeleteApi, "/account/delete")
|
||||||
api.add_resource(AccountDeleteUpdateFeedbackApi, "/account/delete/feedback")
|
api.add_resource(AccountDeleteUpdateFeedbackApi, "/account/delete/feedback")
|
||||||
|
api.add_resource(EducationVerifyApi, "/account/education/verify")
|
||||||
|
api.add_resource(EducationApi, "/account/education")
|
||||||
|
api.add_resource(EducationAutoCompleteApi, "/account/education/autocomplete")
|
||||||
# api.add_resource(AccountEmailApi, '/account/email')
|
# api.add_resource(AccountEmailApi, '/account/email')
|
||||||
# api.add_resource(AccountEmailVerifyApi, '/account/email-verify')
|
# api.add_resource(AccountEmailVerifyApi, '/account/email-verify')
|
||||||
|
@ -54,6 +54,17 @@ def only_edition_self_hosted(view):
|
|||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
def cloud_edition_billing_enabled(view):
|
||||||
|
@wraps(view)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
features = FeatureService.get_features(current_user.current_tenant_id)
|
||||||
|
if not features.billing.enabled:
|
||||||
|
abort(403, "Billing feature is not enabled.")
|
||||||
|
return view(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
def cloud_edition_billing_resource_check(resource: str):
|
def cloud_edition_billing_resource_check(resource: str):
|
||||||
def interceptor(view):
|
def interceptor(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
|
@ -6,7 +6,7 @@ from tenacity import retry, retry_if_exception_type, stop_before_delay, wait_fix
|
|||||||
|
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.helper import RateLimiter
|
from libs.helper import RateLimiter
|
||||||
from models.account import TenantAccountJoin, TenantAccountRole
|
from models.account import Account, TenantAccountJoin, TenantAccountRole
|
||||||
|
|
||||||
|
|
||||||
class BillingService:
|
class BillingService:
|
||||||
@ -106,6 +106,48 @@ class BillingService:
|
|||||||
json = {"email": email, "feedback": feedback}
|
json = {"email": email, "feedback": feedback}
|
||||||
return cls._send_request("POST", "/account/delete-feedback", json=json)
|
return cls._send_request("POST", "/account/delete-feedback", json=json)
|
||||||
|
|
||||||
|
class EducationIdentity:
|
||||||
|
verification_rate_limit = RateLimiter(prefix="edu_verification_rate_limit", max_attempts=10, time_window=60)
|
||||||
|
activation_rate_limit = RateLimiter(prefix="edu_activation_rate_limit", max_attempts=10, time_window=60)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def verify(cls, account_id: str, account_email: str):
|
||||||
|
if cls.verification_rate_limit.is_rate_limited(account_email):
|
||||||
|
from controllers.console.error import EducationVerifyLimitError
|
||||||
|
|
||||||
|
raise EducationVerifyLimitError()
|
||||||
|
|
||||||
|
cls.verification_rate_limit.increment_rate_limit(account_email)
|
||||||
|
|
||||||
|
params = {"account_id": account_id}
|
||||||
|
return BillingService._send_request("GET", "/education/verify", params=params)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_active(cls, account_id: str):
|
||||||
|
params = {"account_id": account_id}
|
||||||
|
return BillingService._send_request("GET", "/education/status", params=params)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def activate(cls, account: Account, token: str, institution: str, role: str):
|
||||||
|
if cls.activation_rate_limit.is_rate_limited(account.email):
|
||||||
|
from controllers.console.error import EducationActivateLimitError
|
||||||
|
|
||||||
|
raise EducationActivateLimitError()
|
||||||
|
|
||||||
|
cls.activation_rate_limit.increment_rate_limit(account.email)
|
||||||
|
params = {"account_id": account.id, "curr_tenant_id": account.current_tenant_id}
|
||||||
|
json = {
|
||||||
|
"institution": institution,
|
||||||
|
"token": token,
|
||||||
|
"role": role,
|
||||||
|
}
|
||||||
|
return BillingService._send_request("POST", "/education/", json=json, params=params)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def autocomplete(cls, keywords: str, page: int = 0, limit: int = 20):
|
||||||
|
params = {"keywords": keywords, "page": page, "limit": limit}
|
||||||
|
return BillingService._send_request("GET", "/education/autocomplete", params=params)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_compliance_download_link(
|
def get_compliance_download_link(
|
||||||
cls,
|
cls,
|
||||||
|
@ -17,6 +17,11 @@ class BillingModel(BaseModel):
|
|||||||
subscription: SubscriptionModel = SubscriptionModel()
|
subscription: SubscriptionModel = SubscriptionModel()
|
||||||
|
|
||||||
|
|
||||||
|
class EducationModel(BaseModel):
|
||||||
|
enabled: bool = False
|
||||||
|
activated: bool = False
|
||||||
|
|
||||||
|
|
||||||
class LimitationModel(BaseModel):
|
class LimitationModel(BaseModel):
|
||||||
size: int = 0
|
size: int = 0
|
||||||
limit: int = 0
|
limit: int = 0
|
||||||
@ -38,6 +43,7 @@ class LicenseModel(BaseModel):
|
|||||||
|
|
||||||
class FeatureModel(BaseModel):
|
class FeatureModel(BaseModel):
|
||||||
billing: BillingModel = BillingModel()
|
billing: BillingModel = BillingModel()
|
||||||
|
education: EducationModel = EducationModel()
|
||||||
members: LimitationModel = LimitationModel(size=0, limit=1)
|
members: LimitationModel = LimitationModel(size=0, limit=1)
|
||||||
apps: LimitationModel = LimitationModel(size=0, limit=10)
|
apps: LimitationModel = LimitationModel(size=0, limit=10)
|
||||||
vector_space: LimitationModel = LimitationModel(size=0, limit=5)
|
vector_space: LimitationModel = LimitationModel(size=0, limit=5)
|
||||||
@ -128,6 +134,7 @@ class FeatureService:
|
|||||||
features.can_replace_logo = dify_config.CAN_REPLACE_LOGO
|
features.can_replace_logo = dify_config.CAN_REPLACE_LOGO
|
||||||
features.model_load_balancing_enabled = dify_config.MODEL_LB_ENABLED
|
features.model_load_balancing_enabled = dify_config.MODEL_LB_ENABLED
|
||||||
features.dataset_operator_enabled = dify_config.DATASET_OPERATOR_ENABLED
|
features.dataset_operator_enabled = dify_config.DATASET_OPERATOR_ENABLED
|
||||||
|
features.education.enabled = dify_config.EDUCATION_ENABLED
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _fulfill_params_from_billing_api(cls, features: FeatureModel, tenant_id: str):
|
def _fulfill_params_from_billing_api(cls, features: FeatureModel, tenant_id: str):
|
||||||
@ -136,6 +143,7 @@ class FeatureService:
|
|||||||
features.billing.enabled = billing_info["enabled"]
|
features.billing.enabled = billing_info["enabled"]
|
||||||
features.billing.subscription.plan = billing_info["subscription"]["plan"]
|
features.billing.subscription.plan = billing_info["subscription"]["plan"]
|
||||||
features.billing.subscription.interval = billing_info["subscription"]["interval"]
|
features.billing.subscription.interval = billing_info["subscription"]["interval"]
|
||||||
|
features.education.activated = billing_info["subscription"].get("education", False)
|
||||||
|
|
||||||
if "members" in billing_info:
|
if "members" in billing_info:
|
||||||
features.members.size = billing_info["members"]["size"]
|
features.members.size = billing_info["members"]["size"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user